Help with infinite random generated levels.

Hi, I’m making an infinite level that will keep generating random buildings as long the player keeps on the level.


Basically the script generates a random template of the building with a random length and random height for each length, then start to fill with wall/roof/details/etc.


The first script it fills each section with a wall 1x1, so a wall with 5 meters will be filled with 5 instances of the wall. But this was inefficient and cause an FPS drop when spawning.


I made some improvements in the script and now he changes the scale and tile of texture and makes 1 big wall as far as possible. The FPS keeps dropping, but now is like 1/10 of second or less, but is enough to notice while playing.


This is the code:

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

public class LevelGenerator : MonoBehaviour
{
    Vector3 leftSpawner = new Vector3(-10, 0, 0);
    Vector3 rightSpawner = new Vector3(10, 0, 0);

    GameObject player;

    public GameObject buildingPivot;
    public GameObject emptyPrefab;

    public GameObject groundCivilian;

    List<GameObject> roof = new List<GameObject>();
    List<GameObject> floor1Details = new List<GameObject>();
    List<GameObject> floor1Main = new List<GameObject>();
    List<GameObject> floor2Details = new List<GameObject>();
    List<GameObject> floor3Details = new List<GameObject>();
    List<GameObject> floor4Details = new List<GameObject>();
    List<GameObject> supportPillar = new List<GameObject>();

    List<GameObject> citizenWall = new List<GameObject>();

    List<GameObject> borderBase = new List<GameObject>();
    List<GameObject> borderTop = new List<GameObject>();
    List<GameObject> borderRoof = new List<GameObject>();
    List<GameObject> borderRoot = new List<GameObject>();

    float lastBuilding_L = 0;
    float lastBuilding_R = 0;

    float SPAWNERS_STARTING_POINT = -15;

    float BASEHEIGHT = 5f;
    float BASELENGHT = 5f;

    Vector3 leftCalc;
    Vector3 rightCalc;

    float leftDist;
    float rightDist;

    void Start()
    {
        floor1Details.AddRange(Resources.LoadAll("Floor1/Details").Cast<GameObject>().ToArray());
        floor1Main.AddRange(Resources.LoadAll("Floor1/Main").Cast<GameObject>().ToArray());
        floor2Details.AddRange(Resources.LoadAll("Floor2").Cast<GameObject>().ToArray());
        floor3Details.AddRange(Resources.LoadAll("Floor3").Cast<GameObject>().ToArray());
        floor4Details.AddRange(Resources.LoadAll("Floor4").Cast<GameObject>().ToArray());
        roof.AddRange(Resources.LoadAll("Roof").Cast<GameObject>().ToArray());
        supportPillar.AddRange(Resources.LoadAll("Support_Pillar").Cast<GameObject>().ToArray());
        citizenWall.AddRange(Resources.LoadAll("Citizen_Wall").Cast<GameObject>().ToArray());
        borderBase.AddRange(Resources.LoadAll("Border1/Border_Base_1").Cast<GameObject>().ToArray());
        borderTop.AddRange(Resources.LoadAll("Border1/Border_Top_1").Cast<GameObject>().ToArray());
        borderRoof.AddRange(Resources.LoadAll("Border1/Border_Roof_1").Cast<GameObject>().ToArray());
        borderRoot.AddRange(Resources.LoadAll("Border1/Border_Root_1").Cast<GameObject>().ToArray());

        leftSpawner = new Vector3(leftSpawner.x, leftSpawner.y, leftSpawner.z + SPAWNERS_STARTING_POINT);
        rightSpawner = new Vector3(rightSpawner.x, rightSpawner.y, rightSpawner.z + SPAWNERS_STARTING_POINT);

        player = GameObject.Find("Player");

        GenerateBuilding(50, 5, true);
        GenerateBuilding(50, 5, false);
        GenerateBuilding(50, 5, true);
        GenerateBuilding(50, 5, false);
    }

    void Update()
    {
        leftCalc = new Vector3(leftSpawner.x, leftSpawner.y, leftSpawner.z + (lastBuilding_L * BASELENGHT));
        rightCalc = new Vector3(rightSpawner.x, rightSpawner.y, rightSpawner.z + (lastBuilding_R * BASELENGHT));

        leftDist = Vector3.Distance(player.transform.position, leftCalc);
        rightDist = Vector3.Distance(player.transform.position, rightCalc);

        if (leftDist < 1000)
        {
            GenerateBuilding(20, 5, true);
        }

        if (rightDist < 1000)
        {
            GenerateBuilding(20, 5, false);
        }
    }

    void GenerateBuilding(int max_lenght, int max_heigth, bool side)
    {
        Vector3 pivotPos;

        int numberOfEmptys = 2;

        float slope = Random.Range(-1f, 1f);

        if (side)//if in the right or the left side of level
        {
            pivotPos = new Vector3(leftSpawner.x + slope, leftSpawner.y, leftSpawner.z + (lastBuilding_L * BASELENGHT));
        }
        else
        {
            pivotPos = new Vector3(rightSpawner.x + slope, rightSpawner.y, rightSpawner.z + (lastBuilding_R * BASELENGHT));
        }

        int lastUp = 0;
        bool oneFloorUp = false;
        bool oneFloorDown = false;

        int randLenght = Random.Range(5, max_lenght + 1); // Random Build Lenght
        int randHeigth = 0;

        int lastHeight = 0;

        int MAINDETAILCONSTANT = 25;
        int FLOOR1DETAILCONSTANT = 30;
        int mainDetailChance = MAINDETAILCONSTANT;

        int FLOOR2DETAILCONSTANT = 10;
        int floor2DetailChance = FLOOR2DETAILCONSTANT;

        int FLOOR3DETAILCONSTANT = 10;

        int FLOOR4DETAILCONSTANT = 10;
        int floor4DetailChance = FLOOR4DETAILCONSTANT;

        int randLargeRoof = Random.Range(0, 3); // Random Large 4th Floor

        List<GameObject> buildingList = new List<GameObject>();

        GameObject wall = citizenWall[Random.Range(0, citizenWall.Count)]; // random wall
        byte randColor = (byte)Random.Range(100, 150);

        GameObject pivot = Instantiate(buildingPivot, new Vector3(pivotPos.x, pivotPos.y, pivotPos.z), Quaternion.identity);

        int[] segment = new int[randLenght];

        /// Creating template
        for (int i = 0; i < randLenght; i++)
        {
            if (i == 0)
            {
                randHeigth = Random.Range(2, max_heigth + 1); // Random Build Height
            }
            else
            {
                if (lastHeight == 2) //Check if current lenght will be -1/0/+1 Height. Will never build less than 2 Lenght
                {
                    randHeigth = lastHeight + Random.Range(0, 2);

                    if (randHeigth > 0) //oneFloorUp and oneFloorDows is to prevent "serpentine" buildings
                    {
                        oneFloorUp = true;
                    }
                    else
                    {
                        oneFloorUp = false;
                        oneFloorDown = false;
                    }
                }
                else
                {
                    if (oneFloorUp && oneFloorDown)
                    {
                        switch (lastUp)
                        {
                            case -1:
                                randHeigth = lastHeight + Random.Range(0, 2);
                                oneFloorUp = false;
                                oneFloorDown = true;
                                break;

                            case 1:
                                randHeigth = lastHeight + Random.Range(-1, 1);
                                oneFloorUp = true;
                                oneFloorDown = false;
                                break;
                        }
                    }
                    else
                    {
                        randHeigth = lastHeight + Random.Range(-1, 2);

                        switch (randHeigth - lastHeight)
                        {
                            case -1:
                                lastUp = randHeigth - lastHeight;
                                oneFloorDown = true;
                                break;

                            case 0:
                                oneFloorUp = false;
                                oneFloorDown = false;
                                break;

                            case 1:
                                lastUp = randHeigth - lastHeight;
                                oneFloorUp = true;
                                break;
                        }
                    }
                }
            }

            if (randHeigth > max_heigth)
            {
                randHeigth = max_heigth;
                oneFloorUp = false;
                oneFloorDown = false;
            }

            lastHeight = randHeigth;
            segment *= randHeigth;*

}

//First wall of building, is always a constant wall
GameObject firstWall = Instantiate(wall, new Vector3(pivotPos.x, pivotPos.y + 2.5f, pivotPos.z - 2.5f), Quaternion.Euler(-90, 0, 0));
firstWall.transform.position = new Vector3(firstWall.transform.position.x + (side ? -2.5f : +2.5f), firstWall.transform.position.y * segment[0], firstWall.transform.position.z);
firstWall.transform.localScale = new Vector3(firstWall.transform.localScale.x * 2, firstWall.transform.localScale.y, firstWall.transform.localScale.z * segment[0]);
firstWall.GetComponent().material.mainTextureScale = new Vector2(2, segment[0]);

buildingList.Add(firstWall);

//Second wall of building, is always a constant wall
GameObject secondWall = Instantiate(wall, new Vector3(pivotPos.x + (side ? +2.5f : -2.5f), pivotPos.y + 2.5f, pivotPos.z), Quaternion.Euler(90, (side ? 90 : -90), 0));
secondWall.transform.position = new Vector3(secondWall.transform.position.x, secondWall.transform.position.y * 2, secondWall.transform.position.z + (2.5f * (segment.Length - 1)));
secondWall.transform.localScale = new Vector3(secondWall.transform.localScale.x * segment.Length, secondWall.transform.localScale.y, secondWall.transform.localScale.z * 2);
secondWall.GetComponent().material.mainTextureScale = new Vector2(segment.Length, 2);

buildingList.Add(secondWall);

List lastSegment = new List();
List lastRoofSegment = new List();

for (int i = 0; i < max_heigth; i++)
{
lastSegment.Add(-1);
lastRoofSegment.Add(-1);
}

for (int i = 0; i < segment.Length; i++)
{
for (int j = 0; j < segment*; j++)*
{
if (i > lastSegment[j])//if i is not the last segment tagged
{
for (int k = i; k < segment.Length; k++)
{
if (segment[k] > j) // if segment if bigger than j, then tag
{
lastSegment[j] = k;
}
else
{
break;
}
}

// Check if will be a large floor
if (j == 3 && randLargeRoof == 0)
{
GameObject halfWall = Instantiate(wall, new Vector3(pivotPos.x + (side ? +3.75f : -3.75f), pivotPos.y + 2.5f + (BASEHEIGHT * j), pivotPos.z - 2.5f + (BASELENGHT * i)), Quaternion.Euler(90, 90, -90));
halfWall.transform.localScale = new Vector3(halfWall.transform.localScale.x / 2, halfWall.transform.localScale.y, halfWall.transform.localScale.z);
buildingList.Add(halfWall);

GameObject fragRoof = Instantiate(roof[0], new Vector3(pivotPos.x + (side ? +4f : -4f), pivotPos.y + 5 + (BASEHEIGHT * j), pivotPos.z), Quaternion.identity);
fragRoof.transform.position = new Vector3(fragRoof.transform.position.x, fragRoof.transform.position.y, fragRoof.transform.position.z + (5 * ((lastSegment[j] + i) / 2f)));
fragRoof.transform.localScale = new Vector3(fragRoof.transform.localScale.x / 2, fragRoof.transform.localScale.y, fragRoof.transform.localScale.z + (5 * (lastSegment[j] - i)));
fragRoof.GetComponent().material.mainTextureScale = new Vector2((lastSegment[j] - i), 1);
buildingList.Add(fragRoof);

GameObject fragWall = Instantiate(wall, new Vector3(pivotPos.x + (side ? +5f : -5f), pivotPos.y + 2.5f, pivotPos.z), Quaternion.Euler(90, (side ? 90 : -90), 0));
fragWall.transform.position = new Vector3(fragWall.transform.position.x, fragWall.transform.position.y + (5 * j), fragWall.transform.position.z + (5f * ((lastSegment[j] + i) / 2f)));
fragWall.transform.localScale = new Vector3(fragWall.transform.localScale.x * (lastSegment[j] - i + 1), fragWall.transform.localScale.y, fragWall.transform.localScale.z);
fragWall.GetComponent().material.mainTextureScale = new Vector2((lastSegment[j] - i + 1), 1);
buildingList.Add(fragWall);

for (int k = i; k < lastSegment[j] + 1; k++)
{
buildingList.Add(Instantiate(supportPillar[0], new Vector3(pivotPos.x + (side ? +3.75f : -3.75f), pivotPos.y - 0.05f + (BASEHEIGHT * j), pivotPos.z + (BASELENGHT * k)), Quaternion.Euler(0, (side ? -90 : 90), 0)));
}
}
else if (j > 1)
{
GameObject fragWall = Instantiate(wall, new Vector3(pivotPos.x + (side ? +2.5f : -2.5f), pivotPos.y + 2.5f, pivotPos.z), Quaternion.Euler(90, (side ? 90 : -90), 0));
fragWall.transform.position = new Vector3(fragWall.transform.position.x, fragWall.transform.position.y + (5 * j), fragWall.transform.position.z + (5f * ((lastSegment[j] + i) / 2f)));
fragWall.transform.localScale = new Vector3(fragWall.transform.localScale.x * (lastSegment[j] - i + 1), fragWall.transform.localScale.y, fragWall.transform.localScale.z);
fragWall.GetComponent().material.mainTextureScale = new Vector2((lastSegment[j] - i + 1), 2);
buildingList.Add(fragWall);
}
}

if (j == segment - 1)//same as segments, but for roof
{
if (i > lastRoofSegment[j])
{
for (int k = i; k < segment.Length; k++)
{
if (segment[k] - 1 == j)
{
lastRoofSegment[j] = k;
}
else
{
break;
}
}

GameObject fragRoof = Instantiate(roof[0], new Vector3(pivotPos.x + (side ? -2.5f : 2.5f), pivotPos.y + 5 + (BASEHEIGHT * j), pivotPos.z + (5f * ((lastRoofSegment[j] + i) / 2f))), Quaternion.identity);
fragRoof.transform.localScale = new Vector3(fragRoof.transform.localScale.x * 2, fragRoof.transform.localScale.y, fragRoof.transform.localScale.z * (lastRoofSegment[j] - i + 1));
fragRoof.GetComponent().material.mainTextureScale = new Vector2((lastRoofSegment[j] - i + 1), 1);
buildingList.Add(fragRoof);
}
}

//if the first segment of the building
if (i == 0)
{
if (j == segment - 1)
{
buildingList.Add(Instantiate(borderTop[0], new Vector3(pivotPos.x + (side ? +2.5f : -2.5f), pivotPos.y + 2.5f + (BASEHEIGHT * j), pivotPos.z - 2.5f), Quaternion.Euler(0, (side ? 135 : -135), 0)));
buildingList.Add(Instantiate(borderRoof[0], new Vector3(pivotPos.x + (side ? +2.5f : -2.5f), pivotPos.y + 5.25f + (BASEHEIGHT * j), pivotPos.z - 2.5f), Quaternion.Euler(0, (side ? 135 : -135), 0)));
}
else
{
buildingList.Add(Instantiate(borderBase[0], new Vector3(pivotPos.x + (side ? +2.5f : -2.5f), pivotPos.y + 2.5f + (BASEHEIGHT * j), pivotPos.z + (BASELENGHT * i) - 2.5f), Quaternion.Euler(0, (side ? 135 : -135), 0)));
}
}
//if the last segment of the building
else if (i == (segment.Length - 1))
{
if (j == segment - 1)
{
buildingList.Add(Instantiate(borderTop[0], new Vector3(pivotPos.x + (side ? +2.5f : -2.5f), pivotPos.y + 2.5f + (BASEHEIGHT * j), pivotPos.z + (BASELENGHT * i) + 2.5f), Quaternion.Euler(0, (side ? 45 : -45), 0)));
buildingList.Add(Instantiate(borderRoof[0], new Vector3(pivotPos.x + (side ? +2.5f : -2.5f), pivotPos.y + 5.25f + (BASEHEIGHT * j), pivotPos.z + (BASELENGHT * i) + 2.5f), Quaternion.Euler(0, (side ? 45 : -45), 0)));
}
else
{
buildingList.Add(Instantiate(borderBase[0], new Vector3(pivotPos.x + (side ? +2.5f : -2.5f), pivotPos.y + 2.5f + (BASEHEIGHT * j), pivotPos.z + (BASELENGHT * i) + 2.5f), Quaternion.Euler(0, (side ? 45 : -45), 0)));
}

}
}

#region Details --------------------------
/// Floor 1
int rand = Random.Range(0, mainDetailChance);

if (rand < 2)
{
mainDetailChance = MAINDETAILCONSTANT;
buildingList.Add(Instantiate(floor1Main[rand], new Vector3(pivotPos.x + (side ? +5f : -5f), pivotPos.y, pivotPos.z + (BASELENGHT * i)), Quaternion.Euler(0, (side ? 0 : 180), 0)));
}
else
{
mainDetailChance–;
int randDetails = Random.Range(0, FLOOR1DETAILCONSTANT);

if (randDetails < floor1Details.Count)
{
buildingList.Add(Instantiate(floor1Details[randDetails], new Vector3(pivotPos.x + (side ? +5f : -5f), pivotPos.y, pivotPos.z + (BASELENGHT * i)), Quaternion.Euler(0, (side ? 0 : 180), 0)));
}
}

/// Floor 2
rand = Random.Range(0, floor2DetailChance);

if (rand < floor2Details.Count)
{
floor2DetailChance = FLOOR2DETAILCONSTANT;
buildingList.Add(Instantiate(floor2Details[rand], new Vector3(pivotPos.x + (side ? +5f : -5f), pivotPos.y + BASEHEIGHT, pivotPos.z + (BASELENGHT * i)), Quaternion.Euler(0, (side ? 0 : 180), 0)));
}
else
{
floor2DetailChance–;
}

/// Floor 3
if (segment >= 4)
{
rand = Random.Range(0, FLOOR3DETAILCONSTANT);

if (rand == 0)
{
if (randLargeRoof == 0 && segment >= 3)
{
int randFloor3 = Random.Range(0, floor3Details.Count);
buildingList.Add(Instantiate(floor3Details[randFloor3], new Vector3(pivotPos.x + (side ? +5.05f : -5.05f), pivotPos.y + (BASEHEIGHT * 3), pivotPos.z + (BASELENGHT * i)), Quaternion.Euler(0, (side ? -90 : 90), 0)));
}
else
{
int randFloor3 = Random.Range(0, floor3Details.Count);
buildingList.Add(Instantiate(floor3Details[randFloor3], new Vector3(pivotPos.x + (side ? 2.55f : -2.55f), pivotPos.y + (BASEHEIGHT * 3), pivotPos.z + (BASELENGHT * i)), Quaternion.Euler(0, (side ? -90 : 90), 0)));
}
}
}

/// Floor 4
if (segment >= 5)
{
if (randLargeRoof == 0)
{
rand = Random.Range(0, floor4Details.Count);
buildingList.Add(Instantiate(floor4Details[rand], new Vector3(pivotPos.x + (side ? +7.5f : -7.5f), pivotPos.y + 15, pivotPos.z + (BASELENGHT * i)), Quaternion.Euler(0, (side ? 0 : 180), 0)));
}
else
{
rand = Random.Range(0, floor4DetailChance);

if (rand < floor4Details.Count)
{
floor4DetailChance = FLOOR4DETAILCONSTANT;
buildingList.Add(Instantiate(floor4Details[rand], new Vector3(pivotPos.x + (side ? +5f : -5f), pivotPos.y + 15, pivotPos.z + (BASELENGHT * i)), Quaternion.Euler(0, (side ? 0 : 180), 0)));
}
else
{
floor4DetailChance–;
}
}

}
#endregion
///////---------------------------------

if (segment > (i == 0 ? 0 : segment[i - 1]) && (i != 0 || i == randLenght - 1)) ///Check if there is a missing wall and place wall and border
* {*
GameObject missingWall = Instantiate(wall, new Vector3(pivotPos.x + (side ? -2.5f : +2.5f), pivotPos.y + 2.5f + (BASEHEIGHT * (segment - 1)), pivotPos.z + (BASELENGHT * i) - 2.5f), Quaternion.Euler(-90, 0, 0));
missingWall.transform.localScale = new Vector3(missingWall.transform.localScale.x * 2, missingWall.transform.localScale.y, missingWall.transform.localScale.z);
missingWall.GetComponent().material.mainTextureScale = new Vector2(2, 1);
buildingList.Add(missingWall);

buildingList.Add(Instantiate(borderRoof[0], new Vector3(pivotPos.x + (side ? +2.5f : -2.5f), pivotPos.y + 0.25f + (BASEHEIGHT * segment_), pivotPos.z + (BASELENGHT * i) - 2.5f), Quaternion.Euler(0, (side ? 135 : -135), 0)));
buildingList.Add(Instantiate(borderTop[0], new Vector3(pivotPos.x + (side ? +2.5f : -2.5f), pivotPos.y - 2.5f + (BASEHEIGHT * segment), pivotPos.z + (BASELENGHT * i) - 2.5f), Quaternion.Euler(0, (side ? 135 : -135), 0)));
buildingList.Add(Instantiate(borderRoot[0], new Vector3(pivotPos.x + (side ? +2.5f : -2.5f), pivotPos.y - 5.5f + (BASEHEIGHT * segment), pivotPos.z + (BASELENGHT * i) - 2.5f), Quaternion.Euler(0, (side ? 135 : -135), 0)));
}
else if (segment < (i == 0 ? 0 : segment[i - 1]) && (i != 0 || i == randLenght - 1))
{
buildingList.Add(Instantiate(borderRoof[0], new Vector3(pivotPos.x + (side ? +2.5f : -2.5f), pivotPos.y + 5.25f + (BASEHEIGHT * segment), pivotPos.z + (BASELENGHT * i) - 2.5f), Quaternion.Euler(0, (side ? 45 : -45), 0)));
buildingList.Add(Instantiate(borderTop[0], new Vector3(pivotPos.x + (side ? +2.5f : -2.5f), pivotPos.y + 2.5f + (BASEHEIGHT * segment), pivotPos.z + (BASELENGHT * i) - 2.5f), Quaternion.Euler(0, (side ? 45 : -45), 0)));
buildingList.Add(Instantiate(borderRoot[0], new Vector3(pivotPos.x + (side ? +2.5f : -2.5f), pivotPos.y - 0.5f + (BASEHEIGHT * segment), pivotPos.z + (BASELENGHT * i) - 2.5f), Quaternion.Euler(0, (side ? 45 : -45), 0)));_

}
}

/// Number of emptys segments
for (int i = 1; i < numberOfEmptys + 1; i++)
{
buildingList.Add(Instantiate(emptyPrefab, new Vector3(pivotPos.x, pivotPos.y + 2.5f, pivotPos.z + (BASELENGHT * (randLenght + i)) - 5f), Quaternion.identity));
}

if (side)
{
lastBuilding_L += randLenght + numberOfEmptys;
}
else
{
lastBuilding_R += randLenght + numberOfEmptys;
}

GameObject ground = Instantiate(groundCivilian, new Vector3((side ? -10 : 10), pivotPos.y, pivotPos.z), Quaternion.identity);
ground.transform.position = new Vector3(ground.transform.position.x, ground.transform.position.y * 2, ground.transform.position.z + (2.5f * (segment.Length + 1)));
ground.transform.localScale = new Vector3(ground.transform.localScale.x * 4, secondWall.transform.localScale.y, ground.transform.localScale.z * (segment.Length + 2));
ground.GetComponent().material.mainTextureScale = new Vector2(4, segment.Length + 2);

buildingList.Add(ground);

/// Paint all building tagged objects
foreach (GameObject x in buildingList)
{
x.transform.parent = pivot.transform; // Adding all the building inside of a empty

if (x.tag == “Building”)
{
x.GetComponent().material.color = new Color32(randColor, randColor, randColor, 255);
}

if (x.transform.childCount > 0)
{
Transform[] childs = x.gameObject.GetComponentsInChildren();
for (int i = 0; i < childs.Length; i++)
{
if (childs*.tag == “Building”)*
{
childs*.GetComponent().material.color = new Color32(randColor, randColor, randColor, 255);*
}
}
}
}

buildingList.Clear();
}
}
----------
(I tried without the foreach, but didn’t notice a difference)
----------
I don’t know where I can improve more in the script, this is a problem since this is only the level without gameplay in action, and all the building is extremely low-poly.
----------
My guess is the script is taking too long in the 3 “for” calculating the length of the wall. I think I made an improvement in the GPU side and a downgrade on the CPU side.
----------
If someone knows what I can do to improve the script, I’ll grateful.
Thanks.

One thing that I made that improves (a lot) the performance is changing to URP. I was impressed by how much my game was running more smoothly, but, there’s no Ambient Occlusion yet, so… I’ll keep developing without URP until they release the AO feature…sad.