TRAIL BLAZER QUEST: ‘Procedural Generation’ - Solutions

Quest: Trail Blazer Quest
Challenge: Procedural Generation

Feel free to share your solutions, ideas and creations below. If you get stuck, you can find some ideas here for completing this challenge.

This was me tackling the challenge of Procedural Generation for the first time.
Since then, and after doing a few more quests, i’m getting a lot more comfortable with this concept, and have been making lots of things procedurally generate :smiley:
But this was my path as i figured it out for the first time on this challenge.

1 Like

Finally got this working. My first step was to trash all the assets imported as part of the package (I really hate disorganization in my file structure) and create new assets with descriptive names. Once I had a few assets created I set to work creating a few block patterns to use with the generator script only to find out there was some issue with the player mover. I tracked that down, turned out I needed to create a Ground layer and assign my blocks to it, and set about writing my first procedural generator.

My final script

PlatformGenerator.cs

public class PlatformGenerator : MonoBehaviour
{
    [SerializeField] Transform myCamera;
    [SerializeField] float verticalShift;
    [SerializeField] float horizontalShift;
    [SerializeField] float generationPosition;
    [SerializeField] float maxElements;

    int elementCount = 0;
    bool generateEnding = false;

    BlockPicker blockPicker;
    Vector3 endPosition;
    string[] platformTypes = new string[] { "Multi", "Single" };


    #region "start routine"
    void Start()
    {
        blockPicker = GetComponent<BlockPicker>();

        GameObject startPlatform = GameObject.Find("Start Position Group");
        if (!startPlatform) CreateStartPosition();
        else
        {
            CountElements();
            endPosition = blockPicker.GetStartElementEndPoint();
        }
    }

    void CreateStartPosition()
    {
        var newBlock = Instantiate(blockPicker.GetStartElement(), new Vector3(), Quaternion.identity) as GameObject;
        newBlock.name = "Start Position Group";
        newBlock.transform.parent = this.transform;
        elementCount++;
        endPosition = blockPicker.GetStartElementEndPoint();

        Debug.Log("current end point" + endPosition);
    }

    void CountElements() { for (int i = 1; i <= this.transform.childCount; i++) elementCount++; }
    #endregion


    #region "Main Loop"
    void Update()
    {
        if (endPosition.x - myCamera.transform.position.x < generationPosition)
        {
            if (elementCount > (maxElements-1)) generateEnding = true;
            else GeneratePlatform();
        }
    }

    void GeneratePlatform()
    {
        string platformType = SelectPlatformType();
        Block generatedBlock = blockPicker.GenerateElement(platformType);

        GameObject newElement = blockPicker.GetDefaultElement();
        if (generateEnding) newElement = blockPicker.GetEndingElement();
        else if (generatedBlock.newBlock) newElement = generatedBlock.newBlock;

        Vector3 targetPosition = CalculateNewPosition();
        Debug.Log("Instantiating at: " + targetPosition);
        
        var newBlock = Instantiate(newElement, targetPosition, Quaternion.identity) as GameObject;
        if (platformType == "Single") newBlock.transform.localEulerAngles = new Vector3(0, 0, Random.Range(-45, 45));
        SetUpNewBlock(newBlock);
        AssignEndPoint(targetPosition, generatedBlock);
    }

    string SelectPlatformType() { return platformTypes[Mathf.RoundToInt(Random.Range(0f, platformTypes.Length-1))]; }

    Vector3 CalculateNewPosition()
    {
        float newXPos = endPosition.x + (Random.value * horizontalShift);
        float newYPos = endPosition.y + (Random.value * (verticalShift * 2)) - verticalShift;
        Debug.Log("Next Start: " + newXPos + "," + newYPos);
        
        return new Vector3(newXPos, newYPos, 0f);
    }

    private void SetUpNewBlock(GameObject block)
    {
        block.transform.parent = this.transform;
        elementCount++;
    }

    void AssignEndPoint(Vector3 startPos, Block newElement)
    {
        if (!newElement.endPoint) endPosition = new Vector3();
        else endPosition = startPos + newElement.endPoint.position;
        Debug.Log("Next end point is: " + endPosition);
    }
    #endregion
}

Block.cs

public struct Block
{
    public GameObject newBlock;
    public Transform endPoint;

    public Block(GameObject element, Vector3 rotation, Transform ending)
    {
        newBlock = element;
        newBlock.transform.localEulerAngles = rotation;
        endPoint = ending;
    }
}

BlockPicker.cs

public class BlockPicker : MonoBehaviour
{
    [SerializeField] GameObject defaultElement;
    [SerializeField] GameObject startElement;
    [SerializeField] GameObject endElement;

    [SerializeField] GameObject[] singleBlockPlatforms;
    [SerializeField] GameObject[] multiBlockPlatforms;

    public GameObject GetDefaultElement() { return defaultElement; }

    public GameObject GetStartElement() { return startElement; }

    public GameObject GetEndingElement() { return endElement; }

    public Vector3 GetStartElementEndPoint() { return FindEndPoint(startElement).position; }

    public Block GenerateElement(string type)
    {
        GameObject prefab;
        if (type == "Single") prefab = SelectSingleBlockElement();
        else if (type == "Multi") prefab = SelectMultiBlockElement();
        else prefab = defaultElement;
        Transform ending = FindEndPoint(prefab);
        return new Block(prefab, new Vector3(), ending);
    }

    GameObject SelectSingleBlockElement()
    {
        return singleBlockPlatforms[Mathf.RoundToInt(Random.Range(0, singleBlockPlatforms.Length - 1))];
    }

    GameObject SelectMultiBlockElement()
    {
        return multiBlockPlatforms[Mathf.RoundToInt(Random.Range(0, multiBlockPlatforms.Length - 1))];
    }

    Transform FindEndPoint(GameObject element)
    {
        return element.GetComponentInChildren<EndPoint>().transform;
    }

I had trouble with Unity refusing to automatically calculate the endpoint of each block so ended up remaking all of my assets so that I would only have 1 End Point Object in each and assigned an empty script (EndPoint.cs) to it in order to allow Unity to easily identify what I wanted it to find.

and after 18 hours of coding (14 of which were spent hunting the same problem) here is a short vid showing the final product.

1 Like

Looks great :slight_smile: nice work.
Like the single blocks mixed in there at random angles.

This challenge was my first time hitting Procedural Generating to. Felt things came together for me, and i’ve used the concepts i learnt from this in so many challenges and games moving forwards.
I hope you feel the same growth too :).

1 Like