Search Unity

  1. Good news ✨ We have more Unite Now videos available for you to watch on-demand! Come check them out and ask our experts any questions!
    Dismiss Notice

[CoreDev] Creating Voxelised Worlds (Like Minecraft!)

Discussion in 'Community Learning & Teaching' started by Myhijim, Jul 28, 2013.

  1. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148



    |Support us^ | |Need Help?^ | |Have Tut Ideas?^|
    [HR][/HR]


    Hey all, due to the massive interest in all things voxels I have done, I decided that I would create a single easy to use resource for everyone looking where to start with voxels, chunking and mesh deformation with explanations not only in text but also visual representations such as webplayers and pictures.

    Hopefully, I will update this thread from time to time with new and exciting progress, depending on the demand.

    My (HIGHLY OUTDATED) Voxel Game :
    http://forum.unity3d.com/threads/158783-Civil-A-3D-Voxel-World


    Why are you doing this?
    I decided only recently that the way to really connect with the community that you wish to have for your own games is to be a part of that community, and I would like to share all the knowledge I have gained on the use of voxels in Unity.

    Not only that but teaching is an excellent way of re-enforcing what you already know.


    Some of your information is wrong! Your code is bad!
    [/B]I am but only human, and by no means am I an expert on all of this or programming in general, but by sharing what I know, I may build my way towards that. If any expert coders or any coders who can see error in my code or can find a better way to handle something, please, do not make a big deal of it on this thread, I would much prefer it if you. shoot me a private message.

    Contents
    (Click the lesson to jump to)

    Chapter 1 - Basic Principles

    #1 - Generating a 'Chunk'
    #2 - Addition of basic Perlin noise
    #3 - Creating a texture atlas [Art]
    #4 - Implementing multiple 'block' types.
    #5 - Basic generation of block 'layers'

    Chapter 2 - Extending the World

    #1 - Generating multiple chunks [Horizontally]
    #2 - Cleaning chunk edges/Reading from a singular Perlin noise heightmap.
    #3 -
    Generating multiple chunks [Vertically]
    #4 - Chunk face sharing

    Chapter 3 - Getting into the World

    #1 - Chunk mesh colliders and optimization
    #2 - Basic movement
    #3 - Digging into the chunk\Placing on the chunk
    #4 - Basic block selection menu

    Chapter 4 - Advanced World Mechanics

    #1 - Generation of transparent and semi-transparent blocks
    #2 - Liquid physics
    #3 - Basic block physics
    #4 - 'Biome'/'Area' generation
    #5 - 'Biome'/'Area' blending

    Chapter 5 - A bit of Fun

    #1 - Explosions
    #2 - ???
    #3 - ???

     
    Last edited: Mar 6, 2014
  2. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    Chapter 1 - Basic Principles
    Lesson #1


    Let us dive right in the deep end shall we? I will try to keep the language as self explanatory as possible

    Now the first thing that is needed is the understanding of why we must build chunks. So let's start there.

    What are chunks?
    Chunks in essence are nothing more than an empty shell for holding data. In our case, we are using chunks to store block values, and to do this we are going to be using a multi-dimensional array.

    By this point , if you are new to this, you will be wondering why not create individual cube gameobjects for each and every block. You can try do it this way if you wish, but It will completely lag out almost any system. This is why we use a 'chunk' which can basically hold any amount of blocks in a single gameobject.

    For this tutorial series we will be using chunks that are 16 x 16 x 16(height*width*depth). To demonstrate I have found a visual example :

    Remember these from school? Well they follow the same idea as a 'chunk'. Consider the singular entities on the right to be a Cube gameobject. Now consider the one on the left, it is a singular cube but it gives the IMPRESSION of multiple. This is almost exactly how chunks work. In the cube on the left only the visible faces are 'drawn'.

    Pretty good analogy huh ;)

    The 'RVector' Class


    Firstly you MUST download this little helper script of mine and place it somewhere in your asset folder, it is a very simple script, however it needs to be there. All it is a rounded vector3 hence R for rounded.

    Here : View attachment $RVector3.cs

    The 'Block'


    Here is a test texture I whipped up for you to use as the blocks material texture! : $CrateClosed.png
    Just save as, or use your own!

    We must make a very simple block class which will be the basis of everything that we are doing, every piece of pixely goodness!

    Make SURE that you don't forget to remove : Monodevelop, we do not want it to inherit from Monodevelop
    Code (csharp):
    1.  
    2.  
    3. using UnityEngine;
    4. using System.Collections;
    5.  
    6.  
    7. public enum BlockFace
    8. {
    9.     All,
    10.     Top, //Y+
    11.     Bottom, //Y-
    12.     Left, //X-
    13.     Right, //X+
    14.     Far, //Z+
    15.     Near //Z-    
    16. }
    17.  
    18. public class Block
    19. {
    20.     public bool empty = false;
    21.    
    22.     public Block ReturnBlock {get{return this;}}
    23.    
    24.     public Block(bool isEmpty)
    25.     {
    26.         empty = isEmpty;
    27.     }
    28.    
    29. }
    30.  

    [TABLE="width: 500, align: center"]
    [TR]
    [TD="align: center"]BlockFace [/TD]
    [TD="align: center"]This is a basic enum structure that is used solely for the purposes of cleanliness, it has comments on which vector direction corresponds to each, and will make our life much easier a little later on.[/TD]
    [/TR]
    [TR]
    [TD="align: center"]ReturnBlock [/TD]
    [TD="align: center"]This property makes it easier to locate and access specific blocks.[/TD]
    [/TR]
    [TR]
    [TD="align: center"]public Block[/TD]
    [TD="align: center"]This just allows us to, whenever we make a new block, easily pass an argument as to whether the block will be empty or not.[/TD]
    [/TR]
    [/TABLE]

    Huzzar, now that was fairly simple right!




    Generating the Chunk

    Now we move onto creating, and then filling, the empty shell of our chunk. For now in this lesson we will only be creating a very basic chunk with one texture and no colliders. For this we are going to have to set up a few basic variables and functions.

    This tutorials series will require knowledge of the C#/Unityscript language and if you do not understand some of the data types or how things are called, this is probably a little too advanced for you however I will help you out if you ask, just don't expect me to build your game for you.



    The 'Chunk'


    Now this is where all of your block details are stored and processed.

    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5.  
    6.  
    7. [System.Serializable]
    8. public class Chunk : MonoBehaviour
    9. {
    10.  
    Now this has a few very important features to note:


    [TABLE="width: 500, align: center"]
    [TR]
    [TD="align: center"]using.System.Collections.Generic[/TD]
    [TD="align: center"]Allows us to use the List class, plays a key part in mesh generation.[/TD]
    [/TR]
    [TR]
    [TD="align: center"][System.Serializable][/TD]
    [TD="align: center"]This allows us to view the chunk class in the inspector so we can see what's going on under the hood.[/TD]
    [/TR]
    [/TABLE]



    Now it is time of all the accessible variables that must be set up
    Code (csharp):
    1.  
    2. Mesh chunkMesh;
    3.  
    4.      RVector3 chunkPosition;
    5.         public RVector3 Position{get{return chunkPosition;} set{chunkPosition=value;}}
    6.    
    7.     public RVector3 chunkSize;
    8.         public RVector3 Size{get{return chunkSize;} set{chunkSize=value;}}
    9.    
    10.     public Block[,,] chunkBlocks;
    11.         public Block[,,] ReturnChunkBlocks{get{return chunkBlocks;}}
    12.            
    13.     public Chunk ThisChunk {get{return this;}}
    14.    
    15.         List<Vector3> chunkVerticies = new List<Vector3>();
    16.         List<Vector2> chunkUV = new List<Vector2>();
    17.         List<int> chunkTriangles = new List<int>();
    18.     int VerticiesIndex;
    19.  
    [TABLE="width: 500, align: center"]
    [TR]
    [TD="align: center"]chunkPosition
    chunkSize
    chunkBlocks
    ThisChunk
    [/TD]
    [TD="align: center"]These variables are put in place so that each chunk is easily accessible in terms of their values and allows easily setting of values from the WorldControl (Which will be covered when we do multi chunks) hence why they are properties.

    These will become more understandable in later chapters.[/TD]
    [/TR]
    [TR]
    [TD="align: center"]chunkMesh

    chunkTriangles
    chunkUV
    chunkVerticies
    [/TD]
    [TD="align: center"]All of these variables are mesh related and will be covered in more depth when we move onto the actual generation.[/TD]
    [/TR]
    [/TABLE]



    We are getting much closer to the actual generation part of the system, now what we are going to do is generate the values for the mesh not generate just yet. We are essentially filling the empty shell I was talking about a chunk as being. For now, we are going to make a fully solid chunk, later on we will look at various different shapes that you can make, including actual terrain.

    Code (csharp):
    1.  
    2.     void Awake()    
    3.     {
    4.         chunkMesh = this.GetComponent<MeshFilter>().mesh;
    5.         GenerateChunk();
    6.     }
    7.  
    8.     public void GenerateChunk()
    9.     {
    10.         chunkBlocks = new Block[chunkSize.x+1,chunkSize.y+1,chunkSize.z+1];
    11.  
    12.         for(int x=0; x<=chunkSize.x; x++)
    13.             {for(int z=0; z<=chunkSize.z; z++)
    14.                 {for(int y=0; y<=chunkSize.y ; y++)
    15.                         {chunkBlocks[x,y,z] = new Block(0,false);}
    16.                 }
    17.             }
    18.     UpdateChunk();
    19.     }
    20.  
    Fairly simple right? All we are doing here is running a for loop for each axis and filling the chunk with solid blocks. Then updating the physical chunk.



    Now we are going to get onto the SERIOUSLY LONG AND TEDIOUS stuff, but it is the most important of all so I urge you to pay close attention and not just simply copy and paste. We are moving into the generation of the mesh.

    Code (csharp):
    1.  
    2.     public void UpdateChunk()
    3.     {
    4.         chunkVerticies = new List<Vector3>();
    5.         chunkUV = new List<Vector2>();
    6.         chunkTriangles = new List<int>();
    7.        
    8.         chunkMesh.Clear();
    9.        
    10.         float blockSize = 1;
    11.  
    12.  
    [TABLE="width: 500, align: center"]
    [TR]
    [TD="align: center"]chunkVerticies
    chunkUV
    chunkTriangles

    chunkMesh
    [/TD]
    [TD="align: center"]All this does is empty all the lists and clears the mesh so it may be rebuilt as every time you make an edit to a chunk it's rendered mesh must be updated. This will be used more when we move onto actual digging/placing of blocks[/TD]
    [/TR]
    [TR]
    [TD="align: center"]blockSize [/TD]
    [TD="align: center"]Self explanatory. By default this is set to 1x1x1 for each block, I do NOT recommend you change this unless you have a firm grasp of what you are doing.[/TD]
    [/TR]
    [/TABLE]



    Now comes the calm before the storm, this is just before we get into the really long code, so prepare yourself!

    Code (csharp):
    1.  
    2.  
    3. for (var y = 0; y<= chunkSize.y; y++)
    4.         {for (var x = 0; x<= chunkSize.x; x++)
    5.             {for (var z = 0; z<= chunkSize.z; z++)
    6.                 {
    7.            
    8.                     if(!chunkBlocks[x,y,z].empty)
    9.                     {
    10.  
    Now the start of this is really simple and is just looping through the 3 axes again for the size of the block. However the if condition may take a few people some time to wrap their heads around.


    It goes like this :

    [x,y,z]
    relates to not only the position in the array we have created but also the blocks world position/local position within the unity space.

    if the block at [x,y,z] is not empty: Continue


    Prepare yourself, lots of code is coming.



    Now this next part is almost the end of the script and is the whole driving force behind building the chunk.

    Code (csharp):
    1.  
    2.                         if(CheckSides(new RVector3(x,y,z),BlockFace.Top))
    3.                         {
    4.                             VerticiesIndex = chunkVerticies.Count;
    5.  
    6.  
    7.                             chunkVerticies.Add(new Vector3(x,y+blockSize,z));
    8.                             chunkVerticies.Add(new Vector3(x,y+blockSize,z+blockSize));
    9.                             chunkVerticies.Add(new Vector3(x+blockSize,y+blockSize,z+blockSize));
    10.                             chunkVerticies.Add(new Vector3(x+blockSize,y+blockSize,z));
    11.                            
    12.                             UpdateChunkUV();
    13.                         }
    14.                    
    15.                         if(CheckSides(new RVector3(x,y,z),BlockFace.Bottom))
    16.                         {
    17.                             VerticiesIndex = chunkVerticies.Count;
    18.                        
    19.                             chunkVerticies.Add(new Vector3(x,y,z));
    20.                             chunkVerticies.Add(new Vector3(x+blockSize,y,z));
    21.                             chunkVerticies.Add(new Vector3(x+blockSize,y,z+blockSize));
    22.                             chunkVerticies.Add(new Vector3(x,y,z+blockSize));
    23.                            
    24.                             UpdateChunkUV();
    25.                         }
    26.                        
    27.                        
    28.                            
    29.                            
    30.                         if(CheckSides(new RVector3(x,y,z),BlockFace.Right))
    31.                         {
    32.                             VerticiesIndex = chunkVerticies.Count;
    33.                        
    34.                             chunkVerticies.Add(new Vector3(x+blockSize,y,z));
    35.                             chunkVerticies.Add(new Vector3(x+blockSize,y+blockSize,z));
    36.                             chunkVerticies.Add(new Vector3(x+blockSize,y+blockSize,z+blockSize));
    37.                             chunkVerticies.Add(new Vector3(x+blockSize,y,z+blockSize));
    38.                            
    39.                             UpdateChunkUV();
    40.                         }
    41.                        
    42.                         if(CheckSides(new RVector3(x,y,z),BlockFace.Left))
    43.                         {
    44.                             VerticiesIndex = chunkVerticies.Count;
    45.                        
    46.                             chunkVerticies.Add(new Vector3(x,y,z+blockSize));
    47.                             chunkVerticies.Add(new Vector3(x,y+blockSize,z+blockSize));
    48.                             chunkVerticies.Add(new Vector3(x,y+blockSize,z));
    49.                             chunkVerticies.Add(new Vector3(x,y,z));
    50.                            
    51.                             UpdateChunkUV();
    52.                         }
    53.                    
    54.                         if(CheckSides(new RVector3(x,y,z),BlockFace.Far))
    55.                         {
    56.                             VerticiesIndex = chunkVerticies.Count;
    57.                        
    58.                             chunkVerticies.Add(new Vector3(x,y,z+blockSize));
    59.                             chunkVerticies.Add(new Vector3(x+blockSize,y,z+blockSize));
    60.                             chunkVerticies.Add(new Vector3(x+blockSize,y+blockSize,z+blockSize));
    61.                             chunkVerticies.Add(new Vector3(x,y+blockSize,z+blockSize));
    62.                            
    63.                             UpdateChunkUV();
    64.                         }
    65.                    
    66.                          if(CheckSides(new RVector3(x,y,z),BlockFace.Near))
    67.                         {
    68.                             VerticiesIndex = chunkVerticies.Count;
    69.                        
    70.                             chunkVerticies.Add(new Vector3(x,y,z));
    71.                             chunkVerticies.Add(new Vector3(x,y+blockSize,z));
    72.                             chunkVerticies.Add(new Vector3(x+blockSize,y+blockSize,z));
    73.                             chunkVerticies.Add(new Vector3(x+blockSize,y,z));
    74.                            
    75.                             UpdateChunkUV();
    76.                         }
    77.                        
    78.                 }
    79.             }
    80.         }
    81.                
    82.     }
    83. FinalizeChunk();
    84.        
    85. }
    86.  
    Looks like a mess doesn't it, but I assure ​you it is much simpler than it looks, however I will have to break it up further and take teeny steps to ensure you are up to speed.

    As you can see, all of these blocks of code are basically the same, I will analysis one block and the rest should come to you fairly easily. Let us take the first block for example:

    Code (csharp):
    1.  
    2.                         if(CheckSides(new RVector3(x,y,z),BlockFace.Top))
    3.                         {
    4.                             VerticiesIndex = chunkVerticies.Count;
    5.  
    6.  
    7.                             chunkVerticies.Add(new Vector3(x,y+blockSize,z));
    8.                             chunkVerticies.Add(new Vector3(x,y+blockSize,z+blockSize));
    9.                             chunkVerticies.Add(new Vector3(x+blockSize,y+blockSize,z+blockSize));
    10.                             chunkVerticies.Add(new Vector3(x+blockSize,y+blockSize,z));
    11.                            
    12.                             UpdateChunkUV();
    13.                         }
    14.  
    [TABLE="width: 500, align: center"]
    [TR]
    [TD="align: center"]CheckSides(new RVector3(x,y,z),BlockFace.Top)[/TD]
    [TD="align: center"]CheckSides is a custom function I have written that I will describe in a second.

    The first argument simply sends the current for loop values through to the CheckSides function.

    As you can see, we are using our enum BlockFace as it looks much cleaner than 0,1,2,3,4,5 for side reference. As you can also see that there is one block of this code for each BlockFace Type.[/TD]
    [/TR]
    [TR]
    [TD="align: center"]VerticiesIndex[/TD]
    [TD="align: center"]VerticiesIndex in the beginning of the block ensures that the vertices count is current.[/TD]
    [/TR]
    [TR]
    [TD="align: center"]
    chunkVerticies * 4




    [/TD]
    [TD="align: center"]The addition to the vertices is not so easily explained, so please do tell me if I have done a shocking job of explaining.

    Why are there 4 of them?
    This is because in a square there are 4 vertices.

    GAH what is up with all the algebra!?
    Well, I am going to try explain it alongside my stunning drawing skills.

    As you can see, we are working in 3D space and the three axes x,y,z need to be thought of. In our case instead of +1 we are using +blockSize. If you compare the first chunkVerticies.Add it is related to the top left corner in the picture in which we then work around clockwise​.[/TD]
    [/TR]
    [TR]
    [TD="align: center"]UpdateChunkUV[/TD]
    [TD="align: center"]Another separate method that I will describe a little later.[/TD]
    [/TR]
    [/TABLE]

    Please do tell me if you understood that, if not please consult me for a private help session and I will try to make it much easier for you. You can now understand how all the other blocks of the same code work so I am not going to go in-depth with them.




    Now for a vital part that has been evident and possibly very confusing for the people reading it, it is CheckSides, I must warn you, this piece is also quite long also.

    Code (csharp):
    1.  
    2. public bool CheckSides(RVector3 blockPosition, BlockFace blockFace)
    3.     {
    4.         int x,y,z;
    5.         x = blockPosition.x;
    6.         y = blockPosition.y;
    7.         z = blockPosition.z;
    8.  
    9.  
    10.         switch(blockFace)
    11.         {
    12.        
    13.         case BlockFace.Top:    //Checks top face
    14.            
    15.             if(y+1<=chunkSize.y)
    16.                 {
    17.                 if(!chunkBlocks[x,y+1,z].empty)
    18.                 {return false;}
    19.                 }
    20.             break;
    21.            
    22.            
    23.         case BlockFace.Bottom:    //Checks bottom face
    24.            
    25.             if(y-1>=0  !chunkBlocks[x,y-1,z].empty)
    26.                 {return false;}
    27.             break;    
    28.        
    29.         case BlockFace.Right:    //Checks right face
    30.  
    31.  
    32.             if(x+1<=chunkSize.x)
    33.                 {
    34.                 if(!chunkBlocks[x+1,y,z].empty)
    35.                 {return false;}
    36.                 }
    37.             break;
    38.            
    39.            
    40.         case BlockFace.Left:    //Checks Left face
    41.            
    42.             if(x-1>=0)
    43.                 {
    44.                 if(!chunkBlocks[x-1,y,z].empty)
    45.                 {return false;}
    46.                 }
    47.             break;        
    48.            
    49.            
    50.         case BlockFace.Far:    //Checks Far face
    51.            
    52.             if(z+1<=chunkSize.z)
    53.                 {
    54.                 if(!chunkBlocks[x,y,z+1].empty)
    55.                 {return false;}
    56.                 }
    57.             break;
    58.            
    59.            
    60.         case BlockFace.Near:    //Checks Near face
    61.            
    62.             if(z-1>=0)
    63.                 {
    64.                 if(!chunkBlocks[x,y,z-1].empty)
    65.                 {return false;}
    66.                 }
    67.             break;    
    68.            
    69.         }
    70.     return true;
    71.     }
    72.  
    Ofcourse I am going to break it up and explain it, however it is following the same concept as the mesh vertices placement! Therefore I am going to explain one block and allow you to figure out the rest of them intuitively.

    Code (csharp):
    1.  
    2. public bool CheckSides(RVector3 blockPosition, BlockFace blockFace)
    3.     {
    4.         int x,y,z;
    5.         x = blockPosition.x;
    6.         y = blockPosition.y;
    7.         z = blockPosition.z;
    8.  
    9.  
    10.         switch(blockFace)
    11.         {
    12.        
    13.         case BlockFace.Top:    //Checks top face
    14.            
    15.             if(y+1<=chunkSize.y)
    16.                 {
    17.                 if(!chunkBlocks[x,y+1,z].empty)
    18.                 {return false;}
    19.                 }
    20.             break;
    21.  
    This method returns either true or false as to whether there is NOT a face present in the location checked. When the block face is found it will instantly return false.

    [TABLE="width: 500, align: center"]
    [TR]
    [TD="align: center"]x, y, z[/TD]
    [TD="align: center"]These are solely variables for ease of access, therfore instead of having to write blockPosition.x every time, I can instead just write x[/TD]
    [/TR]
    [TR]
    [TD="align: center"]if(y+1<=chunkSize.y)[/TD]
    [TD="align: center"]All this does is check if you are within the array when checking in order to avoid errors. We also want the outside face drawn if there is no face adjacent.[/TD]
    [/TR]
    [/TABLE]



    We are well and truly on the final stretch of this lesson, but if you want to give your brain a rest and stop it imploding be my guest or just want to take a breather. If not LET'S FINISH THIS!

    There are 2 methods we still need to look at, both fairly simple and self explanatory, UpdateChunkUV and FinalizeChunk

    Code (csharp):
    1.  
    2. void UpdateChunkUV()
    3.     {
    4.         chunkTriangles.Add(VerticiesIndex);
    5.         chunkTriangles.Add(VerticiesIndex+1);
    6.         chunkTriangles.Add(VerticiesIndex+2);
    7.        
    8.         chunkTriangles.Add(VerticiesIndex+2);
    9.         chunkTriangles.Add(VerticiesIndex+3);
    10.         chunkTriangles.Add(VerticiesIndex);
    11.        
    12.         chunkUV.Add(new Vector2(0,0));
    13.         chunkUV.Add(new Vector2(0,1));
    14.         chunkUV.Add(new Vector2(1,1));
    15.         chunkUV.Add(new Vector2(1,0));
    16.  
    17.     }
    18.  
    As this code is called every time a face has been detected, it then adds the found faces to the new mesh, as well as the new uv.

    [TABLE="width: 500, align: center"]
    [TR]
    [TD="align: center"]chunkTriangles[/TD]
    [TD="align: center"]There are 6 of these called as in a square there are 2 triangles, each with 3 vertices, thus 3*2=6[/TD]
    [/TR]
    [TR]
    [TD="align: center"]chunkUV[/TD]
    [TD="align: center"]Another awesome visual example Woo!

    As you can see, much like the last example we start in the bottom left and work our way clockwise, this basically adds the co-ordinates for the UV, or for those who do know what that means, the texture co-ordinates.[/TD]
    [/TR]
    [/TABLE]

    Final piece! Finalize Mesh!

    Code (csharp):
    1.  
    2. void FinalizeChunk()
    3.     {
    4.        
    5.         chunkMesh.vertices = chunkVerticies.ToArray();
    6.         chunkMesh.triangles = chunkTriangles.ToArray();
    7.         chunkMesh.uv = chunkUV.ToArray();
    8.         chunkMesh.RecalculateNormals();
    9.     }
    10.  
    How simple is this huh? Nice easy breezy ending isn't it!

    [TABLE="width: 500, align: center"]
    [TR]
    [TD="align: center"] chunkMesh.vertices = chunkVerticies.ToArray();
    chunkMesh.triangles = chunkTriangles.ToArray();
    chunkMesh.uv = chunkUV.ToArray();
    [/TD]
    [TD="align: center"]These operations simply set the chunks mesh to the new edited mesh, to the values generated through UpdateChunk()[/TD]
    [/TR]
    [TR]
    [TD="align: center"]RecalculateNormals[/TD]
    [TD="align: center"]This tells the mesh itself to update, if this is not called, then it would never update.[/TD]
    [/TR]
    [/TABLE]




    Now all you need to do is attach the chunk script to your chunk gameObject and assign it a texture of your choosing, and you are set. Also make sure you set the chunk size in the inspector to your liking of a value greater than 0 as otherwise.... nothing really will happen.

    Here is what it produced for me when put together. (Left) Normal Chunk (Right) Some Contraption
    $Chunk.JPG $WeirdChunk.JPG


    And thus concludes our first lesson, and golly it was a long one, but plenty more to come in future lessons. I think it is time for a good old fashioned bit of fun, you've earned it. So here are a few different things to try!
    • Add some if statements in the GenerateChunk() for loops that way you can make all different kinds of shapes. (see above)
    • Try changing the values of the chunk size to your liking.
    • Challenge : Try to make a circle generate! PM me if you get it and I will certainly pump out the next few lessons quickly

    Until next lesson, farewell! Hope you all enjoyed. Any problems please PM me.
    James/Myhi



    Block Class : http://pastebin.com/ifdSc9ef
    Chunk Class : http://pastebin.com/ELUv9dtG
     
    Last edited: Dec 21, 2013
  3. draulleo

    draulleo

    Joined:
    Aug 3, 2012
    Posts:
    27
    Looks like a cool tutorial!
    Looking forward for the rest!
     
  4. Omelet

    Omelet

    Joined:
    May 26, 2013
    Posts:
    1
    Looks like this is going to be a great tutorial, Myhijim and lanDog! Thanks for taking the time to teach the community about voxels, keep it up! Looking forward to more. :D
     
  5. mimminito

    mimminito

    Joined:
    Feb 10, 2010
    Posts:
    780
    Do you know when this will get updated? Looks like its going to be a great tutorial set, but the first one is only half finished :)
     
  6. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    Sorry about that, I have been updating as I have been writing it, I have exams in the next few weeks, so it may either be a slow 2 weeks or a quick 2 weeks depending how hard I study.

    On Friday, I am hoping to have the first lesson done.
     
  7. jordantotty

    jordantotty

    Joined:
    Dec 15, 2011
    Posts:
    77
    Thanks for this, it will help me alot! <3 Please dont forget to do it :p I am really looking forward to understanding perlin noise
     
  8. SubZeroGaming

    SubZeroGaming

    Joined:
    Mar 4, 2013
    Posts:
    1,008
    Excellent work, James!
     
  9. Asmayus

    Asmayus

    Joined:
    Jun 24, 2013
    Posts:
    2
    Same here, I'm about to launch into a minecraft clone after reading the "after playing minecraft" thread in it's entirety. Took me 4 days :0

    Looking forwards to the updates!
     
  10. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    Tomorrow guys/girls after my last exam :D

    New stuff be on it's way, been itching to write up new stuff.

    By the way, spread the word and advertise our games/studio and in future I may make a complete video tutorial series.

    Hang in there everyone, new content will be barreling out soon (tomorrow)!
     
  11. CazicThule

    CazicThule

    Joined:
    Nov 29, 2012
    Posts:
    95
    This looks like a cool tutorial, is it still in progress?
     
  12. Valtiel_

    Valtiel_

    Joined:
    Jul 27, 2013
    Posts:
    4
    Tomorrow hum ?
    Take your time to make a good tutorial but not publish if isn't finish. ^^
     
  13. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    I'm sorry if my Hard Drive failed and I lost everything. I'm sorry my internet is so slow it can't download Unity on my new Hard Drive. I'm sorry all of my progress on this tutorial series was wiped. I'm sorry I have to start again.

    Yes, it was supposed to be that day, but, as luck had it both of my computers crashed, unbelievable isn't it!

    I also said from the start that this would be gradual on a "When I get around to it basis" and right now, under the circumstances, I do not have the time. Take it as it is, it will be updated when it is updated. Hopefully soon. But first I have to bring back the motivation to restart.
     
  14. DrSega

    DrSega

    Joined:
    Mar 6, 2012
    Posts:
    157
    This looks interesting, dying to learn how to make voxel worlds, my main interest is how do you do procedurally generated worlds with chunks and keep the performance high?
     
  15. Asmayus

    Asmayus

    Joined:
    Jun 24, 2013
    Posts:
    2
    Sorry to hear that, hard drive failure is always a royal pain :(
     
  16. cod3r

    cod3r

    Joined:
    Sep 19, 2012
    Posts:
    91
    same thing happened to the dude that did this tutorial on youtube. Seems like making voxel worlds is just asking for computer to crash.

    Edit:

    also if you know anything about linux you might be able to recover some or all of what is on your hard drive. Just throw ubuntu CD in and mount the drive through the command prompt. I have saved several friends data this way.
     
  17. Valtiel_

    Valtiel_

    Joined:
    Jul 27, 2013
    Posts:
    4
    Sorry for you man :(
     
  18. Sashimi_2

    Sashimi_2

    Joined:
    Jun 26, 2013
    Posts:
    30
    come on man, I was happy to click that thread to get a nice read and than......
    keep it coming, the idea on making a voxel tutorial was pretty nice and you had a nice jumpstart before you blamed your hardisc :) (just some powder in the wound)

    seriously, keep it coming...... it will be a great experience for YOU and for us readers!
     
  19. Deadphyre

    Deadphyre

    Joined:
    Sep 26, 2013
    Posts:
    21
    I was really looking forward to a little more in the tutorial. Anyone know of any other tutorials out there? I understand some parts but need help with others and there isn't any decent tutorials out there that kind of walk you through the process of scripting a 3d with mesh and collision and creating the first terrain and only having visible vertices load. Something that could walk me through this part would be awesome. I always end up on the "After minecraft" thread and they only briefly go over some of that stuff but go onto Perlin Noise and such.
     
  20. Sashimi_2

    Sashimi_2

    Joined:
    Jun 26, 2013
    Posts:
    30
    Well not a specific Unity tutorial but worth a read.
    https://sites.google.com/site/letsmakeavoxelengine/home

    Cheers,
    Sash
     
  21. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    If anyone wants unofficial help please do contact me on Skype and I will get around to your questions when I can.

    I am still alive and kicking, but I am so swamped right now. This series may even have to wait for atleast another 5 weeks, but then again, if I get around to it in the following 2 weeks I will be updating it, but please do not hold me to it.

    After we complete the chunk rendering parts in part one, the next nine lessons will be a peice of cake for me to write up.

    As for resources, I have included a few in my main thread.

    Sorry guys, do bear with me, I promise it will come, just unsure when exactly :/

    Edit : The link Sash posted was VERY useful for me understanding the concepts, however it is not written in C#. It is linked in my main post already
     
    Last edited: Sep 29, 2013
  22. TheRaider

    TheRaider

    Joined:
    Dec 5, 2010
    Posts:
    2,185
    hope you do get to finishing this. I would love to see the javascript examples :)
     
  23. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    I'm currently developing my own new voxel game now, so it is driving me to restart this series, so far all the basic noise and atlas stuff is well underway and when I get the updation of the chunk meshes completed I will be stopping and updating this thread
     
  24. darkhog

    darkhog

    Joined:
    Dec 4, 2012
    Posts:
    2,219
    Thanks for the tutorial, it's great.

    For one of future projects I plan to make voxel-based level editor (like e.g. in GunCraft or Ace Of Spades), with no dynamic generation whatsoever. Which parts of tutorial can I skip given that I don't need world generation, only manipulation, texturing and rendering (+physics and collisons)?
     
  25. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    Uh, I'll have to write it all up before I can suggest the right approach, currently I am working on my own and want to get it to a fairly polished state before I continue with this.

    I used to love ace of spades before it got taken over by Jagex and became paid.
     
  26. Yoska

    Yoska

    Joined:
    Nov 14, 2012
    Posts:
    188
    This being a very hot topic and all if you pull this off I suspect the tutorial will end up being one of the most popular Unity tutorials around. Your first chapter was certainly a good start. Looking forward more.
     
  27. optic5471

    optic5471

    Joined:
    Dec 10, 2013
    Posts:
    6
    Is this still going to happen, it has been almost 2 moths now since the last comment, I love the idea for this and I think you could make a great tutorial series.

    I hope you can finish it or at least upload what you have and then quit or something. I just need somewhere that I can start, after that I can finish it, for the most part.
     
  28. mescalin

    mescalin

    Joined:
    Dec 19, 2012
    Posts:
    67
    this is not how i do it, i have one multidimensional array of ints, so it'd be in a script:

    public int[,,] GridArray

    one refresh function would go for all x, nested for all y, nested for all z etc, instantiate loads of blocks, according to what int is there, further optimisations could be a progressive refresh function that


    sorry just i feel your method is a bit complicated, i use a similar method for this old version of my 2.5D framework demo:

    https://googledrive.com/host/0BygN08G88ppXM3kySDZCUHpzdUk/WebTest.html


    you'd use so much memory, so i do not create a block class i notice a lot of people try to use "objects" on OO programming to encapsulate game objects which I don't think is how minecraft would have done it... maybe i am just oldschool though each to their own i guess
     
    Last edited: Dec 11, 2013
  29. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    Uhhh, if you are making loads of blocks, that would use much more memory, try creating 20,000 or so blocks procedurally at runtime. This method is for making games like minecraft, which, uses a similar method to this.

    As for the block class, yes that is true, it would be better to use an enum, however for the purposes of this tutorial and my own needs this works best. Minecraft follows a similar mesh generation system, however I do not think it uses a class for the block values.

    This method is quite complicated at first but once you do understand and can apply it, it is much more optimized and much more useful.

    Back on topic

    I'm back everyone! And new lessons are soon to come. Sorry for the huge wait but the past few months quite a bit has gone wrong so, everything is fixed now and well, hopefully I can stay back on track now the hardest part of the tutorial series is over!

    Thanks Myi
     
  30. stuart6854

    stuart6854

    Joined:
    Dec 16, 2012
    Posts:
    21
    Looking forward to following your tutorials, although i have already learnt to make chunks and render only visible faces and apply basic perlin noise.

    The main bit (got a while though) i will be waiting for is the tutorial on biomes.

    Can't wait :)
     
  31. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    Chapter 1 - Basic Principles
    Lesson #2

    Now that we have generated our chunk, well, it is frankly quite bland isn't it?

    So in this fairly short lesson, we are going to the very basic addition of perlin noise to the world to generate a basic "terrain".

    What is Perlin noise?
    Perlin noise is, as quoted by Wikipedia (Only the important stuff)

    So basically all perlin noise is in laymens terms is a set of values that gradually change to a lesser or greater value. It is NOT the texture itself, perlin noise just contains a value ranging from 0.0 to 1.0.

    When you generate perlin noise it looks as shown, in texture form:
    $128px-Perlin.png

    Now you can clearly see the gradual change in values when perlin noise is applied to a texture in terms of grey-scale.

    The 'Noise' Struct


    As we are simply using Unity's default Mathf.PerlinNoise function, there is not much to be done. All we have to do is generate an array of values that will return and smooth them to ints equivalent to the height.

    As can be noted we are not using a class for the noise, instead using a Struct. We have no desire to attach or read any of the values in the struct, so we are also going to make it not inherit from Monobehaviour.

    There is one simple method that we will look at, Generate.

    Code (csharp):
    1.  
    2. public struct Noise
    3. {
    4.     public static float[,] Generate(int xSize, int ySize, int seed, float intensity)
    5.     {
    6.         float[,] noise = new float[xSize,ySize];
    7.        
    8.         for(int x=0; x<xSize; x++)
    9.         {
    10.             for(int y=0; y<ySize; y++)
    11.             {
    12.             float xNoise = (float) x/xSize*intensity;
    13.             float yNoise = (float) y/ySize*intensity ;
    14.  
    15.                 noise[x,y] = Mathf.PerlinNoise(seed+xNoise,seed+yNoise)*intensity;
    16.             }
    17.         }
    18.         return noise;
    19.     }
    20. }
    21.  
    22.  
    23.  
    So let's break down the simple generate method, the arguments will become more understandable later on.

    Code (csharp):
    1.  
    2.     public static float[,] Generate(int xSize, int ySize, int seed, float intensity)
    3.  
    This method is static so that it may be called from anywhere as needed and returns an array of floats this doesn't need to be, it could be more modular, but for now, retuning an array of floats is all that is required.

    [TABLE="width: 500, align: center"]
    [TR]
    [TD="align: center"]xSize
    ySize
    [/TD]
    [TD="align: center"]The size of the array to generate, very self explanatory.[/TD]
    [/TR]
    [TR]
    [TD="align: center"]seed[/TD]
    [TD="align: center"]This is an interesting little piece of variable as Perlin Noise always produces the same results if passed the same parameters, therefore that would be boring and would really only give one result. All this seed does is give the ability to shift the perlin noise for a more "random" effect.[/TD]
    [/TR]
    [TR]
    [TD="align: center"]intensity[/TD]
    [TD="align: center"]Intensity has to do with the bumpiness of the terrain the lower it is the smoother the terrain and vica versa.[/TD]
    [/TR]
    [/TABLE]


    Code (csharp):
    1.  
    2.         float[,] noise = new float[xSize,ySize];
    3.        
    4.         for(int x=0; x<xSize; x++)
    5.         {
    6.             for(int y=0; y<ySize; y++)
    7.             {
    8.             float xNoise = (float) x/xSize;
    9.             float yNoise = (float) y/ySize;
    10.  
    11.                 noise[x,y] = Mathf.PerlinNoise(seed+xNoise,seed+yNoise)*intensity;
    12.             }
    13.         }
    14.         return noise;
    15.     }
    16. }
    17.  
    Now the rest of this script is very self explanatory

    [TABLE="width: 500, align: center"]
    [TR]
    [TD="align: center"]xNoise
    yNoise
    [/TD]
    [TD="align: center"]As shown these are both cast to floats. x y values are used in order to shift the value around otherwise we would end up with a very flat world. The x y values are divided by the size so that it will always give a number between 0.0 and 1.0[/TD]
    [/TR]
    [TR]
    [TD="align: center"]Mathf.PerlinNoise[/TD]
    [TD="align: center"]Mathf.PerlinNoise is the built in Perlin Noise function for unity and while simple, it does the job effectively. The arguments that Mathf.PerlinNoise takes are two values x and y which just determine the origin as to where the noise values will be taken from. Hence taken for example the argument seed+xNoise
    the noise is pushed across due to the seed.

    The noise is then multiplied by the intensity in order to determine the "bumpyness" of the chunk.[/TD]
    [/TR]
    [/TABLE]


    Additions to the 'Chunk' Class


    Now a few very small additions need to be made to the chunk class, a few new variables and one new if statement.

    New Awake Variables
    These variables should slot in between :
    Code (csharp):
    1.  
    2. public Chunk ThisChunk {get{return this;}}
    3.  
    4.     public int seed;
    5.     public int intensity;
    6.    
    7.     List<Vector3> chunkVerticies = new List<Vector3>();
    8.  
    9.  
    As you can see there are two new public variables which allow you to assign the intensity and the seed of the chunk.
    Simple eh?


    GenerateChunk Changes
    Now that we have the noise struct underway we need to actually implement it.

    In the GenerateChunk method we have to add a new variable right at the start :
    Code (csharp):
    1.  
    2.     public void GenerateChunk()
    3.     {
    4.         float[,] chunkHeights = Noise.Generate(chunkSize.x+1,chunkSize.y+1,seed,intensity);
    Here we implement our Noise.Generate method taking the actual chunksize due to the +1 and our inspector defined variables seed and intensity.

    Code (csharp):
    1.             {for(int y=0; y<=chunkSize.y ; y++)
    2.                 {
    3.                     chunkBlocks[x,y,z] = new Block(true);
    4.  
    5.  
    6.                     if(y<=chunkHeights[x,z])
    7.                     {chunkBlocks[x,y,z] = new Block(false);}
    8.                         ..............
    9.  
    As can be noted the original initialization of the blocks was to make them all solid by setting the one argument, empty, of the new Block to false, however now we change that and make it all empty, generating the chunk from the returned noise values.

    Here we implement a new if statement that basically just checks the current y value against the noise generated heights and if it is lower then it will PLACE a block in that space.




    And that concludes the short second lesson of this chapter! Congratz on making it this far, the terrain is now starting to look like terrain. Here is what I got from mine with a few values :

    Seed :
    123 Intensity : 8​


    Here is a few things to try :

    • Alter the seed and intensity values to your heart's content
      • Also the chunk sizes values alongside this may produce some interesting results
    • Generate noise on a 3D level and apply it (Making weird shapes as terrain will be mirrored)
    • Try using a different form of noise (eg. Simplex)

    Hope to see you all for the next lesson! As always, any problems, PM me!

    Thanks
    Myhijim/James




    Noise Class : http://pastebin.com/gBf9MA7p
     
    Last edited: Dec 22, 2013
  32. landon912

    landon912

    Joined:
    Nov 8, 2011
    Posts:
    1,573
    Very nice James! PS: If you guys want this tutorial to continue Speak Up! It's hard work doing this so throw some compliments at him if you enjoyed :p
     
  33. mathias234

    mathias234

    Joined:
    Sep 9, 2012
    Posts:
    239
    Thank you this helps alot !!!
     
  34. dterbeest

    dterbeest

    Joined:
    Mar 23, 2012
    Posts:
    385
    Subscribed, looks very interesting!
     
  35. Jagwire

    Jagwire

    Joined:
    Dec 5, 2013
    Posts:
    59
    Great tutorial so far!

    Would love it if various generation algorithms were eventually discussed such as dual contouring, dual marching cubes, SurfaceNets and so forth...
     
  36. S0rn0

    S0rn0

    Joined:
    Feb 25, 2013
    Posts:
    1
    I would love to see dual contouring covered as Jagwire said.
     
  37. amindler

    amindler

    Joined:
    May 21, 2010
    Posts:
    10
    Wow! Great job breaking down the code and each element. I've created tutorials before myself and have to say the amount of effort it took to create this is quite a thing! I for one really appreciate it! Can't wait for the next part, texture atlasing!
     
  38. TheRaider

    TheRaider

    Joined:
    Dec 5, 2010
    Posts:
    2,185
    Would love to see you increase the pace :)
     
  39. Valtiel_

    Valtiel_

    Joined:
    Jul 27, 2013
    Posts:
    4
    Your tutorial is good and you explained very well ^^. When is the next ? :3 Sorry it must be a lot of work but I look forward the next chapter.
     
  40. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    Chapter 1 - Basic Principles

    Lesson #3

    *Quick Note*
    I am so sorry this has taken me this long, seems I am full of excuses and frankly from you guy's perspective you must think I am full of s#%^, and you have a right to believe that so I'm just going to continue with my series and try to get as much done as possible while my motivation is at a high
    This will be a VERY picture based tutorial.



    What is a Texture Atlas?
    For all of you who do not know, a texture atlas is a collection of textures within one image file, as done in minecraft. Below is an example of a small texture atlas I created for the purposed of this tutorial :



    And throughout the tutorial I aim to help you create a simple, but nice looking texture atlas containing four textures.

    You may use whatever paint editing program you wish, however I prefer to use Paint.net due to it's simplicity and i's effects that make my terrible artwork look slightly LESS terrible

    Here is Pain.net website/download : http://www.getpaint.net/download.html



    Step by Step

    Now this may not be everyone's idea of a good texture atlas, therefore one thing I MUST URGE is try out your own tastes, and your own mixtures of effects.
    Getting Started
    Open Paint.net and make a new image with the dimensions 64 by 64 pixels. Zoom in a fair way and turn on the grid.




    Select an Area

    Select the area for the first texture by using the select tool in order to cover a 32x32 area.



    Add some Noise
    Fill the area you have just selected with a colour, I chose green for a grass texture. Now press the Effect menu, then Noise then Add Noise and adjust till you get a nice noisy pattern suitable for pixelated grass



    Repeat to Create Different Textures






    Once again I am sorry for such a long wait guys, I have been feeling guilty for the past few *However long it has been* and it is only recently I have gotten my stuff together . And this lesson may have not been very big, however it is necessary for our progress onto the next BIG lesson, Different Block Types.

    Thanks again guys.
    If you think I am being lazy, please come and harrass me at my blog : http://programmingwithclass.blogspot.com.au/

    Thanks again guys! Seen you again VERY soon.
    Myhi/Jimmy
     
  41. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    Chapter 1 - Basic Principles

    Lesson #4

    So, you have gotten this far, congratulations! In this lesson we are going to look at implementing our previous lesson's texture atlas so that we can have a variety of different block "types".



    Importing our Texture Atlas

    First it is necessary to import our texture atlas and apply it as our chunk texture.

    (If you generate it now, it sure would look funky :p )




    Some new Code

    Now before we get into any new and exciting stuff we need to look back on our code and make a few alterations in order to progress further.



    The Block Class

    A tiny alteration to the Block Class is necessary as we need some sort of indicator as to what type of block is in the chunk. Therefore logically, we will use a byte as we really do not need many different block types as of right now.

    Thus we need to edit the class to :

    Code (csharp):
    1.  
    2. public class Block
    3.    
    4. {
    5.    
    6.     public bool empty = false;
    7.    
    8.     public byte id = 0;
    9.  
    As you can clearly see, the only added line here is the id byte which, as stated before, will be used to keep track of what type of block is in this position of the chunk.




    The Chunk Class

    New Variables

    Now there are a few vitally important variables that do need to be set up, so put these above the Awake() method and I will explain them :

    Code (csharp):
    1.  
    2.         public Vector2 textureBlockSize;
    3.     Texture textureAtlas;
    4.     Vector2 atlasSize;
    5.  
    Ok so obviously you see a few familiar terms here :

    [TABLE="width: 500, align: center"]
    [TR]
    [TD="align: center"]textureBlockSize[/TD]
    [TD="align: center"]This variable is very basic and is set in the inspector, all you set it to is the size (in pixels) of each 'block' in your texture atlas.[/TD]
    [/TR]
    [TR]
    [TD="align: center"]textureAtlas


    atlasSize
    [/TD]
    [TD="align: center"]This Texture is simply for caching purposes of the renderer's material texture.

    The atlas size is also for caching purposes and store, you guessed it, the amount of 'blocks' in the texture.[/TD]
    [/TR]
    [/TABLE]

    Now onto the Awake method :


    The Awake method

    In this function we have a few VERY basic additions at the start of the function :
    Code (csharp):
    1.  
    2.         textureAtlas = transform.GetComponent<MeshRenderer> ().material.mainTexture;
    3.         atlasSize = new Vector2(textureAtlas.width/textureBlockSize.x,textureAtlas.height/textureBlockSize.y);
    4.  
    Now honestly, this probably looks like more of a mouthful than it is, so yet again, let me explain :

    [TABLE="width: 500, align: center"]
    [TR]
    [TD="align: center"]textureAtlas =[/TD]
    [TD="align: center"]Really simple stuff here, all this is doing is caching the texture (our texture atlas) which is attached to the material of our chunk.[/TD]
    [/TR]
    [TR]
    [TD="align: center"]atlasSize=[/TD]
    [TD="align: center"]This declaration just finds out how many 'blocks' are in the atlas so we have an easy reference for it later.[/TD]
    [/TR]
    [/TABLE]



    The GenerateChunk method


    To make our world less dull, we are going to randomize the blocks that spawn. This is a temporary step and will be removed in the next lesson.

    Now, in this method we are going to add only 1 line. Therefore find :

    Code (csharp):
    1.  
    2.                 {
    3.                     chunkBlocks[x,y,z] = new Block(true);
    4.  
    5.  
    6.                     if(y<=chunkHeights[x,z])
    7.                     {chunkBlocks[x,y,z] = new Block(false);}
    8.                 }  
    9.  
    And make it :

    Code (csharp):
    1.  
    2.         {
    3.             chunkBlocks[x,y,z] = new Block(true);
    4.  
    5.  
    6.             if(y<=chunkHeights[x,z])
    7.                 {
    8.                     chunkBlocks[x,y,z] = new Block(false);
    9.                     chunkBlocks[x,y,z].id = (byte)Random.Range(0,3);
    10.                 }
    11.         }      
    12.  
    [TABLE="width: 500, align: center"]
    [TR]
    [TD="align: center"](byte)Random.Range(0,3);[/TD]
    [TD="align: center"]In this we are simply setting the ID of the block to a random value between 0 and 3.[/TD]
    [/TR]
    [/TABLE]

    Now if you are wondering "What exactly is this doing" well I am going to try my best to explain it. We are setting each block's ID to a random number. Now if you look at our texture atlas in terms of top left to bottom right, think of the ID as so :

    Where 0 is grass, 1 is dirt, 2 is stone etc...

    Super simple right?

    Well that part may be, but the next part is hard so hold onto your hats folks


    Editing the UpdateChunkUV Method

    While this is still technically editing the Chunk class, it is so big that it is necessary for it to have it's own segment. Firstly, instead of just giving you the code, I would like you to understand the basic concept of UV Mapping (which I myself had/have a lot of trouble with also).


    Understanding UV Mapping

    I am going to start this part of the tutorial visually as In my opinion it is the easiest way to get a grasp of UV mapping.

    [TABLE="width: 500, align: center"]
    [TR]
    [TD="align: center"]Consider our texture[/TD]
    [TD="align: center"]Now consider our texture in terms of x and y[/TD]
    [/TR]
    [TR]
    [TD="align: center"]


    [/TD]
    [TD="align: center"][/TD]
    [/TR]
    [/TABLE]

    In terms of UV mapping, this is how the texture is measured :
    :​

    As you can clearly see in terms of UV Mapping the measurements start from the bottom left corner as opposed to top left. Now, if you were to number the intervals with this knowledge on OUR texture, I would assume that most of you would label it like so :
    However, I would then request you to look back at this and realize that in terms of UV mapping, the values always stay the same in the corners :

    So therefore our atlas if we labelled where each corner of each block was, it would look like so :
    AND if we then expanded our texture atlas to three by three the "intervals" between each block would be 0.3333 (1/3), if four by four it would be 0.25(1/4), and so on.

    Now that we have covered that mindblowing stuff (you may want to read over it a few more times of PM me if you are not confident) the next part should be a piece of cake!



    Changes to UpdateChunkUV Method

    While we have covered the randomization of the ID, the measuring of the texture and so forth, we have not really implemented it

    Firstly we need to give the method a new parameter :
    Code (csharp):
    1.  
    2.     void UpdateChunkUV(byte blockID)
    3.     {
    4.  
    And it will now give you errors to that "UpdateChunkUV does not take 0 parameters" so it is a good idea to now scroll through the UpdateChunk() Method and replace all :
    Code (csharp):
    1.  
    2. UpdateChunkUV();    
    3.  
    With :
    Code (csharp):
    1.  
    2. UpdateChunkUV(chunkBlocks[x,y,z].id);    
    3.  


    Now that that new parameter is in place, we must actually edit the UVs. And to do so, we now need to replace in UpdateChunkUV method, this :

    1. chunkUV.Add(new Vector2(0,0));
    2. chunkUV.Add(new Vector2(0,1));
    3. chunkUV.Add(new Vector2(1,1));
    4. chunkUV.Add(new Vector2(1,0));

    With this :
    Code (csharp):
    1.  
    2.         Vector2 textureInterval = new Vector2 (1/atlasSize.x, 1/atlasSize.y);
    3.  
    4.  
    5.         Vector2 textureID = new Vector2(textureInterval.x*(blockID%atlasSize.x),textureInterval.y*Mathf.FloorToInt(blockID/atlasSize.y));
    6.  
    7.  
    8.         chunkUV.Add(new Vector2(textureID.x,textureID.y-textureInterval.y));
    9.         chunkUV.Add(new Vector2(textureID.x+textureInterval.x,textureID.y-textureInterval.y));
    10.         chunkUV.Add(new Vector2(textureID.x+textureInterval.x,textureID.y));
    11.         chunkUV.Add(new Vector2(textureID.x,textureID.y));
    12.  
    This in it's entirety took me about 3 hours to work out and apply, so don't worry if you didn't understand at first glance, as as always, I am going to explain it thoroughly.
    [TABLE="width: 500, align: center"]
    [TR]
    [TD="align: center"]textureInterval
    [/TD]
    [TD="align: center"]As we talked about before, with each corner of each 'block' in our atlas, this is the gap between each corner, so in this instance as our texture atlas is 2x2 we revieve the gap of 0.5 as show in this from before :


    [/TD]
    [/TR]
    [TR]
    [TD="align: center"]textureID [/TD]
    [TD="align: center"]textureID is where it starts to get quite confusing for some with the % operator. But DO NOT FEAR.

    textureInterval.x*(blockID%atlasSize.x)
    What this little gem does is give the correct x value in terms of our atlas. Due to it not being a single row of texture it creates a challenging problem as to when you reset the x value in order to count from the beginning again. The % operator gives the remainder of blockID/atlasSize.x so that when we have a blockID of 2 or greater, the counting jumps back to x=0 but on the next Y layer.

    textureInterval.y*Mathf.FloorToInt(blockID/atlasSize.y)

    This is very similar to the first part of this Vector2, however we floor the value, so that if the blockID is 1 for example, usually it would push us down on the y-axis 0.5 which we do not want as the blockID 1 (Dirt) is still on the top layer. If it was not floored, then the texture replacing dirt would be the stone. [/TD]
    [/TR]
    [TR]
    [TD="align: center"]chunkUV.Add[/TD]
    [TD="align: center"]This part may seem complex, but really it is much similar to before, giving the UV the positions in which the corners are placed. Try to piece it together in your head as it is so very hard to explain. Take the picture we have been using and substitute the blockID into the equation and see the results.
    [/TD]
    [/TR]
    [/TABLE]






    Phew!

    It's great that all of you have come this far! And after that intense lesson, it only gets better as the next lesson is much more down to earth, with little to no problems of playing with vertices or UV's.

    Now generate your chunk a few times with different intensities and you come up with some nice results!



    Thanks Again!

    Hope to see you next time guys!
    Myhi/Jimmy

    PPS
    Please feel free to visit my blog!
    http://programmingwithclass.blogspot.com.au/
     
    Last edited: Mar 6, 2014
    xxZap likes this.
  42. landon912

    landon912

    Joined:
    Nov 8, 2011
    Posts:
    1,573
  43. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    *Post of confidence that the next lesson is due this weekend/tomorrow if I can finish it quickly* LIES*
     
    Last edited: May 17, 2014
    ThiagoDziedzic likes this.
  44. mathias234

    mathias234

    Joined:
    Sep 9, 2012
    Posts:
    239
  45. ThiagoDziedzic

    ThiagoDziedzic

    Joined:
    Feb 17, 2015
    Posts:
    1
    Please come back!!!!! =(
     
  46. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    Hey, been a while, please do add me on Skype if you want to discuss with me further :)

    I haven't disappeared from dev, just disappeared currently from the forums. If you contact me now, I will find time to help you out as much as possible, probably moreso than these old lessons as I have progressed significantly.

    Thanks
    James
     
  47. Flickayy

    Flickayy

    Joined:
    Jan 20, 2013
    Posts:
    40
    HeyMyhijim,

    Any updates to this? Looking forward to the next chapter.
     
  48. darkhog

    darkhog

    Joined:
    Dec 4, 2012
    Posts:
    2,219
    I'd particularly like someone to write how to manage cellural automata in "infinite" world. Like spreading fire/water, redstone-like things, etc.
     
  49. Myhijim

    Myhijim

    Joined:
    Jun 15, 2012
    Posts:
    1,148
    @darkhog
    One approach in order minimize the impact on the CPU would be to have jagged world 'ticks' based off the current CPU load. Delayed updates etc.

    In an infinite world, managing all of the data, especially on a huge scale gets more and more complex and can be done with even more delayed ticks, but at some point (Very very large amounts of data stored) it becomes more effort than it is worth, even minecraft does not manage every single chunk's automata, it does cap at a X amount of chunks from the player.

    If you'd like to theorize, please do message me, because I'm sure it would be an awesome discussion :)

    @Flickayy
    I have been meaning to come back and completely redo this tutorial, inclusive of Marching Cubes and much more professional code. When I have free time, I will probably retake this up as a bit of fun! And the updates would come much quicker, but the concepts will probably be more indepth due to me gaining knowledge.


    Thanks
    Myhi
     
  50. Flickayy

    Flickayy

    Joined:
    Jan 20, 2013
    Posts:
    40
    @darkhog
    I've been reading something that might be of use, Cellular Automata for Physical Modelling

    @Myhi
    Well, what you've already written is great for people like me who don't know where to start, it wouldn't need a redo :3
    I eagerly await the return!
     
unityunity