How could I get rid of lag spike when generating new chunks in my infinite world?

I’m working on infinite generation for my 2D game. I got everything working-- terrain generation, biome generation, chunk loading and offloading, and infinite generation.

However, even though I only set chunks a certain distance away from my player active, there is still a noticeable lag spike when new chunks are loaded in.

What could I do to prevent this? Here is my code, and all infinite generation is delt with from lines 66-78:

using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Tilemaps;

public class WorldGeneration : MonoBehaviour
{
    public static WorldGeneration Instance;

    public bool debugMode;
    public bool enableChunkLoading;
    public int seed;

    public int chunkSize = 16;
    public int worldSize = 3;
    
    Chunk chunk;
    Dictionary<Vector2Int, Chunk> chunks = new Dictionary<Vector2Int, Chunk>();  //Key == Coordinate && Value == Chunk
    public static readonly List<Vector2Int> chunkDirections = new List<Vector2Int>() { Vector2Int.zero, Vector2Int.up, Vector2Int.down, Vector2Int.right, Vector2Int.left, new Vector2Int(1, 1), new Vector2Int(-1, 1), new Vector2Int(1, -1), new Vector2Int(-1, -1) };

    List<BiomePoint> biomePoints = new List<BiomePoint>();

    public Vector3Int playerPosInChunk;
    public Grid chunkGrid;
    public Grid grid;

    public AnimatedTile waterTile;
    public List<Biome> biomes;
    List<Biome> biomes_to_generate = new List<Biome>();

    [SerializeField] GameObject player;
    [SerializeField] int renderDistance = 25;

    public static readonly List<Vector2Int> psudoChunkDirections = new List<Vector2Int>() { Vector2Int.up, Vector2Int.down, Vector2Int.right, Vector2Int.left, new Vector2Int(1, 1), new Vector2Int(-1, 1), new Vector2Int(1, -1), new Vector2Int(-1, -1)};

    public GameObject biomePointIndcator;
    public GameObject psudoBiomePointIndcator;

    private void Awake()
    {
        Instance = this;
    }

    private void Start()
    {

        chunkGrid.cellSize = new Vector3(chunkSize, chunkSize);


        //We add each biome to a list based on their rarity setting. 1 == most rare, 10 == least rare. We add each biome to the list as amny times as its rarity setting.
        //Then wen we randomly choose a biome, we will have a greater chance of picking the least rare biomes as they appear most in the list
        foreach (Biome biome in biomes) 
        {
            for (int i = 0; i < biome.rarity; i++)
            {
                biomes_to_generate.Add(biome);
            }
        }

    }

    private void Update()
    {

        playerPosInChunk = chunkGrid.WorldToCell(player.transform.position);
        //print(playerPosInChunk);


        foreach(Vector2Int pos in chunkDirections)
        {
            Vector2Int chunkPos = new Vector2Int(playerPosInChunk.x, playerPosInChunk.y) + pos;
            if (!chunks.ContainsKey(chunkPos))
            {
                CreateChunk(chunkPos);
            }
        }




        if (enableChunkLoading)
        {
            foreach(KeyValuePair<Vector2Int, Chunk> m_chunk in chunks)
            {
                if (m_chunk.Value != null)
                {

                    if (Vector2.Distance(player.transform.position, m_chunk.Value.chunk_obj.transform.position) < (float)renderDistance)
                    {
                        m_chunk.Value.isActive = true;
                    }
                    else
                    {
                        m_chunk.Value.isActive = false;
                    }

                    m_chunk.Value.chunk_obj.SetActive(m_chunk.Value.isActive);
                }
            }
        }

    }


    void CreateChunk(Vector2Int chunkCoord)
    {
        Vector2 chunkCoord_worldPos = chunkGrid.CellToWorld(new Vector3Int(chunkCoord.x, chunkCoord.y));
        Vector2Int startCoord = new Vector2Int((int)chunkCoord_worldPos.x - (chunkSize / 2), (int)chunkCoord_worldPos.y - (chunkSize / 2));

        CreateChunkRepresentation(startCoord, out GameObject chunkContainer, out GameObject trees, out GameObject rocks, out Tilemap m_tilemap, out Tilemap m_details, out Tilemap m_water);

        Chunk chunk = new Chunk();


        chunks.Add(chunkCoord, chunk);



        //Create BiomePoint for THIS CHUNK
        if (chunk.biomePoint == null)
        {

            BiomePoint biomePoint = new BiomePoint();
            biomePoint.location = new Vector2Int(startCoord.x + chunkSize + Random.Range(-chunkSize/2, chunkSize/2 + 1), startCoord.y + chunkSize + Random.Range(-chunkSize / 2, chunkSize / 2 + 1));

            if (biomePoints.Count > 0) //If our biome point is close enough to another biome point, it will equal that biomePoint
            {
                float biomePoinDistance = 50;
                foreach (BiomePoint bPoint in biomePoints)
                {
                    float dist = Vector2.Distance(bPoint.location, biomePoint.location);
                    if (dist < biomePoinDistance)
                    {
                        biomePoinDistance = dist;
                        biomePoint.biome = bPoint.biome;
                    }
                }
            }


            biomePoint.biome = biomes_to_generate[Random.Range(0, biomes_to_generate.Count)];
            chunk.biomePoint = biomePoint;
            //print(biomePoint.location + " " + biomePoint.biome.biomeName);

            biomePoints.Add(biomePoint);
            if (debugMode)
            {
                Instantiate(biomePointIndcator, (Vector3Int)biomePoint.location, Quaternion.identity);
            }

        }


        foreach(Vector2Int pos in psudoChunkDirections)
        {
            BiomePoint psudoBiomePoint = new BiomePoint();
            psudoBiomePoint.location = new Vector2Int(startCoord.x + chunkSize + (pos.x * chunkSize) + Random.Range(-chunkSize / 2, chunkSize / 2 + 1),  startCoord.y + chunkSize + (pos.y * chunkSize) + Random.Range(-chunkSize / 2, chunkSize / 2 + 1));
            psudoBiomePoint.biome = biomes_to_generate[Random.Range(0, biomes_to_generate.Count)];
            biomePoints.Add(psudoBiomePoint);
            
            if(debugMode)
            {
                Instantiate(psudoBiomePointIndcator, (Vector3Int)psudoBiomePoint.location, Quaternion.identity);
            }


        }


        for (int x = 0; x < chunkSize; x++)
        {
            for (int y = 0; y < chunkSize; y++)
            {

                Vector2Int actualCoordinates = new Vector2Int(startCoord.x + x, startCoord.y + y);

                Coordinates coordinate = new Coordinates();

                int selectedBiome_index = new int();
                float noise;
                NoiseGeneration.GenerateNoise(actualCoordinates, out noise);




                //EQUATION FOR CELULAR NOISE
                BiomePoint closestBiomePoint = new BiomePoint();
                float biomeDistance = Mathf.Infinity;
                foreach (BiomePoint b_point in biomePoints) //This finds the closest biome point to our corrdinate! The coordinate is then set to the biome point's biome!
                {
                    float dist = Vector2.Distance(b_point.location, actualCoordinates);
                    if(dist < biomeDistance)
                    {
                        closestBiomePoint = b_point;
                        biomeDistance = dist;
                    }

                }


                selectedBiome_index = biomes_to_generate.IndexOf(closestBiomePoint.biome);
                Biome selectedBiome = biomes_to_generate[selectedBiome_index];
                BiomeTile tile = new BiomeTile();
                RuleTile detailTile = new RuleTile();

                foreach (BiomeTile b_tile in selectedBiome.biomeTiles)
                {
                    if (noise > b_tile.minNoiseValue && noise <= b_tile.maxNoiseValue)
                    {

                        tile = b_tile;

                        //SET TREES
                        if (!coordinate.isOccupied)
                        {
                            if (b_tile.canGrowPlants && selectedBiome.trees.Count > 0)
                            {
                                float treeNoise = NoiseGeneration.GenerateTrees(actualCoordinates);
                                if (treeNoise > 0f && treeNoise <= 1 && selectedBiome.trees.Count > 0)
                                {
                                    GameObject tree = selectedBiome.trees[Random.Range(0, selectedBiome.trees.Count)];
                                    GameObject instantiatedTree = Instantiate(tree, trees.transform);
                                    instantiatedTree.transform.localPosition = grid.GetCellCenterWorld((Vector3Int) new Vector2Int(x, y));


                                    coordinate.isOccupied = true;
                                }
                            }

                        }
                        
                        //SET ROCKS
                        if (!coordinate.isOccupied) //If our coordinate doesn't have a tree, place rock
                        {
                            float rockNoise = NoiseGeneration.GenerateRocks(actualCoordinates);
                            if (rockNoise > 0f && rockNoise <= 1 && selectedBiome.rocks.Count > 0)
                            {
                                int rockProbability = Random.Range(0, 10);
                                if (rockProbability % 2 == 0)
                                {
                                    GameObject rock = selectedBiome.rocks[Random.Range(0, selectedBiome.rocks.Count)];
                                    GameObject instantiatedRock = Instantiate(rock, rocks.transform);
                                    instantiatedRock.transform.localPosition = grid.GetCellCenterWorld((Vector3Int)new Vector2Int(x, y));


                                    coordinate.isOccupied = true;
                                }

                            }
                        }

                        //SET PLANTS
                        if (!coordinate.isOccupied) //If our coordinate doesn't have a tree or a rock, grow plants / details
                        {
                            if (b_tile.canGrowPlants)
                            {
                                int plantProbability = Random.Range(0, 10);
                                if (plantProbability % 2 == 0)
                                {
                                    if (selectedBiome.plants.Count > 0)
                                    {
                                        detailTile = selectedBiome.plants[Random.Range(0, selectedBiome.plants.Count)];
                                        m_details.SetTile((Vector3Int)new Vector2Int(x, y), detailTile);
                                    }
                                }

                            }
                        }

                    }

                } 


                m_tilemap.SetTile((Vector3Int)new Vector2Int(x, y), tile.tile);

                if (noise <= selectedBiome.biomeTiles[0].minNoiseValue)
                {
                    m_water.SetTile((Vector3Int)new Vector2Int(x, y), waterTile);
                }

            }
        }

        chunk.chunk_obj = chunkContainer;

    }

    void CreateChunkRepresentation(Vector2Int startCoord, out GameObject chunkContainer, out GameObject trees, out GameObject rocks, out Tilemap m_tilemap, out Tilemap m_details, out Tilemap m_water)
    {
        //Create a GameObject that contains everything in a chunk
        chunkContainer = new GameObject("Chunk");
        chunkContainer.transform.localPosition = new Vector2(startCoord.x + chunkSize, startCoord.y + chunkSize);
        chunkContainer.transform.SetParent(grid.transform);

        //Create a GameObject with a Tilmap component to hold the tiles that represent land
        GameObject tilemap = new GameObject("Tilemap");
        tilemap.transform.SetParent(chunkContainer.transform);
        tilemap.transform.position = new Vector2((startCoord.x + (startCoord.x + chunkSize)) / 2, (startCoord.y + (startCoord.y + chunkSize)) / 2);

        m_tilemap = tilemap.AddComponent<Tilemap>();
        tilemap.AddComponent<TilemapRenderer>();

        //Create a GameObject to hold trees (GameObject)
        trees = new GameObject("Trees");
        trees.transform.SetParent(chunkContainer.transform);
        trees.transform.position = new Vector2((startCoord.x + (startCoord.x + chunkSize)) / 2, (startCoord.y + (startCoord.y + chunkSize)) / 2);

        //Create a GameObject to hold rocks (GameObject)
        rocks = new GameObject("Rocks");
        rocks.transform.SetParent(chunkContainer.transform);
        rocks.transform.position = new Vector2((startCoord.x + (startCoord.x + chunkSize)) / 2, (startCoord.y + (startCoord.y + chunkSize)) / 2);

        //Create a GameOject with a Tilemap component to hold the detail tiles
        GameObject details = new GameObject("Details");
        details.transform.SetParent(chunkContainer.transform);
        details.transform.position = new Vector2((startCoord.x + (startCoord.x + chunkSize)) / 2, (startCoord.y + (startCoord.y + chunkSize)) / 2);
 
        m_details = details.AddComponent<Tilemap>();
        details.AddComponent<TilemapRenderer>();
        details.GetComponent<TilemapRenderer>().sortingOrder = 1;

        //Create a GameObject with a Tilemap component to hold water tiles
        GameObject water = new GameObject("Water");
        water.transform.SetParent(chunkContainer.transform);
        water.transform.position = new Vector2((startCoord.x + (startCoord.x + chunkSize)) / 2, (startCoord.y + (startCoord.y + chunkSize)) / 2);

        m_water = water.AddComponent<Tilemap>();
        water.AddComponent<TilemapRenderer>(); 
        m_water.AddComponent<TilemapCollider2D>();
    }
}

public class Chunk 
{
    public bool isActive = true;
    public GameObject chunk_obj;
    public List<Coordinates> coordinates; //List of our coordinates in a chunk
    public BiomePoint biomePoint;
}

public class Coordinates
{
    public bool isOccupied = false;

}

[System.Serializable]
public class Biome
{
    public string biomeName;
    public int rarity;
    public List<BiomeTile> biomeTiles;
    public List<RuleTile> plants;
    public List<GameObject> trees;
    public List<GameObject> rocks;
}

[System.Serializable]
public class BiomeTile
{
    public RuleTile tile;
    public float minNoiseValue;
    public float maxNoiseValue;
    public bool canGrowPlants;
    
}

[System.Serializable]
public class BiomePoint
{
    public Biome biome;
    public Vector2Int location;
}

Have you done any profiling yet to actually see where the overhead is?

1 Like

You can add custom profiling points like this:

UnityEngine.Profiling.Profiler.BeginSample("YourDescription");
SomeMethodOrLogicOfInterest();
UnityEngine.Profiling.Profiler.EndSample();

These show up without turning on Deep Profiling mode, which is great.

There are also ProfileMarkers which are nice for markers that you plan to keep around forever.


Some notes:

  • you’re instantiating a lot - perhaps you can use a coroutine to split these instantiations over more frames
  • possibly these instantiated objects could be pooled, but that introduces more complexity
  • you’re adding components - Unity deserailizes (eg from a prefab) faster than it adds a component, and sometimes this is dramatic. A tilemap prefab may help.
  • RuleTile can be more costly than plainer tiles, if you can get away with it
  • you’re using SetTile. Instead, try SetTilesBlock or SetTiles because these can be dramatically faster

But you shouldn’t make any changes in response to those notes until you’ve profiled since the source of the problem could just be one or two parts.

4 Likes

So your saying I would put in my CreateChunk() method as the SomeMethodOrLogicOfInterest()?

Do that PLUS a bunch of other places all over your code because it’s quick to add and will give you much better info on how to prioritize optimizations.

Okay, looking at the profiling, everything that seems to take the longest to run is rendering, camera, and tilemap stuff. Scripting isn’t an issue I think.

Just wanted to point out that newer versions of Unity have an async method for insantiated objects now. So no need to use coroutines for this kind of thing anymore.

https://docs.unity3d.com/ScriptReference/Object.InstantiateAsync.html

3 Likes

I’m using 2022.3 LTS, but visual studio wont let me type it. Do I need to import anydependencies?

Which particular 2022.3? Are you on the latest version of the LTS?

I’m using 2022.3.11.

I did find a work around using:

    IEnumerator InstantaiteAsync(GameObject obj, Transform parent, Vector2Int pos)
    {

        yield return new WaitForEndOfFrame();
        GameObject instantiatedObject = Instantiate(obj, parent);
        instantiatedObject.transform.localPosition = grid.GetCellCenterWorld((Vector3Int) pos);

    }

But the lag spike still persists

That’s about 30 2022.3 versions behind. The method is likely in the more recent 2023.3 LTS versions. You should update to the latest version.

How many times are you spinning up this coroutine though? This would likely just be worse than instantiating normally.

Did you specifically profile when the lag spike occurs?

All this coroutine does is instantiate at the end of the frame. Why would that reduce the burden on your laggy frame? You might be missing the concept here. If you have 1000ms of work to do, you need to spread that out over enough frames so that it’s not noticeable.

But per @Sluggy1’s sugegstion, you should try InstantiateAsync instead of coroutines here.

Yes, the lag spike occurs when Unity has to render loads of things. Nothing relating to scripting was sticking out in the profiler.

I thought coroutines executed in the background? Or do coroutines just allow for the code to execute while the coroutines run, but the the run during the same frame?

Okay, I did all these things, and the lag spike is decreased! It’s still there, but a lot less noticeable.

using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting.Antlr3.Runtime.Tree;
using UnityEngine;
using UnityEngine.Tilemaps;


public class WorldGeneration : MonoBehaviour
{
    public static WorldGeneration Instance;

    [SerializeField] GameObject chunkPrefab;

    public bool debugMode;
    public bool enableChunkLoading;

    [SerializeField] GameObject player;
    [SerializeField] int renderDistance = 25;

    public int seed;

    public int chunkSize = 16;
    public int worldSize = 3;
    
    Chunk chunk;
    Dictionary<Vector2Int, Chunk> chunks = new Dictionary<Vector2Int, Chunk>();  //Key == Coordinate && Value == Chunk
    public static readonly List<Vector2Int> chunkDirections = new List<Vector2Int>() { Vector2Int.zero, Vector2Int.up, Vector2Int.down, Vector2Int.right, Vector2Int.left, new Vector2Int(1, 1), new Vector2Int(-1, 1), new Vector2Int(1, -1), new Vector2Int(-1, -1) };

    List<BiomePoint> biomePoints = new List<BiomePoint>();

    public Vector3Int playerPosInChunk;
    public Grid chunkGrid;
    public Grid grid;

    public AnimatedTile waterTile;
    public List<Biome> biomes;
    List<Biome> biomes_to_generate = new List<Biome>();


    public static readonly List<Vector2Int> psudoChunkDirections = new List<Vector2Int>() { Vector2Int.up, Vector2Int.down, Vector2Int.right, Vector2Int.left, new Vector2Int(1, 1), new Vector2Int(-1, 1), new Vector2Int(1, -1), new Vector2Int(-1, -1)};

    public GameObject biomePointIndcator;
    public GameObject psudoBiomePointIndcator;

    private void Awake()
    {
        Instance = this;
    }

    private void Start()
    {

        chunkGrid.cellSize = new Vector3(chunkSize, chunkSize);


        //We add each biome to a list based on their rarity setting. 1 == most rare, 10 == least rare. We add each biome to the list as amny times as its rarity setting.
        //Then wen we randomly choose a biome, we will have a greater chance of picking the least rare biomes as they appear most in the list
        foreach (Biome biome in biomes) 
        {
            for (int i = 0; i < biome.rarity; i++)
            {
                biomes_to_generate.Add(biome);
            }
        }

    }

    private void Update()
    {

        playerPosInChunk = chunkGrid.WorldToCell(player.transform.position);
        //print(playerPosInChunk);


        foreach(Vector2Int pos in chunkDirections)
        {
            Vector2Int chunkPos = new Vector2Int(playerPosInChunk.x, playerPosInChunk.y) + pos;
            if (!chunks.ContainsKey(chunkPos))
            {
                //UnityEngine.Profiling.Profiler.BeginSample("CreateChunk");
                CreateChunk(chunkPos);
                //UnityEngine.Profiling.Profiler.EndSample();            
            }
        }




        if (enableChunkLoading)
        {
            ChunkLoading();
        }
        

    }


    void CreateChunk(Vector2Int chunkCoord)
    {
        Vector2 chunkCoord_worldPos = chunkGrid.CellToWorld(new Vector3Int(chunkCoord.x, chunkCoord.y));
        Vector2Int startCoord = new Vector2Int((int)chunkCoord_worldPos.x - (chunkSize / 2), (int)chunkCoord_worldPos.y - (chunkSize / 2));
        Chunk chunk = new Chunk();
        chunks.Add(chunkCoord, chunk);

        CreateChunkRepresentation(startCoord, out GameObject chunkContainer, out GameObject trees, out GameObject rocks, out Tilemap m_tilemap, out Tilemap m_details, out Tilemap m_water);



        //Create BiomePoint for THIS CHUNK
        CreateBiomePoint(chunk, startCoord);


        Dictionary<Vector2Int, GameObject> trees_toInstantiateAsync = new Dictionary<Vector2Int, GameObject>();
        Dictionary<Vector2Int, GameObject> rocks_toInstantiateAsync = new Dictionary<Vector2Int, GameObject>();

        foreach (Vector2Int pos in psudoChunkDirections)
        {
            BiomePoint psudoBiomePoint = new BiomePoint();
            psudoBiomePoint.location = new Vector2Int(startCoord.x + chunkSize + (pos.x * chunkSize) + Random.Range(-chunkSize / 2, chunkSize / 2 + 1),  startCoord.y + chunkSize + (pos.y * chunkSize) + Random.Range(-chunkSize / 2, chunkSize / 2 + 1));
            psudoBiomePoint.biome = biomes_to_generate[Random.Range(0, biomes_to_generate.Count)];
            biomePoints.Add(psudoBiomePoint);
            
            if(debugMode)
            {
                Instantiate(psudoBiomePointIndcator, (Vector3Int)psudoBiomePoint.location, Quaternion.identity);
            }


        }

        //Create some TileBase[] so that we can use the setTiles() method
        Vector3Int[] tilePositions = new Vector3Int[chunkSize * chunkSize];
        TileBase[] tileArray = new TileBase[tilePositions.Length];
        TileBase[] waterTileArray = new TileBase[tilePositions.Length];
        TileBase[] detailTileArray = new TileBase[tilePositions.Length];

        int tileIndex = 0;  
        for (int x = 0; x < chunkSize; x++)
        {
            for (int y = 0; y < chunkSize; y++)
            {

                Vector2Int actualCoordinates = new Vector2Int(startCoord.x + x, startCoord.y + y);

                Coordinates coordinate = new Coordinates();

                int selectedBiome_index = new int();
                float noise;
                NoiseGeneration.GenerateNoise(actualCoordinates, out noise);




                //EQUATION FOR CELULAR NOISE
                BiomePoint closestBiomePoint = new BiomePoint();
                float biomeDistance = Mathf.Infinity;
                foreach (BiomePoint b_point in biomePoints) //This finds the closest biome point to our corrdinate! The coordinate is then set to the biome point's biome!
                {
                    float dist = Vector2.Distance(b_point.location, actualCoordinates);
                    if(dist < biomeDistance)
                    {
                        closestBiomePoint = b_point;
                        biomeDistance = dist;
                    }

                }

                selectedBiome_index = biomes_to_generate.IndexOf(closestBiomePoint.biome);
                Biome selectedBiome = biomes_to_generate[selectedBiome_index];

                tilePositions[tileIndex] = new Vector3Int(x, y);
                foreach (BiomeTile b_tile in selectedBiome.biomeTiles)
                {
                    if (noise >= b_tile.minNoiseValue && noise <= b_tile.maxNoiseValue)
                    {
                        Tile m_tile = new Tile();
                        m_tile.sprite = b_tile.tile;

                        tileArray[tileIndex] = m_tile;

                        //SET TREES
                        if (!coordinate.isOccupied)
                        {
                            if (b_tile.canGrowPlants && selectedBiome.trees.Count > 0)
                            {
                                float treeNoise = NoiseGeneration.GenerateTrees(actualCoordinates);
                                if (treeNoise > 0f && treeNoise <= 1 && selectedBiome.trees.Count > 0)
                                {
                                    GameObject tree = selectedBiome.trees[Random.Range(0, selectedBiome.trees.Count)];
                                    trees_toInstantiateAsync.Add(new Vector2Int(x, y), tree);

                                    coordinate.isOccupied = true;
                                }
                            }

                        }
                        
                        //SET ROCKS
                        if (!coordinate.isOccupied) //If our coordinate doesn't have a tree, place rock
                        {
                            float rockNoise = NoiseGeneration.GenerateRocks(actualCoordinates);
                            if (rockNoise > 0f && rockNoise <= 1 && selectedBiome.rocks.Count > 0)
                            {
                                int rockProbability = Random.Range(0, 10);
                                if (rockProbability % 2 == 0)
                                {
                                    GameObject rock = selectedBiome.rocks[Random.Range(0, selectedBiome.rocks.Count)];
                                    rocks_toInstantiateAsync.Add(new Vector2Int(x, y), rock);

                                    coordinate.isOccupied = true;
                                }

                            }
                        }

                        //SET Details
                        if (!coordinate.isOccupied) //If our coordinate doesn't have a tree or a rock, grow plants / details
                        {
                            if (b_tile.canGrowPlants)
                            {
                                int plantProbability = Random.Range(0, 10);
                                if (plantProbability % 2 == 0)
                                {
                                    if (selectedBiome.plants.Count > 0)
                                    {
                                        Sprite detailTile = selectedBiome.plants[Random.Range(0, selectedBiome.plants.Count)];
                                        
                                        Tile tile = new Tile();
                                        tile.sprite = detailTile;
                                        detailTileArray[tileIndex] = tile;
                                    }
                                }
                            }
                        }

                    }
                }

                if (noise <= selectedBiome.biomeTiles[0].minNoiseValue)
                {
                    waterTileArray[tileIndex] = waterTile;
                }


                tileIndex++;
            }
        }

        m_tilemap.SetTiles(tilePositions, tileArray);
        m_water.SetTiles(tilePositions, waterTileArray);
        m_details.SetTiles(tilePositions, detailTileArray);

        StartCoroutine(InstantaiteAsync(trees_toInstantiateAsync, trees.transform));
        StartCoroutine(InstantaiteAsync(rocks_toInstantiateAsync, rocks.transform));

        chunk.chunk_obj = chunkContainer;

    }
    

    void CreateChunkRepresentation(Vector2Int startCoord, out GameObject chunkContainer, out GameObject trees, out GameObject rocks, out Tilemap m_tilemap, out Tilemap m_details, out Tilemap m_water)
    {
        Vector2 pos = new Vector2((startCoord.x + (startCoord.x + chunkSize)) / 2, (startCoord.y + (startCoord.y + chunkSize)) / 2);

        chunkContainer = Instantiate(chunkPrefab, pos, Quaternion.identity, grid.transform);
        chunkContainer.name = "Chunk " + pos;
        
        GameObject tilemap = chunkContainer.transform.GetChild(0).gameObject;
        m_tilemap = tilemap.GetComponent<Tilemap>();  
        
        GameObject details = chunkContainer.transform.GetChild(1).gameObject;
        m_details = details.GetComponent<Tilemap>();

        GameObject water = chunkContainer.transform.GetChild(2).gameObject;
        m_water = water.GetComponent<Tilemap>();

        trees = chunkContainer.transform.GetChild(3).gameObject;
        rocks = chunkContainer.transform.GetChild(4).gameObject;

    }

    void CreateBiomePoint(Chunk chunk, Vector2Int startCoord)
    {
        if (chunk.biomePoint == null)
        {

            BiomePoint biomePoint = new BiomePoint();
            biomePoint.location = new Vector2Int(startCoord.x + chunkSize + Random.Range(-chunkSize / 2, chunkSize / 2 + 1), startCoord.y + chunkSize + Random.Range(-chunkSize / 2, chunkSize / 2 + 1));

            if (biomePoints.Count > 0) //If our biome point is close enough to another biome point, it will equal that biomePoint
            {
                float biomePoinDistance = 50;
                foreach (BiomePoint bPoint in biomePoints)
                {
                    float dist = Vector2.Distance(bPoint.location, biomePoint.location);
                    if (dist < biomePoinDistance)
                    {
                        biomePoinDistance = dist;
                        biomePoint.biome = bPoint.biome;
                    }
                }
            }


            biomePoint.biome = biomes_to_generate[Random.Range(0, biomes_to_generate.Count)];
            chunk.biomePoint = biomePoint;
            //print(biomePoint.location + " " + biomePoint.biome.biomeName);

            biomePoints.Add(biomePoint);
            if (debugMode)
            {
                Instantiate(biomePointIndcator, (Vector3Int)biomePoint.location, Quaternion.identity);
            }

        }
    }

    IEnumerator InstantaiteAsync(Dictionary<Vector2Int, GameObject> list, Transform parent)
    {
 
        foreach(KeyValuePair<Vector2Int, GameObject> m_dict in list)
        {
            GameObject instantiatedObject = Instantiate(m_dict.Value, parent);
            instantiatedObject.transform.localPosition = grid.GetCellCenterWorld((Vector3Int)m_dict.Key);
            yield return new WaitForSeconds(0.05f);
        }
    }

    void ChunkLoading()
    {
        foreach (KeyValuePair<Vector2Int, Chunk> m_chunk in chunks)
        {
            if (m_chunk.Value != null)
            {

                if (Vector2.Distance(player.transform.position, m_chunk.Value.chunk_obj.transform.position) < (float)renderDistance)
                {
                    m_chunk.Value.isActive = true;
                }
                else
                {
                    m_chunk.Value.isActive = false;
                }

                m_chunk.Value.chunk_obj.SetActive(m_chunk.Value.isActive);
            }
        }
    }

}

public class Chunk 
{
    public bool isActive = true;
    public GameObject chunk_obj;
    public List<Coordinates> coordinates; //List of our coordinates in a chunk
    public BiomePoint biomePoint;
}

public class Coordinates
{
    public bool isOccupied = false;

}

[System.Serializable]
public class Biome
{
    public string biomeName;
    public int rarity;
    public List<BiomeTile> biomeTiles;
    public List<Sprite> plants;
    public List<GameObject> trees;
    public List<GameObject> rocks;
}

[System.Serializable]
public class BiomeTile
{
    //public RuleTile tile;
    public Sprite tile;
    public float minNoiseValue;
    public float maxNoiseValue;
    public bool canGrowPlants;
    
}

[System.Serializable]
public class BiomePoint
{
    public Biome biome;
    public Vector2Int location;
}

Okay! I solved the issue, I didn’t upgrade my unity, but I did make a bunk of my own custom Instantiate_Async coroutines that fixed my issue!

  #region INSTANTIATE_ASYNC_METHODS
  IEnumerator InstantaiteAsync(Dictionary<Vector2Int, GameObject> list, Transform parent)
  {
 
      foreach(KeyValuePair<Vector2Int, GameObject> m_dict in list)
      {
          GameObject instantiatedObject = Instantiate(m_dict.Value, parent);
          instantiatedObject.transform.localPosition = grid.GetCellCenterWorld((Vector3Int)m_dict.Key);
          yield return new WaitForSeconds(0.05f);
      }
  }

  IEnumerator InstantaiteAsync(Dictionary<Vector2Int, Tile> list, Tilemap tilemap)
  {

      foreach (KeyValuePair<Vector2Int, Tile> m_dict in list)
      {
          tilemap.SetTile((Vector3Int)m_dict.Key, m_dict.Value);
          yield return new WaitForSeconds(0.01f);
      }
  }

  IEnumerator InstantaiteAsync(Dictionary<Vector2Int, AnimatedTile> list, Tilemap tilemap)
  {

      foreach (KeyValuePair<Vector2Int, AnimatedTile> m_dict in list)
      {
          tilemap.SetTile((Vector3Int)m_dict.Key, m_dict.Value);
          yield return new WaitForSeconds(0.01f);
      }
  }
  #endregion

They are called like so:

        StartCoroutine(InstantaiteAsync(tilemap, m_tilemap));
        StartCoroutine(InstantaiteAsync(detail_tilemap, m_details));
        StartCoroutine(InstantaiteAsync(water_tilemap, m_water));


        StartCoroutine(InstantaiteAsync(trees_toInstantiateAsync, trees.transform));
        StartCoroutine(InstantaiteAsync(rocks_toInstantiateAsync, rocks.transform));

I feel like it would’ve been substantially easier to just update your project version, and get all the bug fixes that come with it as well. Early 2022.3 was notoriously buggy.

2 Likes

I didn’t see any other 2022 LTS editor version in my hub.

Try going to Archives and download from official website.
Go To download Archive and install accordingly.

1 Like

You don’t know how to download new versions from the hub?