Unity Voxel Tutorial Part 8: Loading Chunks


Hello everyone following the voxel tutorial, it's been a long time since an update. In this time I've written a new updated tutorial on voxel terrain that supports infinite terrain and saving/loading of chunks. Try it out on my new site: AlexStv.com


So here we are at the eighth and final part of the C# voxel tutorial. I hope everyone who has followed this tutorial has gotten what they want out of it. In this part we'll be making chunks generate around the player and despawn when they are outside of a certain range. This was the last part that I felt was necessary for this kind of game. The reason I think I'll be stopping here is that at this point and onward different types of voxel games will be using very different setups depending on what's necessary and I don't want to make a tutorial on how to make a minecraft clone. Lighting is one point that could be useful but personally I prefer deferred lighting so I haven't done the research to write a tutorial that I think would give the best technique. That said if you've come this far you're well on your way to making whatever voxel game you want and I'll add some links to great resources for further development.

On to loading chunks, we'll be replacing the code that instantiates chunks all at the start with something new both to reduce startup time and to reduce drawcalls. This will mean that we can have much larger levels but it won't reduce the ram cost very much because the data of the entire level is still loaded at all times so we still can't run massive levels. To do that you would have to consider writing the level data to disk and loading it per chunk.

Anyway, wall of text above so we'll get started. We'll start by making two new public functions in our world class:
public void GenColumn(int x, int z){

}

public void UnloadColumn(int x, int z){
 
}


We'll be moving chunk generation to GenColumn in columns at a time and Unload column will delete the game objects. Start with GenColumn which will use the previous chunk spawning code so copy this from the end of the start function and paste it into the GenColumn function.

for (int x=0; x<chunks.GetLength(0); x++) {
 for (int y=0; y<chunks.GetLength(1); y++) {
  for (int z=0; z<chunks.GetLength(2); z++) {
      
   //Create a temporary Gameobject for the new chunk instead of using chunks[x,y,z]
   GameObject newChunk = Instantiate (chunk, new Vector3 (x * chunkSize - 0.5f,
   y * chunkSize + 0.5f, z * chunkSize - 0.5f), new Quaternion (0, 0, 0, 0)) as GameObject;
      
   chunks [x, y, z] = newChunk.GetComponent (\"Chunk\") as Chunk;
   chunks [x, y, z].worldGO = gameObject;
   chunks [x, y, z].chunkSize = chunkSize;
   chunks [x, y, z].chunkX = x * chunkSize;
   chunks [x, y, z].chunkY = y * chunkSize;
   chunks [x, y, z].chunkZ = z * chunkSize;
      
  }
 }
}

Now this is to generate the whole level so remove the x for loop and the z for loop and the corresponding closing brackets. Now it will spawn all the chunks at a given x and z.

For the UnloadChunk function copy the for loop from GenColumn but inside it we'll destroy the gameobject for every script in chunks with the specified x and z:
for (int y=0; y<chunks.GetLength(1); y++) {
 Object.Destroy(chunks [x, y, z].gameObject);
  
}

This calls Object.Destroy on the gameobject of every chunk script we have in the array at x and z. Now we just have to call these functions. We'll do that in the modify terrain class so move over there and create a function called LoadChunks:
public void LoadChunks(Vector3 playerPos, float distToLoad, float distToUnload){
}

What this will do is generate chunks around a position, if it's within distToLoad it loads them and outside distToUnload it removes them. So for each chunk x,z get the distance to the player and then if it's closer than distToLoad and it hasn't been spawned yet spawn the column, otherwise if it's further than distToUnload and it is spawned unload the column:
public void LoadChunks(Vector3 playerPos, float distToLoad, float distToUnload){
 
 
 for(int x=0;x<world.chunks.GetLength(0);x++){
  for(int z=0;z<world.chunks.GetLength(2);z++){
   
   float dist=Vector2.Distance(new Vector2(x*world.chunkSize,
   z*world.chunkSize),new Vector2(playerPos.x,playerPos.z));
   
   if(dist<distToLoad){
    if(world.chunks[x,0,z]==null){
     world.GenColumn(x,z);
    }
   } else if(dist>distToUnload){
    if(world.chunks[x,0,z]!=null){
     
     world.UnloadColumn(x,z);
    }
   }
   
  }
 }
 
}

When this runs it will make sure that all the chunks are in check. We'll call it in the update loop for ModifyTerrain, I've used 32 and 48 for the distances this way you'll see them load not to far away but not be able to fall out of the world. I also get the player position using the Player tag.
LoadChunks(GameObject.FindGameObjectWithTag("Player").transform.position,32,48);

This doesn't need to be run every frame, once a second should be more than enough especially if the distance is large enough. To make this work you'll have to tag something as the player, the camera will do if you change the tag from MainCamera to Player and you can move it around in the editor and see the chunks update around you or you can import the standard unity FPScontroller from Assets>Import Package>Character Controller and tweak the capsule collider radius to 0.4.

Now if you run the game you should see that chunks only load around the player and nowhere else meaning you can have a map size much larger and run it smoothly. Try 512x32x512 for example and see how that works. If you walk to the middle you'll see a large circle of chunks loaded around the player tagged object if you look in the editor view.

Thank you everyone who has followed through all this way I hope your satisfied. Please let me know what you think of this tutorial series as a whole and this part. As always any bugs you encounter I'll try to fix right away. I may be writing a beginner tutorial after this but I haven't decided yet, if you have any suggestions let me know.

Complete unity project file and web player demo

Here are some resources to help you further:

Let's make a Voxel Engine: Concepts behind a Voxel engine explained in detail by the developer of Vox.
LibNoise: Using noise functions to do amazing things
Save Mesh Created by Script in Editor PlayMode: How to save a mesh generated from code ingame
Pathfinding in unity for voxel structures: Again from UnityCoder.com pathfinding for voxel games
Unity forum's "After Playing MineCraft" thread: Unity thread where people have made MineCraft like games using Unity
Cubiquity: An excellent free voxel terrain asset for Unity
More Voxel Resources: Lastly, UnityCoder.com lists even more voxel resources
If you want to stay updated on my next tutorial follow me on twitter (@STV_Alex) or on google+ (+AlexandrosStavrinou). 

28 comments:

  1. Hello very good tutorial, but I have a doubt, if GenColumn void (int x, int z) {
     
    } What I do with these two parameters seen that there are some down soon with the same names ... please pass me the complete function for further clarification ..

    ReplyDelete
  2. Hello dude, could put an example of how to generate terrain using perlin on the y axis, I saw you showed an example in 2d, was wondering do it in 3D as well: http://forum.unity3d.com/attachment.php? attachmentid = 23795 & d = 1313703312, which I tried vertices not SHARE the same vertex staying open mesh .....

    ReplyDelete
  3. Thank you so much for writing this! I've gone through it all and feel like I have a good grasp of how this works and how I can move forward with my idea. This is exactly what I needed to get started.

    ReplyDelete
  4. I think the only big thing that I wish had been explored is keeping the data in the chunks instead of the world and saving the chunks to disk. But it looks like some of the links you provided will help with that. Thanks again!

    ReplyDelete
  5. Hi, Im having a little problem with the "LoadChunks(GameObject.FindGameObjectWithTag("Player").transform.position,32,48)" line. Im getting an error "NullReferenceException" please help :/

    ReplyDelete
    Replies
    1. Hey, I'm thinking this might be caused by unity not finding a game object with the tag "Player", do you have an object tagged player in your scene? I just used the standard unity first person controller which has the tag but you could tag anything as "Player".

      Delete
    2. do you have a semicolon at the end?

      Delete
  6. Thanks for the excellent tutorials! Exactly what I wanted to learn. Just wondering if you could post the complete source as you did with the other parts. Also I am not sure if unloading the columns actually works. If I make a high tower and then walk to the complete other side I don't see it disappearing. Did I misunderstand?

    ReplyDelete
    Replies
    1. I've updated the post with a link to a complete unity project download near the bottom :) If your terrain isn't dissapearing it could just be that you're not far enough away, the distance is set to pretty far, try either using something smaller as the parameters in this line:

      LoadChunks(GameObject.FindGameObjectWithTag("Player").transform.position,32,48);

      or you could make it generate a larger grid of chunks, then go to the opposite corner from where you start and have a look in the editor view.

      Delete
  7. Man, I've spent the past couple of days on your tutorials. They are BY FAR the best and most fun I've accomplished. The only suggestion I could make would be to blatantly list which script we're making modifications to. Some of the posts were a little vague and since I followed them from the start I was able to figure it out, but some spots took a few minutes. Otherwise EXCELLENT WORK!!!

    ReplyDelete
  8. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
  9. You almost never want to gain access to another objects script by string name: chunks[x,y,z] = newChunk.GetComponent("Chunk") as Chunk; This is horribly inefficient. I tried putting in the appropriate syntax on this reply but the blog keeps formatting and ruining my code, so if you want to know the proper way to access a script on a object, please check the manual :) Great tutorial series by the way.

    ReplyDelete
  10. Thanks for this tutorial, I hugely enjoyed it! You must have gone through a lot of trial and error to get it all down to something so simple. A great resource!

    ReplyDelete
  11. (Apologies if I double posed, tried to post from my phone not sure it worked)

    I really enjoyed these tutorials Alex, I learned a lot from them. Ever since I finished following your tutorial I've been trying to implement lighting to your Voxel engine. I've read up how Minecraft implemented lighting (a value from 15 to 1 depending on distance from light source). I understand the theory but I'm having a very hard time figuring out how to implement this in Unity. Perhaps you could offer just a starting point from where I could continue researching this?

    What approach would you take to make the texture on a triangle a bit darker?

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. Ok first of all my apologies for spamming your blog!

      I've finally managed to figure this out. I made a texture atlas (with ALPHA!) that contains several different opacity textures. I've set the material on the chunk to "Self-Illum Diffuse" and applied this shadow texture atlas to the Illumin (A) slot.

      Then it's a matter of assigning a lighting UV map to the meshe's uv2 and voila! Lighting implemented.

      I'm not joking my experience of Unity was multiplied by a factor of 10 from following your tutorials. Please keep em coming!

      Delete
  12. Would you be able to create a tutorial to generate the terrain using some sort of block class instead of bytes? For example, data[x, y, z] = Block.STONE.getBlockID();, instead of data[x, y,z] = 1;

    ReplyDelete
  13. This comment has been removed by a blog administrator.

    ReplyDelete
  14. Hi Alexandros! Just wanted to start off saying that I have loved these tutorials and you're so great for typing all this out for us to see. I do have a question though. In my main game, it's set in a large (really really large) city. For all the buildings I'm using City Engine which generates a whole bunch of meshes. Would there be anyway to use this code for meshes instead of 3d shapes? I know that, for one, you couldn't have them stack (the buildings I mean), and also that it would be slow.

    ReplyDelete
  15. Ok then, now that we're done here, I have to say I didn't understand half of it. No its not your fault at all. The tutorial is excellent, I just can't wrap my head around some things. xD
    And its the first time I'm using functions like the "byte" function and byte arrays, so I'm not entirely clear as to why those functions.

    So I have a question, how in hell did you learn all this? Is it really just lots of doodling squares and how it would work, in order to understand yourself, or is there something else to it? I find it hard to picture everything we're doing here in code so I don't quite understand it.

    ReplyDelete
    Replies
    1. Forgot to mention, I already made a few games, but its the first time I'm trying the procedural voxel level generation, so this area is pretty much new to me. xD

      And I'm really interested in implementing marching cubes into a unity game but can't find any tutorials on it. I just found documentation on marching cubes, and I don't know enough to just implement it myself.

      Delete
  16. Thanks for this tutorial series - its fantastic :)

    ReplyDelete
  17. What does the water prefab do in the complete project?

    ReplyDelete
  18. thanks for the awesome tutorial series on voxels, and making easy to follow.
    you are awesome!

    ReplyDelete
  19. I'm getting a null reference exception at this line in the load chunks function:
    for(int x=0; x<world.chunks.GetLength (0); x++) {

    ReplyDelete