- Home /
Optimizing the generation of a procedural world - 50ms lag spike to
I've finished creating the infinite terrain generator "engin" for my game recently (The object creator and the cliff are still not implemented yet but I'll finish that later).
After optimizing it (By reducing the GC time, reducing the number of mesh to create collider on, adding safe threading & coroutine support) I still get around
50ms to create 191 new chunk (40% of that 50ms is used to create 5 new collider mesh) + 43 KB GC alloc
and about
185ms to create 1009 new chunk (generate 1009 chunk == all the chunk in a world) (22% for collider stuff) + 197.7KB GC alloc
I would like to mention that almost 100% of the time it takes to generate a chunk (and 100% of the GC alloc) come for the function named "GenerateWorldObject" (One of the only function that isn't thread safe, it has to be executed in the coroutine instead of the thread) and here's what takes to most time to load in this function
The script:
IEnumerator LoadNewChunk () {
bool ActionRunning = false;
if(AlreadyLoading) {
ChunkToLoad = NewChunkPos;
yield break;
}
AlreadyLoading = true;
Vector2 PlayerPos = ChunkToLoad;
PrepareActions.Clear();
ThreadingActions.Clear();
NewChunk = new Chunk[0];
Action Preparing = () => {
NewChunkRequirement = new List<ChunkParameters>();
//NewCRequirement0 = new List<ChunkParameters>();
//NewCRequirement1 = new List<ChunkParameters>();
//NewCRequirement2 = new List<ChunkParameters>();
//NewCRequirement3 = new List<ChunkParameters>();
for(int y = -Mathf.CeilToInt(LOD4EndChunkDistance); y <= Mathf.Ceil(LOD4EndChunkDistance); y++) {
for(int x = -Mathf.CeilToInt(LOD4EndChunkDistance); x <= Mathf.Ceil(LOD4EndChunkDistance); x++) {
float Distance = Vector2.Distance(Vector2.zero,new Vector2(x,y));
if(Distance <= LOD0EndChunkDistance) {
//NewCRequirement0.Add(new ChunkParameters(0,new Vector2(NewChunkPos.x+x,NewChunkPos.y+y),(Distance <= EndOfChunkMesh)));
NewChunkRequirement.Add(new ChunkParameters(0,new Vector2(NewChunkPos.x+x,NewChunkPos.y+y),(Distance <= EndOfChunkMesh)));
} else if(Distance <= LOD1EndChunkDistance) {
//NewCRequirement1.Add(new ChunkParameters(1,new Vector2(NewChunkPos.x+x,NewChunkPos.y+y),false));
NewChunkRequirement.Add(new ChunkParameters(1,new Vector2(NewChunkPos.x+x,NewChunkPos.y+y),false));
} else if(Distance <= LOD2EndChunkDistance) {
//NewCRequirement2.Add(new ChunkParameters(2,new Vector2(NewChunkPos.x+x,NewChunkPos.y+y),false));
NewChunkRequirement.Add(new ChunkParameters(2,new Vector2(NewChunkPos.x+x,NewChunkPos.y+y),false));
} else if(Distance <= LOD3EndChunkDistance) {
//NewCRequirement3.Add(new ChunkParameters(3,new Vector2(NewChunkPos.x+x,NewChunkPos.y+y),false));
NewChunkRequirement.Add(new ChunkParameters(3,new Vector2(NewChunkPos.x+x,NewChunkPos.y+y),false));
} else if(Distance <= LOD4EndChunkDistance) {
//NewCRequirement3.Add(new ChunkParameters(4,new Vector2(NewChunkPos.x+x,NewChunkPos.y+y),false));
NewChunkRequirement.Add(new ChunkParameters(4,new Vector2(NewChunkPos.x+x,NewChunkPos.y+y),false));
}
}
}
//NewChunkRequirement.AddRange(NewCRequirement3);
//NewChunkRequirement.AddRange(NewCRequirement2);
//NewChunkRequirement.AddRange(NewCRequirement1);
//NewChunkRequirement.AddRange(NewCRequirement0);
DoNotRecreate.Clear();
NewChunk = new Chunk[NewChunkRequirement.Count];
};
PreparingThread = new Thread(new ThreadStart(Preparing));
PreparingThread.Start();
yield return new WaitUntil(() => !PreparingThread.IsAlive);
Action Cleaning = () => {
for(int i = 0; i < ChunkList.Length; i++) {
ChunkParameters cparams = new ChunkParameters(ChunkList[i].GetLODLevel(),ChunkList[i].GetChunkPosition(),ChunkList[i].IsColliderActive());
bool IsFound = false;
for(int s = 0; s < NewChunkRequirement.Count; s++) {
if(NewChunkRequirement[s].ChunkPos.x == cparams.ChunkPos.x && NewChunkRequirement[s].ChunkPos.y == cparams.ChunkPos.y && NewChunkRequirement[s].ColliderMesh == cparams.ColliderMesh && NewChunkRequirement[s].LevelOfDetails == cparams.LevelOfDetails) {
IsFound = true;
NewChunk[s] = ChunkList[i];
DoNotRecreate.Add(s);
break;
}
}
if(!IsFound) {
Chunk save = ChunkList[i];
Action DestroyChunkAction = () => {
ActionRunning = true;
save.DestroyChunk();
ActionRunning = false;
};
PrepareActions.Add(DestroyChunkAction);
}
}
};
CleaningThread = new Thread(new ThreadStart(Cleaning));
CleaningThread.Start();
yield return new WaitUntil(() => !CleaningThread.IsAlive);
Action Generating = () => {
for(int i = 0; i < NewChunkRequirement.Count; i++) {
bool DoNotRecreateGate = false;
for(int s = 0; s < DoNotRecreate.Count; s++) {
if(DoNotRecreate[s] == i) {
DoNotRecreateGate = true;
}
}
if(!DoNotRecreateGate) {
NewChunk[i] = new Chunk(NewChunkRequirement[i].ChunkPos,Mathf.RoundToInt(Mathf.Pow(2,NewChunkRequirement[i].LevelOfDetails)),SimulatedChunkSize,worldTextures,parameters,MeshTemplate,NewChunkRequirement[i].ColliderMesh);
Chunk save = NewChunk[i];
Action PrepareChunk = () => {
save.PrepareGeneration();
};
ThreadingActions.Add(PrepareChunk);
Action GenerateChunk = () => {
save.GenerateWorldObject();
};
PrepareActions.Add(GenerateChunk); //Instead of adding it at the end, you should put it after the creating the chunk that was loading there
}
}
};
GeneratingThread = new Thread(new ThreadStart(Generating));
GeneratingThread.Start();
yield return new WaitUntil(() => !GeneratingThread.IsAlive);
Action ExecuteThreadingActions = () => {
while(ThreadingActions.Count > 0) {
Action action = ThreadingActions[ThreadingActions.Count-1];
ThreadingActions.RemoveAt(ThreadingActions.Count-1);
action();
}
ThreadingActions.Clear();
};
ActionThread = new Thread(new ThreadStart(ExecuteThreadingActions));
ActionThread.Start();
yield return new WaitUntil(() => !ActionThread.IsAlive);
//int Counter = 0;
while(PrepareActions.Count > 0) {
Action action = PrepareActions[PrepareActions.Count-1];
PrepareActions.RemoveAt(PrepareActions.Count-1);
action();
/*if(Counter >= 40) {
Counter = 0;
yield return new WaitForEndOfFrame();
} else {
Counter++;
}*/
}
PrepareActions.Clear();
ChunkList = NewChunk;
PreLoad = false;
AlreadyLoading = false;
yield return null;
}
Some other functions:
public class Chunk {
Vector2 TestStartPos = new Vector2(0,0);
public GameObject ChunkObject;
public List<GameObject> OldObject = new List<GameObject>();
WorldTexture[] worldTextures;
WorldParameters parameters;
int BlockSize = 1;
int SimulatedChunkSize = 16;
GameObject MeshTemplate;
bool GenerateCollider = false;
public Vector2 GetChunkPosition () {
return TestStartPos;
}
public int GetLODLevel () {
return Mathf.RoundToInt(Mathf.Log(BlockSize,2));
}
public bool IsColliderActive () {
return GenerateCollider;
}
public Chunk(Vector2 Position, int blockSize, int simulatedChunkSize, WorldTexture[] textures, WorldParameters worldParameters, GameObject mesh, bool CreateCollider) {
worldTextures = textures;
parameters = worldParameters;
BlockSize = blockSize;
SimulatedChunkSize = simulatedChunkSize;
GenerateCollider = CreateCollider;
TestStartPos = Position;
MeshTemplate = mesh;
}
public void SetNewValues (Vector2 Position, int blockSize, int simulatedChunkSize, WorldTexture[] textures, WorldParameters worldParameters, GameObject mesh) {
worldTextures = textures;
parameters = worldParameters;
BlockSize = blockSize;
SimulatedChunkSize = simulatedChunkSize;
TestStartPos = Position;
MeshTemplate = mesh;
}
public void ClearChunk () {
OldObject.Add(ChunkObject);
ChunkObject = null;
}
public void DestroyChunk () {
UnityEngine.Object.Destroy(ChunkObject);
foreach(GameObject chunk in OldObject) {
UnityEngine.Object.Destroy(chunk);
}
OldObject = new List<GameObject>();
}
Vector2 VCode1 = new Vector2(0,0);
Vector2 VCode2 = new Vector2(1,0);
Vector2 VCode3 = new Vector2(0,1);
Vector2 VCode4 = new Vector2(1,1);
public bool IsPreparing = false;
//Declaring for the preparation
Vector3[] vertices = new Vector3[0];
int[] triangles = new int[0];
Vector2[] UVs = new Vector2[0];
List<Vector3> physXverts = new List<Vector3>();
List<int> physXtris = new List<int>();
public void PrepareGeneration () {
IsPreparing = true;
int[,] xWallHeight = new int[SimulatedChunkSize/BlockSize,SimulatedChunkSize/BlockSize];
int[,] yWallHeight = new int[SimulatedChunkSize/BlockSize,SimulatedChunkSize/BlockSize];
float nY = 0f;
float nX = 0f;
float nZ = 0f;
float xCoord = 0f;
float yCoord = 0f;
float xPos = 0f;
float yPos = 0f;
int c = 0;
int v = 0;
int c2 = 0;
int neg = 0;
int p = 0;
int p2 = 0;
p = 0;
for(int y = 0; y < SimulatedChunkSize/BlockSize; y++) {
for(int x = 0; x < SimulatedChunkSize/BlockSize; x++) {
p = ((x+y*SimulatedChunkSize/BlockSize)*4);
nY = GetValueAtPixel(x*BlockSize+(TestStartPos.x*(SimulatedChunkSize)),y*BlockSize+(TestStartPos.y*(SimulatedChunkSize)));
physXverts.Add(new Vector3(x,nY,y));
physXverts.Add(new Vector3(x+1,nY,y));
physXverts.Add(new Vector3(x,nY,y+1));
physXverts.Add(new Vector3(x+1,nY,y+1));
physXtris.Add(p+1);
physXtris.Add(p+0);
physXtris.Add(p+2);
physXtris.Add(p+1);
physXtris.Add(p+2);
physXtris.Add(p+3);
}
}
p = 0;
p2 = physXverts.Count;
for(int y = 0; y < SimulatedChunkSize/BlockSize; y++) {
for(int x = 0; x < SimulatedChunkSize/BlockSize; x++) {
p = ((x+y*SimulatedChunkSize/BlockSize)*4);
nX = GetValueAtPixel(x*BlockSize+(TestStartPos.x*SimulatedChunkSize)+BlockSize,y*BlockSize+(TestStartPos.y*SimulatedChunkSize));
nY = GetValueAtPixel(x*BlockSize+(TestStartPos.x*SimulatedChunkSize),y*BlockSize+(TestStartPos.y*SimulatedChunkSize));
nZ = GetValueAtPixel(x*BlockSize+(TestStartPos.x*SimulatedChunkSize),y*BlockSize+(TestStartPos.y*SimulatedChunkSize)+BlockSize);
physXverts.Add(new Vector3(x+1,nX,y));
physXverts.Add(new Vector3(x+1,nX,y+1));
physXverts.Add(new Vector3(x+1,nZ,y+1));
physXverts.Add(new Vector3(x,nZ,y+1));
physXtris.Add(p+3);
physXtris.Add(p+p2);
physXtris.Add(p+1);
physXtris.Add(p+p2);
physXtris.Add(p+p2+1);
physXtris.Add(p+3);
physXtris.Add(p+2);
physXtris.Add(p+p2+3);
physXtris.Add(p+3);
physXtris.Add(p+p2+3);
physXtris.Add(p+p2+2);
physXtris.Add(p+3);
}
}
int Dispacement = 0;
for(int y = 0; y < SimulatedChunkSize/BlockSize; y++) {
for(int x = 0; x < SimulatedChunkSize/BlockSize; x++) {
nX = GetValueAtPixel(x*BlockSize+(TestStartPos.x*SimulatedChunkSize)+BlockSize,y*BlockSize+(TestStartPos.y*SimulatedChunkSize));
nY = GetValueAtPixel(x*BlockSize+(TestStartPos.x*SimulatedChunkSize),y*BlockSize+(TestStartPos.y*SimulatedChunkSize));
nZ = GetValueAtPixel(x*BlockSize+(TestStartPos.x*SimulatedChunkSize),y*BlockSize+(TestStartPos.y*SimulatedChunkSize)+BlockSize);
neg = 1;
if(nX-nY < 0) {
neg = -1;
}
if(neg == 1) {
Dispacement += Mathf.Abs(Mathf.FloorToInt(nY-nX));
} else {
Dispacement += Mathf.Abs(Mathf.CeilToInt(nY-nX));
}
neg = 1;
if(nZ-nY < 0) {
neg = -1;
}
if(neg == 1) {
Dispacement += Mathf.Abs(Mathf.FloorToInt(nY-nZ));
} else {
Dispacement += Mathf.Abs(Mathf.CeilToInt(nY-nZ));
}
}
}
int[] Triangles = new int[(Dispacement + (SimulatedChunkSize/BlockSize * SimulatedChunkSize/BlockSize))*6];
int tri = 0;
Vector3[] Vertices = new Vector3[(Dispacement + (SimulatedChunkSize/BlockSize * SimulatedChunkSize/BlockSize))*4];
int ver = 0;
Vector2[] VerticesUVs = new Vector2[(Dispacement + (SimulatedChunkSize/BlockSize * SimulatedChunkSize/BlockSize))*4];
int uv = 0;
UVs = new Vector2[(Dispacement + (SimulatedChunkSize/BlockSize * SimulatedChunkSize/BlockSize))*6];
for(int y = 0; y < SimulatedChunkSize/BlockSize; y++) {
for(int x = 0; x < SimulatedChunkSize/BlockSize; x++) {
c = ((x+y*SimulatedChunkSize/BlockSize)*4);
nY = GetValueAtPixel(x*BlockSize+(TestStartPos.x*(SimulatedChunkSize)),y*BlockSize+(TestStartPos.y*(SimulatedChunkSize)));
xCoord = x*BlockSize+(TestStartPos.x*(SimulatedChunkSize));
yCoord = y*BlockSize+(TestStartPos.y*(SimulatedChunkSize));
Vertices[ver++] = (new Vector3(x,nY,y));
VerticesUVs[uv++] = ComplexTextureInfoToUV(GetTextureTypeAtPixel(xCoord,yCoord),0,VCode1);
Vertices[ver++] = (new Vector3(x+1,nY,y));
VerticesUVs[uv++] = ComplexTextureInfoToUV(GetTextureTypeAtPixel(xCoord,yCoord),0,VCode2);
Vertices[ver++] = (new Vector3(x,nY,y+1));
VerticesUVs[uv++] = ComplexTextureInfoToUV(GetTextureTypeAtPixel(xCoord,yCoord),0,VCode3);
Vertices[ver++] = (new Vector3(x+1,nY,y+1));
VerticesUVs[uv++] = ComplexTextureInfoToUV(GetTextureTypeAtPixel(xCoord,yCoord),0,VCode4);
Triangles[tri] = (c+1);
tri++;
Triangles[tri] = (c+0);
tri++;
Triangles[tri] = (c+2);
tri++;
Triangles[tri] = (c+1);
tri++;
Triangles[tri] = (c+2);
tri++;
Triangles[tri] = (c+3);
tri++;
}
}
v = ver;
c2 = v;
for(int y = 0; y < SimulatedChunkSize/BlockSize; y++) {
for(int x = 0; x < SimulatedChunkSize/BlockSize; x++) {
c = 0;
nX = GetValueAtPixel(x*BlockSize+(TestStartPos.x*SimulatedChunkSize)+BlockSize,y*BlockSize+(TestStartPos.y*SimulatedChunkSize));
nY = GetValueAtPixel(x*BlockSize+(TestStartPos.x*SimulatedChunkSize),y*BlockSize+(TestStartPos.y*SimulatedChunkSize));
xPos = x*BlockSize+(TestStartPos.x*SimulatedChunkSize);
yPos = y*BlockSize+(TestStartPos.y*SimulatedChunkSize);
neg = 1;
if(nX-nY < 0) {
neg = -1;
}
if(neg == 1) {
xWallHeight[x,y] = Mathf.FloorToInt(nY-nX);
} else {
xWallHeight[x,y] = Mathf.CeilToInt(nY-nX);
}
for(int i = 0; i < Mathf.Abs(xWallHeight[x,y]); i++) {
int TextureT = 0;
if(nX-nY < 0) {
TextureT = GetTextureTypeAtPixel(xPos,yPos);
} else {
TextureT = GetTextureTypeAtPixel(xPos+1,yPos);
}
int tp;
if(i == 0) {
tp = 1;
} else if(i == 1) {
tp = 2;
} else {
tp = 3;
}
c = ver;
if(neg == 1) {
Vertices[ver++] = (new Vector3(x+1,nX-i,y));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode1);
Vertices[ver++] = (new Vector3(x+1,nX-i,y+1));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode2);
Vertices[ver++] = (new Vector3(x+1,nX-i-1,y));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode3);
Vertices[ver++] = (new Vector3(x+1,nX-i-1,y+1));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode4);
Triangles[tri] = (c+3);
tri++;
Triangles[tri] = (c+1);
tri++;
Triangles[tri] = (c+0);
tri++;
Triangles[tri] = (c+2);
tri++;
Triangles[tri] = (c+3);
tri++;
Triangles[tri] = (c+0);
tri++;
} else {
Vertices[ver++] = (new Vector3(x+1,nY-i,y));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode1);
Vertices[ver++] = (new Vector3(x+1,nY-i,y+1));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode2);
Vertices[ver++] = (new Vector3(x+1,nY-i-1,y));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode3);
Vertices[ver++] = (new Vector3(x+1,nY-i-1,y+1));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode4);
Triangles[tri] = (c+0);
tri++;
Triangles[tri] = (c+1);
tri++;
Triangles[tri] = (c+3);
tri++;
Triangles[tri] = (c+0);
tri++;
Triangles[tri] = (c+3);
tri++;
Triangles[tri] = (c+2);
tri++;
}
}
c2+=yWallHeight[x,y]*4;
}
}
v = ver;
c2 = v;
for(int y = 0; y < SimulatedChunkSize/BlockSize; y++) {
for(int x = 0; x < SimulatedChunkSize/BlockSize; x++) {
c = 0;
nY = GetValueAtPixel(x*BlockSize+(TestStartPos.x*SimulatedChunkSize),y*BlockSize+(TestStartPos.y*SimulatedChunkSize));
nZ = GetValueAtPixel(x*BlockSize+(TestStartPos.x*SimulatedChunkSize),y*BlockSize+(TestStartPos.y*SimulatedChunkSize)+BlockSize);
xPos = x*BlockSize+(TestStartPos.x*SimulatedChunkSize);
yPos = y*BlockSize+(TestStartPos.y*SimulatedChunkSize);
neg = 1;
if(nZ-nY < 0) {
neg = -1;
}
if(neg == 1) {
yWallHeight[x,y] = Mathf.FloorToInt(nY-nZ);
} else {
yWallHeight[x,y] = Mathf.CeilToInt(nY-nZ);
}
for(int i = 0; i < Mathf.Abs(yWallHeight[x,y]); i++) {
int TextureT = 0;
if(nZ-nY < 0) {
TextureT = GetTextureTypeAtPixel(xPos,yPos);
} else {
TextureT = GetTextureTypeAtPixel(xPos,yPos+1);
}
int tp;
if(i == 0) {
tp = 1;
} else if(i == 1) {
tp = 2;
} else {
tp = 3;
}
c = ver;
if(neg == 1) {
Vertices[ver++] = (new Vector3(x,nZ-i,y+1));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode1);
Vertices[ver++] = (new Vector3(x+1,nZ-i,y+1));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode2);
Vertices[ver++] = (new Vector3(x,nZ-i-1,y+1));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode3);
Vertices[ver++] = (new Vector3(x+1,nZ-i-1,y+1));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode4);
Triangles[tri] = (c+0);
tri++;
Triangles[tri] = (c+1);
tri++;
Triangles[tri] = (c+3);
tri++;
Triangles[tri] = (c+0);
tri++;
Triangles[tri] = (c+3);
tri++;
Triangles[tri] = (c+2);
tri++;
} else {
Vertices[ver++] = (new Vector3(x,nY-i,y+1));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode1);
Vertices[ver++] = (new Vector3(x+1,nY-i,y+1));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode2);
Vertices[ver++] = (new Vector3(x,nY-i-1,y+1));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode3);
Vertices[ver++] = (new Vector3(x+1,nY-i-1,y+1));
VerticesUVs[uv++] = ComplexTextureInfoToUV(TextureT,tp,VCode4);
Triangles[tri] = (c+3);
tri++;
Triangles[tri] = (c+1);
tri++;
Triangles[tri] = (c+0);
tri++;
Triangles[tri] = (c+2);
tri++;
Triangles[tri] = (c+3);
tri++;
Triangles[tri] = (c+0);
tri++;
}
}
c2+=yWallHeight[x,y]*4;
}
}
Vector3[] oldVerts = Vertices;
triangles = Triangles;
vertices = new Vector3[triangles.Length];
for(int i = 0; i < triangles.Length; i++) {
vertices[i] = oldVerts[triangles[i]];
UVs[i] = VerticesUVs[triangles[i]];
triangles[i] = i;
}
IsPreparing = false;
}
public void GenerateWorldObject () {
GameObject cChunk = (GameObject)GameObject.Instantiate(MeshTemplate,new Vector3(TestStartPos.x*SimulatedChunkSize,0,TestStartPos.y*SimulatedChunkSize),Quaternion.identity);
cChunk.transform.localScale = new Vector3(BlockSize,BlockSize,BlockSize);
Mesh cMesh = new Mesh();
cMesh.vertices = vertices;
cMesh.triangles = triangles;
cMesh.uv = UVs;
Mesh pMesh = new Mesh();
pMesh.SetVertices(physXverts);
pMesh.SetTriangles(physXtris,0);
cMesh.RecalculateNormals();
pMesh.RecalculateBounds();
cChunk.GetComponent<MeshFilter>().mesh = cMesh;
if(GenerateCollider) {
cChunk.GetComponent<MeshCollider>().sharedMesh = pMesh;
}
ChunkObject = cChunk;
}
//Texture Placement Rules:
// 0 : TOP
// 1 : UpperWall
// 2 : LowerWall
// 3 : UnderWall
Vector2 Results = Vector2.zero;
Vector2 ComplexTextureInfoToUV (int TextureType, int texturePlacement, Vector2 verticesUV) {
switch(texturePlacement) {
case 0:
if(verticesUV.x == 0) {
Results.x = worldTextures[TextureType].TopCoords.x;
} else {
Results.x = worldTextures[TextureType].TopCoords.z;
}
if(verticesUV.y == 0) {
Results.y = worldTextures[TextureType].TopCoords.y;
} else {
Results.y = worldTextures[TextureType].TopCoords.w;
}
break;
case 1:
if(verticesUV.x == 0) {
Results.x = worldTextures[TextureType].UpperWallCoords.x;
} else {
Results.x = worldTextures[TextureType].UpperWallCoords.z;
}
if(verticesUV.y == 0) {
Results.y = worldTextures[TextureType].UpperWallCoords.y;
} else {
Results.y = worldTextures[TextureType].UpperWallCoords.w;
}
break;
case 2:
if(verticesUV.x == 0) {
Results.x = worldTextures[TextureType].LowerWallCoords.x;
} else {
Results.x = worldTextures[TextureType].LowerWallCoords.z;
}
if(verticesUV.y == 0) {
Results.y = worldTextures[TextureType].LowerWallCoords.y;
} else {
Results.y = worldTextures[TextureType].LowerWallCoords.w;
}
break;
case 3:
if(verticesUV.x == 0) {
Results.x = worldTextures[TextureType].UnderWallCoords.x;
} else {
Results.x = worldTextures[TextureType].UnderWallCoords.z;
}
if(verticesUV.y == 0) {
Results.y = worldTextures[TextureType].UnderWallCoords.y;
} else {
Results.y = worldTextures[TextureType].UnderWallCoords.w;
}
break;
}
return V2ToUV(Results);
}
int GetTextureTypeAtPixel (float x, float y) {
return 0;
}
float GetValueAtPixel (float x, float y) {
return Mathf.PerlinNoise(x*parameters.Frequency,y*parameters.Frequency)*parameters.Amplitude/(BlockSize);
}
Vector2 vcalcule = Vector2.zero;
Vector2 PixelToUV (int x, int y) {
vcalcule.x = 0;
vcalcule.y = 0;
vcalcule.x = (float)x*0.001953125f; //0.001953125 = 1/512, 512 = size of the texture
vcalcule.y = 1-((float)y*0.001953125f); //0.001953125 = 1/512, 512 = size of the texture
return vcalcule;
}
Vector2 V2ToUV (Vector2 v) {
vcalcule.x = 0;
vcalcule.y = 0;
vcalcule.x = (float)v.x*0.001953125f; //0.001953125 = 1/512, 512 = size of the texture
vcalcule.y = 1-((float)v.y*0.001953125f); //0.001953125 = 1/512, 512 = size of the texture
return vcalcule;
}
bool IsInBound (int v, int bSize, int index) {
return (v-(Mathf.Floor((float)v/bSize)*4))==index;
}
}
PS: Sorry of the headache after reading the script
$$anonymous$$ake the chunks bigger = less chunks generated = less gc. Also don't use colliders, do collision detect yourself. It helps performance a load aswel.
Also, the general aspect of infinite terrain is that you generate your surrounding environment + maybe some extra off camera, and when the player moves outside of the generated environment that you start the generate a bit more, and cache the part you left and turn it inactive. Generating it all at once, I wouldn't recommend it.
I wouldn't recommend to increase the chunk size. A size of 16 is already quite large. A worst case chunk section of 16x16x16x24 (==98k) is already too large for a single mesh. Of course that's only possible if you allow transparent blocks. Without transparent blocks the absolute worst case for a 16x16x16 section is 16x16x16x24 / 2 == ~50k vertices. This is basically a 3d checkerboard pattern. So every second cube is fully visible. If you plan to allow more complex block models it's getting even worse. In Unity a smaller section size would be recommended.
$$anonymous$$inecraft splits the world horizontally into regions of 32x32 chunks. A chunk is 16x16 and splitted further into 16 chunk sections vertically (build-height 256 (16x16)). However $$anonymous$$inecraft uses 32 bit index buffers while Unity currently only supports 16 bit and therefore a single mesh can't have more than 64k vertices (65536).
Answer by Bunny83 · Aug 02, 2017 at 02:42 PM
Actually creating 1000 gameobjects in 185ms is pretty fast. What you have to avoid is destroying and recreating the chunks all the time. You need to use an object pool for the chunk objects.
Like @ShadyProductions said in the comment you shouldn't really use colliders. I've seen some implementations which uses a pool of a few box colliders and only generate / place them around objects which could collider with them. So there would be only colliders around the player. However this requires a lot of preprocessing and bookkeeping so it's usually easier to roll your own collsion detection solution.
"Actually creating 1000 gameobjects in 185ms is pretty fast."
I'm starting to wonder if my 2013 11' $$anonymous$$acBook Air is good or my optimization skillz are good. $$anonymous$$aybe both?
"You need to use an object pool for the chunk objects."
It looks complicated on the wiki but Sebastian Lague made a video on that subject.
"Like @ShadyProductions said in the comment you shouldn't really use colliders. I've seen some implementations which use a pool of a few box colliders and only generate/place them around objects which could collide with them."
Good Idea but the problem is if I have other non-static entity, they need their own ground collider too and it'll be hard to code.
Anyway, thanks for your answers! It helped me a lot.