[Unity] Voxel terrain generation - 2 - Chunk generationsteemCreated with Sketch.

in gaming •  6 years ago  (edited)

To quickly explain, we will put the values of the chunk in lists (Like the cube) and then generate the whole chunk from those values. Instead of making cube by cube, we make chunk by chunk.

Let's start then :

You will need a chunk script. 

- Go to your project and create the script "Chunk", it will store every blocks.

- We also need another script called "Block_Cube", this will be the cube generation script. It tells the chunk how the blocks are generated virtually. 

Go to your Chunk script :

- We can delete the Update method, it won't be used.

 [RequireComponent(typeof(MeshFilter))] 

 When the script is added to the object, Unity will read these lines in the script and automatically add the components we need. 


 public List<Vector3> vertices = new List<Vector3>();
   public List<int> triangles = new List<int>();
   public List<Vector2> UVs = new List<Vector2>();

That's where we will store all of the chunk values, the shape values of it.


  Mesh chunkMesh; 

Here, we will put the values we created and give it to the Chunk object. It will make the final shape of our chunk.


 public Material chunkMaterial; 

 Finally, this is the texture of the chunk, where we will have the texture for each block. 


 Now go to the Block_Cube script : 

 public class Block_Cube { 

 As you can see, the script does not depend on MonoBehaviour, it can't be attached to an object and act by itself, we will use the Chunk script to manipulate the Block_Cube script. 


 public Chunk owner; 

 We need to know which Chunk possesses the block we are creating in order to give it back the values of the block after we called it (Like positions, Vertices, Triangles, ...). 


 public Vector3 position; 

This is the position of the block in the chunk. We will apply the position to the vertices. (Remember that vertices are positions in the world, and we will give them back to the chunk to create it.)

Because the chunk creates the block, we will have a local position for each one of them. 

Block position - Chunk world position


 public Block_Cube(Chunk o, Vector3 pos) 

To create a new cube, the chunk will call this script. That's why we use this method.

We call it a constructor, so when we create a new instance of this script, we call it from here while giving values to the new instance.


 owner = o; 

 When the chunk creates a new cube, it gives itself as the owner of the cube. So we can translate it as "Owner of this actual cube = chunk creating this new cube". 


 position = pos; 

 Given by the chunk, this is the position of the cube inside the chunk. 


 GenerateCube(); 

 After getting it's properties, the cube can start generating. 


 void GenerateCube() 

 Just like the Cube Generation, it will take care of the generation and how to generate the cube. That's where we give conditions. 


Now we can generate a Cube :

From now i will do almost the same way i did for the post about Cube Generation (here), if  there is any changes, it will be explained.

We create the method used to make the cube sides and don't forget to call it when we generate the cube, otherwise the cube will be generated but it won't create anything. 


When we add the new triangles, we give them to the chunk's list, using "owner.triangles".

We do the same for vertices and UVs.

When we add new vertices to the chunk, we also add the position of the cube to the vertices. "Position" and "Vertex" are both positions in the world, so we can add them.

It puts the generated cube at the position it should be inside the chunk, and at the chunk world position.


 Now go back to the Chunk script :

Like we did for the cube, we will generate the full chunk. We add the method which is used to get and give the values we created.

But this time, we empty the mesh before generating.


We didn't tell the chunk how we want it to be created. We will do it in the Start method for now. 

Each 1 unit on the x axis we will create a new block. From 0, we add 1 and

we will do it 5 times, it makes 5 blocks on the X axis.

After generating those virtual blocks, let's make them real in the world. We call the method "GeneratePhysicalChunk".

We don't make each quad and then combine them. Instead, we make the whole chunk directly.

- Now go to Unity and give the script to a new empty object called "Chunk".

- Test your game. Our 5 blocks should be existing in the scene.

But we only created the front of our cubes. Now we need the other faces.

- Go back to the Block_Cube script :

We can call each of the faces

And for each one of them, we will give the vertices at their position, after the front side we already added.

 - Now if you test the game again, you will have full cubes. 

 But there are faces between each cube and we need to remove them. We can't have too much value because Unity has a limit. That's also why we can't make big chunks. 



Let's make a map.

We need a 3D array containing ints. Each index in the 3D array is a position in the chunk, a block.

Example :

Block at x = 2, y = 10 and z = 7 in the chunk is stored at chunkMap[2,10,7].

If the block stored at chunkMap[2,10,7] = 0, it's ID is 0, the block at this position is air.

If the block stored at chunkMap[2,10,7] =  1, it's ID is 1, the block at this position is dirt.


We also add a new variable called "chunkSize" which gives the size of a chunk for each axis. 


Physically, the map array looks like this : 

And for a whole world, each chunk has it's own map : 


Now we have to change and add things to the Chunk script :

- First, we have to change the Start method :

We have to initialize the map array.

When it is created, it doesn't have a size and if we want to access a value inside which is bigger than the size of the array, we will get errors. 

If it is initialized, accessing a value we didn't change inside the array will return 0 instead of an error because we won't be outside of it. So, the size of the array will be the size of the chunk.

Then we call a new method, "GenerateVirtualMap".

We have 3 loops, (x,y,z for every axis because we have a 3D chunk) and for each loop we will create cubes from position 0 to the chunkSize value, adding 1 unit between each block.(Remember than x++ means "X + 1")

And for each position x,y,z we will encounter, we set the block ID in the map to 1. So the whole chunk will be made of the block number 1.

Now that we have a virtual chunk stored in a map, we can get every value of it's blocks and store them in the lists with another method called "GenerateBlocksMap".

Once again, it will loop in each position of the chunk in the 3 axis.

But each time the same position in the map has an ID of 1 (Here the whole chunk), we generate a new block virtually again at the actual position.

And finally, we call the method "GeneratePhysicalChunk" to use every value we generated and make the whole chunk.

You can test it in Unity and you will get a 10x10x10 chunk.

Now we can work on the inter-block idea.

In the Chunk script, we add a new method to check if a block exists at a position in the map :

We call the method with a Vector3, it's easier to call and avoid giving 3 values.

Then, for each position (x, y and z), we put the value from the Vuector3. We make sure it is an int, because the Vector3 contains floats.


 

Then we start by checking if the position we are looking at is outside the chunk. So, Under X or Y or Z, or above the chunkSize.

If it is out of the chunk, we return false, translated by "there is no block there".

Then, if it is not outside, we check if the ID of the block is 0 (air). If it is, we return false again.

Else, we return true because the block must exist in the chunk and is not air.

 Now go back to the Block_Cube script : 

 

For each side we make in the "GenerateCube" method, we have to check if there is a block in front of it.

To do that, we call the method we just created which is stored inside the owner of the cube.

If it returns "false", there is no block in front of the face and we can draw it. If it returns "true", there is a block and the face won't be created.

Now you can test the game once again and you will see that only the visible faces are generated :

 

- The last thing we will do is add a texture to the blocks.

To do that, we will add a UV order and size to the quad generation in the Block_Cube script.

 

Now we can give the generated values to the final mesh;

Go to the Chunk script and we will change the "GeneratePhysicalChunk" method :


3 Lines were added.

- First, we find the MeshRenderer of the chunk and we store it in a variable to access it easily.

- Then, we add the UVs values to the mesh UV array.

- Finally, we set the material of the renderer to be the material we gave to the object. (Don't forget to drag and drop your texture material in the inspector, in the Chunk script of the chunk object, inside the chunk material slot).

- Now test your game and you should see a chunk with textures on each block :

 We can also play with the generation to have different results :  

Authors get paid when people like you upvote their post.
If you enjoyed what you read here, create your account today and start earning FREE STEEM!