Jumat, 01 November 2013

Unity Voxel Tutorial Part 8: Loading Chunks


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). 
Primbon Jawa Tutorial, Unity, Voxel Tut, Voxels