Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

After reading After playing minecraft... (A lurker's journey)

Discussion in 'Scripting' started by Ishkur, Jul 23, 2013.

  1. Ishkur

    Ishkur

    Joined:
    Nov 18, 2011
    Posts:
    26
    Hello, world. I'm a beginner at scripting. I want to make a simple voxel-based terrain here. Yes, I've read jc_lvngstn's thread After playing minecraft... , which is an excellent source for the theory behind making voxel terrains!

    The problem is that a beginner attempting to follow along will only learn theory, and the code in examples of say, MinePackage, CubicWorld version 3-6, stacker, and other Unity Packages are too complicated and sparsely commented to dissect and learn from unless you already know what you're doing.

    And I'm sure I'm not the only noob here interested in voxel terrain with only passion and close to no skill/knowledge.

    So here in this thread you see what the process looks like of a beginner trying to make sense of a voxel system.

    Goals:

    Create a face procedually from script.
    Create a cube proceduralaskdslly from script
    Create a plane of cubes procedurally from script.
    Organize cube generation into chunks
    Texture a face
    Texture a cube
    Texture cubesss
    Now do the last three but use an ATLAS MAP
    Collisions
    Do the culling thing
    Lights

    I think that should cover the absolute essentials needed in a voxel terrain. If I manage to accomplish these things, I will continue.

    I will really really appreciate any help from those better educated, and I hope documenting my efforts will help others less experienced! Do keep in mind that if you follow my methods, this will be the blind leading the blind.
     
    Last edited: Jul 24, 2013
  2. Ishkur

    Ishkur

    Joined:
    Nov 18, 2011
    Posts:
    26
    Alrighty, so in this step I will follow this tutorial here.

    1. I set up an empty Unity project.
    2. I create a new C# script
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class old : MonoBehaviour
    5. {
    6.     void Start ()
    7.     {
    8.         CreateFace(); //On start, we run the method CreateFace
    9.     }
    10.     void CreateFace()
    11.     {
    12.         //Here, we make a new mesh Object, called "mesh".
    13.         Mesh mesh = new Mesh();
    14.        
    15.         //We create an array for 4 Vector3's called verts. Each element in this array will
    16.         //represent the location of one of our 4 vertices that will make up our square face
    17.         Vector3[] verts = new Vector3[4];
    18.        
    19.         //Uh... the guy in the video told me this is for declaring the UV's coordinate vertices...
    20.         //...I'm not ready to apply textures yet so I'm just gonna leave it in...
    21.         Vector2[] uvs = new Vector2[4];
    22.        
    23.         //So this here's an array of integers named tris. It will contain 6 elements in a certain order
    24.         //which represent the vertices defined in our array verts. This array will be used to create our
    25.         //2 triangles which make up our face.
    26.        
    27.         //According to the video in the post, the guy says that the order of the verts listed in tris
    28.         //is important because the way the vertices are "wound" around the triangle determines the way
    29.         //the triangle's normal faces. I suppose that's important for when we apply a texture to the face
    30.         //so that the texture faces outward
    31.         int[] tris = new int[6] {0, 1, 2, 2, 1, 3};
    32.        
    33.         //Here we are defining the individual Vector3's in our verts array of each individual element.
    34.         //Like I said, each element in the vert array is a Vector3, which represents the location
    35.         //of the vertex... I think in world space, not local. Can someone clear that up?
    36.        
    37.         // 0--1
    38.         // |  |
    39.         // 2--3
    40.        
    41.         //That up there is the representation of the front face we're making. So imagine point (0,0,0)
    42.         //is the center of the face in world space with +y on the upper edge, +x on the right edge, and
    43.         //+z going INTO the page.
    44.  
    45.                 //The reason we multiple each offset by 0.5f is because I want the cube to be 1 unit on each
    46.                 //side. The reason for the 'f' and not just play '0.5' is because Vector3's can only be operated
    47.                 //on by float values.
    48.        
    49.         verts[0] = (-Vector3.right * 0.5f) + (Vector3.up * 0.5f) - (Vector3.forward * 0.5f);
    50.         verts[1] = (Vector3.right * 0.5f) + (Vector3.up * 0.5f) - (Vector3.forward * 0.5f);
    51.         verts[2] = (-Vector3.right * 0.5f) - (Vector3.up * 0.5f) - (Vector3.forward * 0.5f);
    52.         verts[3] = (Vector3.right * 0.5f) - (Vector3.up * 0.5f) - (Vector3.forward * 0.5f);
    53.        
    54.         //We set our mesh Object's vertices to our vertex array
    55.         mesh.vertices = verts;
    56.        
    57.         //We tell mesh how to build the triangles from those vertices by passing it our tris array
    58.         mesh.triangles = tris;
    59.        
    60.         //Some sort of magic regarding textures and uvs
    61.         mesh.uv = uvs;
    62.        
    63.         //I don't even know. Doesn't the way we wind the triangles already guarantee that our normals
    64.         //are facing the right way?
    65.         mesh.RecalculateNormals();
    66.        
    67.         //Make a new gameobject to hold our mesh
    68.         GameObject newMeshObject = new GameObject();
    69.        
    70.         //Add a mesh filter. Not sure what that is, but its important
    71.         newMeshObject.AddComponent<MeshFilter>().mesh = mesh;
    72.        
    73.         //Add mesh renderer so we can actually see our mesh
    74.         newMeshObject.AddComponent<MeshRenderer>();
    75.        
    76.         //Give yourself a big old smile we just learned how to procedurally make a face.
    77.     }
    78. }
    79.  
    80.  
    Please, please point me out on anything I'm understanding wrong.
     
    Last edited: Jul 23, 2013
  3. Ishkur

    Ishkur

    Joined:
    Nov 18, 2011
    Posts:
    26
    Ok, now we have a face that is 1 unit by one unit. So I guess the next logical thing to do is to make a cube... by making 6 faces, right?

    I'm going to make a new method called CreateCube, which will call CreateFace 6 times, passing a parameter to CreateFace so that it will know which face to build.

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class old : MonoBehaviour
    5. {
    6.     void Start ()
    7.     {
    8.         CreateBlock(); //On start, we run the method CreateBlock
    9.     }
    10.    
    11.     void CreateBlock()
    12.     {
    13.         //The CreateBlock method will then run CreateFace six times, each time
    14.         //passing an int parameter to CreateFace so it will make each face of the
    15.         //block
    16.        
    17.         CreateFace(0);
    18.         CreateFace(1);
    19.         CreateFace(2);
    20.         CreateFace(3);
    21.         CreateFace(4);
    22.         CreateFace(5);
    23.     }
    24.     void CreateFace(int face) //You see now CreateFace now takes an int parameter, which it will now know as 'face'
    25.     {
    26.         //We use a switch-case statement for it to determine how to handle each case.
    27.         //If CreateFace gets a 0, it will execute script suited for making the front face of the block
    28.         //I've included code for making the front '0', and back '1' face cases.
    29.         //From here, you should be able to make the remaining 4 sides by modifying the verts
    30.         //Vector3's
    31.        
    32.         switch(face)
    33.         {
    34.             case 0:
    35.                 verts[0] = (Vector3.right * 0.5f) + (Vector3.up * 0.5f) - (Vector3.forward * 0.5f);
    36.                 verts[1] = (Vector3.right * 0.5f) + (Vector3.up * 0.5f) - (Vector3.forward * 0.5f);
    37.                 verts[2] = (Vector3.right * 0.5f) - (Vector3.up * 0.5f) - (Vector3.forward * 0.5f);
    38.                 verts[3] = (Vector3.right * 0.5f) - (Vector3.up * 0.5f) - (Vector3.forward * 0.5f);
    39.                
    40.                 uvs[0] = new Vector2(0.0f, 1.0f);
    41.                 uvs[1] = new Vector2(1.0f, 1.0f);
    42.                 uvs[2] = new Vector2(0.0f, 0.0f);
    43.                 uvs[3] = new Vector2(1.0f, 0.0f);
    44.                
    45.                 mesh.vertices = verts;
    46.                 mesh.triangles = tris;
    47.                 mesh.uv = uvs;
    48.                
    49.                 mesh.RecalculateNormals();
    50.                
    51.                 break;
    52.            
    53.             case 1:
    54.                 verts[0] = (Vector3.right * 0.5f) + (Vector3.up * 0.5f) + (Vector3.forward * 0.5f);
    55.                 verts[1] = (Vector3.right * 0.5f) + (Vector3.up * 0.5f) + (Vector3.forward * 0.5f);
    56.                 verts[2] = (Vector3.right * 0.5f) - (Vector3.up * 0.5f) + (Vector3.forward * 0.5f);
    57.                 verts[3] = (Vector3.right * 0.5f) - (Vector3.up * 0.5f) + (Vector3.forward * 0.5f);
    58.                
    59.                 uvs[0] = new Vector2(0.0f, 1.0f);
    60.                 uvs[1] = new Vector2(1.0f, 1.0f);
    61.                 uvs[2] = new Vector2(0.0f, 0.0f);
    62.                 uvs[3] = new Vector2(1.0f, 0.0f);
    63.                
    64.                 mesh.vertices = verts;
    65.                 mesh.triangles = tris;
    66.                 mesh.uv = uvs;
    67.                
    68.                 mesh.RecalculateNormals();
    69.                
    70.                 break;
    71.            
    72.             GameObject newMeshObject = new GameObject();
    73.             newMeshObject.AddComponent<MeshFilter>().mesh = mesh;
    74.             newMeshObject.AddComponent<MeshRenderer>();
    75.            
    76.             //Give yourself a big old smile we just learned how to make a block
    77.     }
    78. }
    79.  
    As it stands, your project should have nothing in it except for 1 script attached to your Main Camera or an empty GameObject.
     
  4. Ishkur

    Ishkur

    Joined:
    Nov 18, 2011
    Posts:
    26
    Okay. As we stray further and further away from the last sighting of a step-by-step tutorial (when we first started), I become less and less sure of how to proceed.

    I will now attempt to create a flat plane of 16 by 16 cubes of 1 cube high.

    Since, from what I gathered from poking around the forums, everybody uses "an array of block references" to know how to build their cubes, I will attempt to use this somewhat vaguely described (to a beginner) method in order to make this plane :D

    By array, I assume they mean a Multidimensional array. After an hour or so of reading about what an array is on MSDN, my best guess is you should use a 3d array.

    I initialize my array in my script and wish it good luck

    Code (csharp):
    1. public static int[, ,] blockArray = new int[16, 16, 2];
    An array of ints, right? They mentioned something about each block reference taking up only 1 or 2 bytes. Dunno what a byte is. But this array is an array of ints. Maybe an int equals a byte? This should be fine, right?

    So the array is 16 by 16 by 2 high... x, y, and z... But isn't the z axis the forward axis in Unity? That would mean I made a wall 2 units thick then? I change my code:

    Code (csharp):
    1. public static int[, ,] blockArray = new int[16, 2, 16];
    That's silly though. In Dwarf Fortress, they call each floor a "z-level". Shouldn't the Z axis be up-down?

    A quick check using Debug.Log of the newly initiated array we named 'blockArray' show all elements of blockArray to be 0. From various readings it seems to be the norm for air to be represented by a block type of 0.

    We want to make a plane of solid blocks, though, so we'll change these elements to 1, to represent our generic solid block.

    At Start(), we call SetBlockArray() to set the elements in BlockArray to solid block.

    Code (csharp):
    1. //Initialize an int array, each int representing a type of block
    2.    
    3.     //Notice that we make an array of 16 elements by 2 elements by 16 elements
    4.     //When we access our array, be aware that the array is 0 indexed, which means
    5.     //values will exist starting at 0 to 15 for x, 0-1 for y, and 0-15 again for z
    6.     public static int[, ,] BlockArray = new int[16, 2, 16];
    7.    
    8.     void Start ()
    9.     {
    10.         SetBlockArray();
    11.     }
    Now SetBlockArray sets our air block solid. We only want our bottom layer of air blocks to be solid while the upper layer remains as air. 'For' loops

    Code (csharp):
    1. void SetBlockArray()
    2.     {
    3.         //Where BlockArray is BlockArray[i, j, k], for every j value inside array bounds
    4.         //0 - 15 of every i value inside array bounds 0 - 15 where k equals 0 (k = 0 is our
    5.         //bottom level of blocks), we set these elements equal to 1 (solid).
    6.        
    7.         for (int i = 0; i <= 15; i++)
    8.         {
    9.             for(int j = 0; j <= 15; j++)
    10.             {
    11.                 BlockArray[i, j, 0] = 1;
    12.             }
    13.         }
    14.         blockarrayIsReady = true;
    15.     }
    Now iterate through each element of BlockArray, calling CreateBlock every time the element of BlockArray equals 1, and not creating a block where the element equals 0.

    Code (csharp):
    1. public void BuildBlockArray()
    2.     {
    3.         //Iterate through each element of BlockArray. If the element is equal to 1,
    4.         //call CreateBlock.
    5.         for (int i = 0; i <= 15; i++)
    6.         {
    7.             for (int j = 0; j <= 1; j++)
    8.             {
    9.                 for (int k = 0; k <= 15; k++)
    10.                 {
    11.                     if (BlockArray[i, j, k] == 1)
    12.                     {
    13.                         CreateBlock();
    14.                     }
    15.                 }
    16.             }
    17.         }
    18.         blocksCreated = true;
    19.     }
    Yeah... that's not gonna work. We'll just end up making 16 blocks on top of each other. Now we need to be able to specify the location of each block being created. We'll do this by referencing the location of the element that called for the block's creation. Modify BuildBlockArray() like so:

    Code (csharp):
    1. public void BuildChunk()
    2.     {
    3.         for (int i = 0; i <= 15; i++)
    4.         {
    5.             for (int j = 0; j <= 1; j++)
    6.             {
    7.                 for (int k = 0; k <= 15; k++)
    8.                 {
    9.                     if (chunk[i, j, k] == 1)
    10.                     {
    11.                         CreateBlock(i, j, k);
    12.                     }
    13.                 }
    14.             }
    15.         }
    16.     }
    This will now pass the location of the BlockArray element as int parameters to the CreateBlock method. CreateBlock then uses these ints to contruct the Vector3 representing the location of the block, blockCoords, and passes this position to CreateFace, called 6 times to create the block.

    Code (csharp):
    1. void CreateBlock(int i, int j, int k)
    2.     {
    3.         Vector3 blockCoords = new Vector3();
    4.         blockCoords.x = i;
    5.         blockCoords.y = j;
    6.         blockCoords.z = k;
    7.        
    8.         CreateFace(blockCoords, 0);
    9.         CreateFace(blockCoords, 1);
    10.         CreateFace(blockCoords, 2);
    11.         CreateFace(blockCoords, 3);
    12.         CreateFace(blockCoords, 4);
    13.         CreateFace(blockCoords, 5);
    14.     }
    Now CreateFace() should take two parameters, a Vector3 representing location, and an int specifying which face to create. Just add your blockCoords Vector3 to each vertex-creating call:

    Code (csharp):
    1. verts[0] = blockCoords - (Vector3.right * 0.5f) + (Vector3.up * 0.5f) - (Vector3.forward * 0.5f);
    Now we're almost ready to test. In our Start() function, call SetBlockArray() to set our layer of air blocks in our BlockArray elements to 1.

    In our Update() function:

    Code (csharp):
    1. void Update ()
    2.     {
    3.         if(blockarrayIsReady  !blocksCreated)
    4.         {
    5.             BuildChunk();
    6.         }
    7.     }
    These bool checks make sure we start reading and building from our array only after we've completed setting what elements we want to 1 and to stop building from the array after we've finished building it one time.

    Give yourself a big old smile, we just made a plane out of cubes :D