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;
}