Chunks take crazy long to load.

Hey guys. I’m trying to generate some chunks for a voxel game I’m working on in my spare time. Currently I’m having an issue when I’m building my chunks. It takes forever. I’m not sure why but I think it has something to do with how I get the blocks when I’m building the terrain.But I’m not entirely sure. Any Ideas? Just so you guys know the BLockType is an enum I have set up. Below is the relavent code :

Mesh Manager :

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

public class MeshManager : MonoBehaviour
{
    public static ChunkManager chunkManager;

    public static MeshObject CreateVisualMesh(int chunkSize, BlockType[,,] blocks, MeshObject meshOb, Vector3 chunkPos, Chunk chunk)
    {
        MeshManager.chunkManager = World.currentWorld.chunkManager;

        Mesh visualMesh = meshOb.Mesh;
        MeshFilter meshFilter = meshOb.MeshFilter;
        MeshCollider meshCollider = meshOb.MeshCollider;

        List<Vector3> verts = new List<Vector3>();
        List<Vector2> uvs = new List<Vector2>();
        List<int> tris = new List<int>();

        int[,,] map = Chunk.GetIntFromBlockType(blocks, chunkSize);

        Vector3 worldPos = new Vector3(chunkPos.x * chunkSize, chunkPos.y * chunkSize, chunkPos.z * chunkSize);

        for (int x = 0; x < chunkSize; x++)
        {
            for (int y = 0; y < chunkSize; y++)
            {
                for (int z = 0; z < chunkSize; z++)
                {
                    if (map[x, y, z] == 0) continue;

                    int block = map[x, y, z];


                    // Left wall
                    if (IsTransparent(x - 1, y, z, worldPos, chunk))
                    {
                        BuildFace(block, new Vector3(x, y, z), Vector3.up, Vector3.forward, false, verts, uvs, tris);
                    }
                    // Right wall
                    if (IsTransparent(x + 1, y, z, worldPos, chunk))
                        BuildFace(block, new Vector3(x + 1, y, z), Vector3.up, Vector3.forward, true, verts, uvs, tris);

                    // Bottom wall
                    if (IsTransparent(x, y - 1, z, worldPos, chunk))
                        BuildFace(block, new Vector3(x, y, z), Vector3.forward, Vector3.right, false, verts, uvs, tris);
                    // Top wall
                    if (IsTransparent(x, y + 1, z, worldPos, chunk))
                        BuildFace(block, new Vector3(x, y + 1, z), Vector3.forward, Vector3.right, true, verts, uvs, tris);

                    // Back
                    if (IsTransparent(x, y, z - 1, worldPos, chunk))
                        BuildFace(block, new Vector3(x, y, z), Vector3.up, Vector3.right, true, verts, uvs, tris);
                    // Front
                    if (IsTransparent(x, y, z + 1, worldPos, chunk))
                        BuildFace(block, new Vector3(x, y, z + 1), Vector3.up, Vector3.right, false, verts, uvs, tris);


                }
            }
        }

        visualMesh.vertices = verts.ToArray();
        visualMesh.uv = uvs.ToArray();
        visualMesh.triangles = tris.ToArray();
        visualMesh.RecalculateBounds();
        visualMesh.RecalculateNormals();

        meshFilter.mesh = visualMesh;
        meshCollider.sharedMesh = visualMesh;

        return new MeshObject(visualMesh, meshCollider, meshFilter);
    }

    public static void BuildFace(int block, Vector3 corner, Vector3 up, Vector3 right, bool reversed, List<Vector3> verts, List<Vector2> uvs, List<int> tris)
    {
        int index = verts.Count;

        Vector2 uvCorner = GetTexturePos(block);

        verts.Add(corner);
        verts.Add(corner + up);
        verts.Add(corner + up + right);
        verts.Add(corner + right);

        Vector2 uvWidth = new Vector2(0.22f, 0.22f);

        uvs.Add(uvCorner);
        uvs.Add(new Vector2(uvCorner.x, uvCorner.y + uvWidth.y));
        uvs.Add(new Vector2(uvCorner.x + uvWidth.x, uvCorner.y + uvWidth.y));
        uvs.Add(new Vector2(uvCorner.x + uvWidth.x, uvCorner.y));

        if (reversed)
        {
            tris.Add(index + 0);
            tris.Add(index + 1);
            tris.Add(index + 2);
            tris.Add(index + 2);
            tris.Add(index + 3);
            tris.Add(index + 0);
        }
        else
        {
            tris.Add(index + 1);
            tris.Add(index + 0);
            tris.Add(index + 2);
            tris.Add(index + 3);
            tris.Add(index + 2);
            tris.Add(index + 0);
        }
    }

    public static int[,,] GetMap(int chunkSize, List<Block> blocks)
    {
        int[,,] map = new int[chunkSize, chunkSize, chunkSize];
        for (int x = 0; x < chunkSize; x++)
        {
            for (int y = 0; y < chunkSize; y++)
            {
                for (int z = 0; z < chunkSize; z++)
                {
                    try
                    {
                        map[x, y, z] = (int)blocks[x + y + z].Id;
                    }
                    catch
                    {

                        Debug.Log(x + y + z);
                    }
                }
            }
        }

        return map;
    }

    public static bool IsTransparent(int x, int y, int z, Vector3 worldPos, Chunk chunk)
    {

        BlockType block = GetByte(x, y, z, worldPos, chunk);

        switch (block)
        {
            case BlockType.air:
                return true;
            default:
                return false;
        }
    }

    public static BlockType GetByte(int x, int y, int z, Vector3 worldPos, Chunk chunk)
    {

        if ((x < 0) || (y < 0) || (z < 0) || (y >= chunk.chunkSize) || x >= chunk.chunkSize || z >= chunk.chunkSize)
        {

            Vector3 pos = new Vector3(x, y, z) + chunk.gameObject.transform.position;
            Chunk chunk1 = chunkManager.GetChunkByWorldPos(pos);
            if (chunk1 == chunk) { return 0; }
            if (chunk1 == null) { return Generate.GetTheoreticalByte(worldPos, Biomes.plains); }

            return GetByte(Mathf.FloorToInt(pos.x), Mathf.FloorToInt(pos.y), Mathf.FloorToInt(pos.z), chunk1.transform.position, chunk1);
        }
        return chunk.GetBlockFromPosition(x, y, z);

    }

    public static Vector2 GetTexturePos(int block)
    {
        int b1 = block;
        float lenght = 0.00f;
        float height = 0.75f;



        while (b1 >= 1)
        {
            if (b1 < 17)
            {
                lenght += 0.25f;
                b1 -= 1;
            }
            else
            {
                height -= 0.25f;
                b1 -= 16;
            }
        }
        return new Vector2(lenght + 0.02f, height + 0.02f);

    }

    private static List<Block> GetBlockListFromMap(int[,,] map, int chunkSize)
    {
        List<Block> blockList = new List<Block>();
        for (int x = 0; x < chunkSize; x++)
        {
            for (int y = 0; y < chunkSize; y++)
            {
                for (int z = 0; z < chunkSize; z++)
                {
                    blockList.Add(Block.getBlock((BlockType)map[x, y, z]));
                }
            }
        }
        return blockList;
    }

    private static int GetXUnderBlock(Vector3 chunkPos, int y, int z)
    {
        Chunk chunk = World.currentWorld.chunkManager.GetChunkByPos(new Vector3(chunkPos.x - 1, chunkPos.y, chunkPos.z));
        try
        {
            return (int)chunk.GetBlockFromPosition(15, y, z);
        }
        catch (System.NullReferenceException)
        {
            return (int)BlockType.air;
        }
    }

    private static int GetXAboveBlock(Vector3 chunkPos, int y, int z)
    {
        Chunk chunk = World.currentWorld.chunkManager.GetChunkByPos(new Vector3(chunkPos.x - 1, chunkPos.y, chunkPos.z));
        try
        {
            return (int)chunk.GetBlockFromPosition(0, y, z);
        }
        catch (System.NullReferenceException)
        {
            return (int)BlockType.air;
        }
    }

    private static int GetYUnderBlock(Vector3 chunkPos, int x, int z)
    {
        Chunk chunk = World.currentWorld.chunkManager.GetChunkByPos(new Vector3(chunkPos.x - 1, chunkPos.y, chunkPos.z));
        try
        {
            return (int)chunk.GetBlockFromPosition(x, 15, z);
        }
        catch (System.NullReferenceException)
        {
            return (int)BlockType.air;
        }
    }

    private static int GetYAboveBlock(Vector3 chunkPos, int x, int z)
    {
        Chunk chunk = World.currentWorld.chunkManager.GetChunkByPos(new Vector3(chunkPos.x - 1, chunkPos.y, chunkPos.z));
        try
        {
            return (int)chunk.GetBlockFromPosition(x, 0, z);
        }
        catch (System.NullReferenceException)
        {
            return (int)BlockType.air;
        }
    }

    private static int GetZUnderBlock(Vector3 chunkPos, int x, int y)
    {
        Chunk chunk = World.currentWorld.chunkManager.GetChunkByPos(new Vector3(chunkPos.x - 1, chunkPos.y, chunkPos.z));
        try
        {
            return (int)chunk.GetBlockFromPosition(x, y, 15);
        }
        catch (System.NullReferenceException)
        {
            return (int)BlockType.air;
        }
    }

    private static int GetZAboveBlock(Vector3 chunkPos, int x, int y)
    {
        Chunk chunk = World.currentWorld.chunkManager.GetChunkByPos(new Vector3(chunkPos.x - 1, chunkPos.y, chunkPos.z));
        try
        {
            return (int)chunk.GetBlockFromPosition(x, y, 0);
        }
        catch (System.NullReferenceException)
        {
            return (int)BlockType.air;
        }
    }


}

Chunk Manager :

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

public class ChunkManager {


    private List<GameObject> chunkList;
    private List<GameObject> visibleChunksList;

    public List<GameObject> VisibleChunksList
    {
        get
        {
            return visibleChunksList;
        }

        set
        {
            visibleChunksList = value;
        }
    }

    public ChunkManager()
    {
        chunkList = new List<GameObject>();
    }

    public BlockType GetBlockByWorldPos(Vector3 p)
    {
        Chunk chunk = GetChunkByWorldPos(p);

        Vector3 blockPos = getBlockFromPos(p);

        return chunk.GetBlockFromPosition(blockPos);
    }

    public BlockType GetMeshBuildBlock(Vector3 pos)
    {
        if(GetChunkByWorldPos(pos) == null)
        {
            return Generate.GetTheoreticalByte(pos, Biomes.plains);
        }
        else
        {
            Vector3 pos1 = getBlockFromPos(pos);

            return GetChunkByWorldPos(pos).GetBlockFromPosition(pos1);
        }
    }

    public void SetBlockByWorldPos(Vector3 p, BlockType block)
    {
        //Debug.Log(p.x + ":" + p.y + ":" + p.z + ":");
        Chunk chunk = GetChunkByWorldPos(p);

        Vector3 blockPos = getBlockFromPos(p);

        chunk.SetBlock(block, blockPos);
    }

    public Vector3 getBlockFromPos(Vector3 pos)
    {
        Vector3 vector3 = new Vector3();
        vector3.x = Mathf.Abs(pos.x) % World.staticChunkSize;
        vector3.y = Mathf.Abs(pos.y) % World.staticChunkSize;
        vector3.z = Mathf.Abs(pos.z) % World.staticChunkSize;

        return vector3;
    }

    public void AddChunkToList(GameObject chunk)
    {
        chunkList.Add(chunk);
    }

    public Chunk GetChunkByPos(Vector3 pos)
    {
        for(int i = 0; i < chunkList.Count; i++)
        {
            if(chunkList[i].GetComponent<Chunk>().chunkPos == pos)
            {
                return chunkList[i].GetComponent<Chunk>();
            }
        }
        return null;
    }

    public Chunk GetChunkByWorldPos(Vector3 pos)
    {
        Vector3 chunkPos = new Vector3(Mathf.FloorToInt((pos.x / World.staticChunkSize)),
            Mathf.FloorToInt((pos.y / World.staticChunkSize)),
            Mathf.FloorToInt((pos.z / World.staticChunkSize)));
        return GetChunkByPos(chunkPos);
    }

    public Chunk[] GetVisibleChunks()
    {
        Chunk[] visibleChunkArray = new Chunk[VisibleChunksList.Count];

        for(int i = 0; i < visibleChunkArray.Length; i++)
        {
            visibleChunkArray[i] = VisibleChunksList[i].GetComponent<Chunk>();
        }

        return visibleChunkArray;
    }

    public void LoadVisibleChunks(Vector3 chunkPos)
    {
        VisibleChunksList.Clear();
        for (int i = 0; i < chunkList.Count; i++)
        {
            Vector3 pos = chunkList[i].GetComponent<Chunk>().chunkPos;
            if (pos.x < chunkPos.x + World.viewDistance || pos.x >= chunkPos.x - World.viewDistance ||
                pos.z < chunkPos.z + World.viewDistance || pos.z >= chunkPos.z - World.viewDistance)
            {
                VisibleChunksList.Add(chunkList[i]);
                chunkList[i].GetComponent<Chunk>().isVisble = true;
            }else
            {
                chunkList[i].GetComponent<Chunk>().isVisble = false;
            }
        }
    }

    public Chunk GetNextChunkX(int pos, Chunk chunk)
    {
        if(pos == 1)
        {
            return GetChunkByPos(new Vector3(chunk.ChunkPos.x + 1, chunk.ChunkPos.y, chunk.ChunkPos.z));
        } else if(pos == -1)
        {
            return GetChunkByPos(new Vector3(chunk.ChunkPos.x - 1, chunk.ChunkPos.y, chunk.ChunkPos.z));
        }
        else
        {
            return null;
        }
    }

    public Chunk GetNextChunkY(int pos, Chunk chunk)
    {
        if (pos == 1)
        {
            return GetChunkByPos(new Vector3(chunk.ChunkPos.x, chunk.ChunkPos.y + 1, chunk.ChunkPos.z));
        }
        else if (pos == -1)
        {
            return GetChunkByPos(new Vector3(chunk.ChunkPos.x, chunk.ChunkPos.y - 1, chunk.ChunkPos.z));
        }
        else
        {
            return null;
        }
    }

    public Chunk GetNextChunkZ(int pos, Chunk chunk)
    {
        if (pos == 1)
        {
            return GetChunkByPos(new Vector3(chunk.ChunkPos.x, chunk.ChunkPos.y, chunk.ChunkPos.z + 1));
        }
        else if (pos == -1)
        {
            return GetChunkByPos(new Vector3(chunk.ChunkPos.x, chunk.ChunkPos.y, chunk.ChunkPos.z - 1));
        }
        else
        {
            return null;
        }
    }

    public Vector3 GetChunkPos(Vector3 worldPos)
    {
        World world = GameObject.FindGameObjectWithTag("World").GetComponent<World>();
        return new Vector3((int) worldPos.x / world.chunkSize, (int)worldPos.y / world.chunkSize, (int)worldPos.z / world.chunkSize);
    }
}

Chunk :

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



[RequireComponent(typeof(MeshRenderer))]
[RequireComponent(typeof(MeshCollider))]
[RequireComponent(typeof(MeshFilter))]
public class Chunk : MonoBehaviour
{

    public BlockType[,,] blockList;
    public int chunkSize;
    public Vector3 chunkPos;
    public Biomes biome;
    public Vector3 worldPos;

    private Mesh mesh;
    private MeshFilter meshFilter;
    private MeshCollider meshCollider;

    public bool isVisble = false;
    private bool previousVisible = false;
    public bool created = false;

    public void Update()
    {
        if (isVisble == true && previousVisible == false)
        {
            gameObject.GetComponent<MeshRenderer>().enabled = true;
            meshCollider.enabled = true;
            previousVisible = true;
        }

        if (isVisble == false && previousVisible == true)
        {
            gameObject.GetComponent<MeshRenderer>().enabled = false;
            meshCollider.enabled = false;
            previousVisible = false;
        }

    }

    public BlockType GetBlockFromPosition(Vector3 blockPos)
    {
        return GetBlockFromPosition(Mathf.FloorToInt(blockPos.x), Mathf.FloorToInt(blockPos.y), Mathf.FloorToInt(blockPos.z));
    }

    public MeshCollider MeshCollider
    {
        get
        {
            return meshCollider;
        }

        set
        {
            meshCollider = value;
        }
    }

    public MeshFilter MeshFilter
    {
        get
        {
            return meshFilter;
        }

        set
        {
            meshFilter = value;
        }
    }

    public Mesh Mesh
    {
        get
        {
            return mesh;
        }

        set
        {
            mesh = value;
        }
    }

    public Vector3 ChunkPos
    {
        get
        {
            return chunkPos;
        }

        set
        {
            chunkPos = value;
        }
    }

    public void SetChunk(Chunk chunk)
    {
        this.blockList = chunk.blockList;
        this.chunkSize = chunk.chunkSize;
        this.MeshCollider = chunk.MeshCollider;
        this.MeshFilter = chunk.MeshFilter;
        this.Mesh = chunk.Mesh;
        this.ChunkPos = chunk.ChunkPos;
    }

    public void CreateChunk(Biomes biome)
    {
        this.worldPos = transform.position;

        blockList = new BlockType[chunkSize, chunkSize, chunkSize];

        this.biome = biome;

        this.blockList = Generate.GenerateChunk(chunkSize, chunkPos, 20, biome);

        BuildFullMesh();

        created = true;
    }

    private void BuildFullMesh()
    {
        BlockType[,,] list = blockList;
        MeshObject meshObject = new MeshObject(new Mesh(),
            gameObject.GetComponent<MeshCollider>(), gameObject.GetComponent<MeshFilter>());

        meshObject = MeshManager.CreateVisualMesh(chunkSize, list, meshObject, chunkPos, this);

        mesh = meshObject.Mesh;
        meshFilter = meshObject.MeshFilter;
        meshCollider = meshObject.MeshCollider;

    }

    public void SetBlock(BlockType block, Vector3 worldPos)
    {
        worldPos -= transform.position;
        SetBlock(block, Mathf.FloorToInt(worldPos.x), Mathf.FloorToInt(worldPos.y), Mathf.FloorToInt(worldPos.z));
    }

    public void SetBlock(BlockType block, int x, int y, int z)
    {
       
        blockList[x, y, z] = block;
        BuildFullMesh();

        if(x == 0 || x == chunkSize - 1)
        {
            if(x == 0)
            {
                World.currentWorld.chunkManager.GetNextChunkX(-1, this).BuildFullMesh();
            }else
            {
                World.currentWorld.chunkManager.GetNextChunkX(1, this).BuildFullMesh();
            }
        }

        if (y == 0 || y == chunkSize -1)
        {
            if (y == 0)
            {
                World.currentWorld.chunkManager.GetNextChunkY(-1, this).BuildFullMesh();
            }
            else
            {
                World.currentWorld.chunkManager.GetNextChunkY(1, this).BuildFullMesh();
            }
        }

        if(z == 0 || z == chunkSize - 1)
        {
            if (z == 0)
            {
                World.currentWorld.chunkManager.GetNextChunkZ(-1, this).BuildFullMesh();
            }
            else
            {
                World.currentWorld.chunkManager.GetNextChunkZ(1, this).BuildFullMesh();
            }
        }

    }

    public string BlockListToString()
    {
        string list = "";

        for (int x = 0; x < chunkSize; x++)
        {
            for (int y = 0; y < chunkSize; y++)
            {
                for (int z = 0; z < chunkSize; z++)
                {
                    list += ", " + x + " : " + y + " : " + z + " = " + this.blockList[x, y, z];
                }
            }
        }

        return list;
    }

    public BlockType GetBlockFromPosition(int x, int y, int z)
    {
        //Debug.Log(x + ":" + y + ":" + z);
        return blockList[x, y, z];

    }

    public static int[,,] GetIntFromBlockType(BlockType[,,] blocks, int chunkSize)
    {
        int[,,] map = new int[chunkSize, chunkSize, chunkSize];
        for (int x = 0; x < chunkSize; x++)
        {
            for (int y = 0; y < chunkSize; y++)
            {
                for (int z = 0; z < chunkSize; z++)
                {
                    map[x, y, z] = (int)blocks[x, y, z];
                }
            }
        }

        return map;
    }

}

I can give you a few performance tips:

  1. In GetChunkByWorldPos method you are using Mathf.FloorToInt. This is unnecessary, you are casting float to int and back with no reason. Use Mathf.Floor.

  2. Don’t use gameObject.GetComponent<>() in Update(), assign component to a variable in Awake or Start method and in your Update use the variable instead.

  3. Don’t use exceptions the way you are using them in GetXUnderBlock and similar methods. Raising exceptions can cost you a lot of performance even if you are catching them.

Test with deep profiler.

and check some of those unite performance videos, like this one *start from 29min+
** some of those might be already improved in latest versions though

I would try to do that but it already takes up to 20 mins to start the thing so adding deep profile wouldn’t be a good idea (at the moment)

Thanks for the advice. I’ll give it a shot and see how it goes

I change everything you said and it still hangs, any other ideas?

remove debug log (if it hits there, thats going to slow down a lot), try building smaller world first?

When I know the specific methods and classes that are a performance issue then I use System.Stopwatch instead of Profiler. You can analyze which parts of code take how much time. Also for testing you should reduce your map / chuck size so that you don’t have to wait too long.

Also did you remove all try / catch statements?

Yeah I removed all of them. I’ll give the stopwatch a try

Alright it seems to be finding the chunk when it needs to load the chunk from the list of chunks. Any ideas how to store it more efficiently?

The way you are doing it now in GetChunkByPos is O(n) operation, you are searching whole list item by item to get the one that matches the postition. If you need fast retrieval by its position, you can store the chunks in Dictionary<Vector3, Chunk> and it will be O(1) operation.

That’s completely solved it mate. Thanks a million!!

You are welcome, I am glad you got it fixed.