I wrote a infinite terrain script which works! Saddly everytime the player moves a chunk it lags for a moment. I know my code isn't great but I'm here to learn why and get this working too :D
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GEN_InfiniteTerrain : MonoBehaviour
{
public GameObject targetObject;
public GameObject chunkObject;
public int chunkSize;
public float unitSize;
public int renderDistance;
Dictionary<Vector2, GameObject> gridOfChunks = new Dictionary<Vector2, GameObject>();
List<Vector2> expectedChunkGridPositions = new List<Vector2>();
public float noiseScale;
// Infinite terrain values
float absoluteChunkSize;
private void Start()
{
// Calculate absolute chunk size
GetAbsoluteChunkSize();
// Generate base world
GenerateBase();
}
Vector2 lastTargetGridPosition = Vector2.zero;
private void LateUpdate()
{
// Get the targets position in world space
Vector3 targetAbsolutePosition = targetObject.transform.position;
// Convert the targets world position to grid position (/ 10 * 10 is just rounding to 10)
Vector2 targetGridPosition = new Vector2();
targetGridPosition.x = Mathf.RoundToInt(targetAbsolutePosition.x / 10) * 10 / absoluteChunkSize;
targetGridPosition.y = Mathf.RoundToInt(targetAbsolutePosition.z / 10) * 10 / absoluteChunkSize;
if (targetGridPosition - lastTargetGridPosition != Vector2.zero)
{
GenerateExpectedChunkAreas(targetGridPosition);
UpdateChunkPositions(targetGridPosition);
}
lastTargetGridPosition = targetGridPosition;
}
void GenerateBase()
{
for (int x = -renderDistance / 2; x < renderDistance / 2; x++)
{
for (int z = -renderDistance / 2; z < renderDistance / 2; z++)
{
Vector2 gridPosition = new Vector2(x, z);
Vector3 worldPosition = new Vector3(x * (unitSize * chunkSize), 0, z * (unitSize * chunkSize));
GameObject chunk = Instantiate(chunkObject, worldPosition, Quaternion.identity);
chunk.GetComponent<GEN_Chunk>().gridPosition = gridPosition;
gridOfChunks.Add(gridPosition, chunk);
}
}
GenerateExpectedChunkAreas(Vector2.zero);
}
void GenerateExpectedChunkAreas(Vector2 targetGridPosition)
{
expectedChunkGridPositions.Clear();
for (int x = -renderDistance / 2; x < renderDistance / 2; x++)
{
for (int z = -renderDistance / 2; z < renderDistance / 2; z++)
{
Vector2 gridPosition = new Vector2(x, z) + targetGridPosition;
expectedChunkGridPositions.Add(gridPosition);
}
}
}
void UpdateChunkPositions(Vector2 targetGridPosition)
{
List<Vector2> positionsWithoutChunks = new List<Vector2>();
List<Vector2> positionsWithOldChunks = new List<Vector2>();
for (int chunkCount = 0, x = -renderDistance / 2; x < renderDistance / 2; x++)
{
for (int z = -renderDistance / 2; z < renderDistance / 2; z++)
{
Vector2 gridPosition = new Vector2(x, z) + targetGridPosition;
if(!gridOfChunks.ContainsKey(gridPosition))
{
positionsWithoutChunks.Add(gridPosition);
}
chunkCount++;
}
}
foreach (GameObject chunk in gridOfChunks.Values)
{
if(!expectedChunkGridPositions.Contains(chunk.GetComponent<GEN_Chunk>().gridPosition))
{
positionsWithOldChunks.Add(chunk.GetComponent<GEN_Chunk>().gridPosition);
}
}
for (int i = 0; i < positionsWithOldChunks.Count; i++)
{
Vector3 worldPosition = new Vector3(positionsWithoutChunks[i].x * absoluteChunkSize, 0, positionsWithoutChunks[i].y * absoluteChunkSize);
gridOfChunks[positionsWithOldChunks[i]].transform.position = worldPosition;
// Recalculating noise for chunk based on its new position does lag more but even WITHOUT this it still stutters when player moves around. ( plan to learn threading just to calculate noise on seperate threads )
// gridOfChunks[positionsWithOldChunks[i]].GetComponent<GEN_Chunk>().ApplyNoise();
}
}
void GetAbsoluteChunkSize()
{
absoluteChunkSize = unitSize * chunkSize;
}
}
2 Answers 2
Since I don’t see any function of type IEnumerable
, I guess you have not taken advantage of Unity’s StartCoroutine
function.
It starts a new thread that runs parallel to the main thread. The main thread is the one making sure the game renders without lag. So from your description it sound exactly like the solution to the kind of problem you are having.
I would suggest first looking up the examples for StartCoroutine
; it takes a little time to figure out how it works, but after that it’s really easy to implement.
I can only give you some general tips; I'm afraid Unity is outside my expertise.
I would consider redefining what
renderDistance
means. Currently it seems like a diameter; the rendered terrain consists of renderDistance2 chunks. What that means in practice is that every time you reference renderDistance, you divide it by 2. What happens if it is odd? It may be easier to treat this value as a radius instead (and just use values that are about twice as large).You have a list of
expectedGridChunkPositions
, and it's not clear to me why it exists. It may just be that I'm not understanding the work done in the latter third ofUpdateChunkPositions
. What is troubling is that you have a lookup on this list (expectedChunkGridPositions.Contains
). I doubt that your performance problems are coming from this alone... but if it's not possible to remove this List, you might consider changed it to a HashSet.The latter two thirds of
UpdateChunkPositions
could be combined, in fact. You spend one foreach-loop building uppositionsWithOldChunks
, just to iterate through it and do work in another for-loop. Why not do the work right away?It's possible that, as you examine the behavior of your game more closely, you will find opportunities for broad optimizations, and you will be able to keep the game running smoothly on a single thread. I recommend1 that you treat multithreading as a last resort, due to the inherent complexity increase it will cause in your code. You may find Visual Studio's performance analysis tooling very helpful here, especially its capability to show which functions are consuming the most resources.
It's also possible that, as the player crosses a chunk boundary, there's just too much terrain generation work to handle in the next dozen milliseconds, and lag will be unavoidable. In that case, good luck with your multithreading adventure. One thing you'll want to avoid as you explore that space is the use of Systems.Collections.Generic for anything that might be shared across threads. Instead, you'll need to use specialized collections like
ConcurrentDictionary
andConcurrentBag
, which have very handy functions with built-in locking mechanisms (such asGetOrAdd
, andAddOrUpdate
).
1On the other hand, I've never programmed a game before. Maybe multithreading is considered a best practice for Unity3d games? If so, then my recommendation isn't worth much.