Unity Voxel Tutorial Part 6: 3d Voxels


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


You may have noticed how I cleverly named the last part 3d voxel and this one 3d voxels. That it because in this part we'll be making our chunk show an actual chunk of blocks. So picking up from last tile where we had one block rendering what we'll do now is make a way to store the information of the chunk.

This is an interesting point because there are two ways to do this, you can store the information in each chunk so that every chunk has the data for the blocks it contains or you can use a big array for all the level data that each chunk refers to. I think we'll use the big array because it's easier later on. This will mean that our world has a fixed size though but making an infinitely generating world will have to remain outside of the scope of this tutorial.

So to store our level data make a new script and call it World .Also create a gameobject and call it world as well and put the script on it. The core of this script will be the data array, start by setting up the variables:
public byte[,,] data;
public int worldX=16;
public int worldY=16;
public int worldZ=16;

We'll initiate the byte array with the world* variables as the sizes of the array. Use the start array for this:
void Start () {

 data = new byte[worldX,worldY,worldZ];
 
}

With the default variables our world is only one chunk in size but we'll change that later. This array is pretty useless when it's all empty so let's at least fill it with some placeholder data, similar to how we filled the 2d array we'll cycle through all the dimensions of this array and turn anything lower than 8 to stone. Remember that in our data array 0 is air, 1 is stone and 2 is dirt.
void Start () {

 data = new byte[worldX,worldY,worldZ];
 
 for (int x=0; x<worldX; x++){
  for (int y=0; y<worldY; y++){
   for (int z=0; z<worldZ; z++){
    
    if(y<=8){
     data[x,y,z]=1;
    }
    
   }
  }
 }
 
}

Later on we'll use this script to also generate all our chunk game objects but for now we're just going to use it to store the data while we finish setting up the chunk script so we'll jump back to that chunk script. Go ahead and add a new public variable to the chunk for the world object and a private one for the world script. This is temporary so that we can access the world data.
public GameObject worldGO;
private World world;

Go into unity and set the worldGO variable to the world gameobject by dragging it from the heirarchy window to the variable in the inspector on the Chunk game object. In the start function add the following line:
world=worldGO.GetComponent("World") as World;

Now we can access the world data with world.data but this isn't ideal so jump over to the world script and add a new function called Block:
public byte Block(int x, int y, int z){
 
 if( x>=worldX || x<0 || y>=worldY || y<0 || z>=worldZ || z<0){
  return (byte) 1;
 }
 
 return data[x,y,z];
}

Now in the chunk script we'll use world.block(0,0,0) to get block data. This function just returns the data from the array but it includes an if to check that the requested block is within the boundaries of the array and otherwise it returns stone.

Back in the chunk script let's get to generating. Create a new function in the chunk script and call it Generate Mesh, with this chunk we're going to cycle through every block in the chunk and based on data from the world.data array generate a mesh. We can't just create a face for every block though because that would be far too many faces that the player actually can't see, instead we're going to only make faces that are exposed to air. This means that blocks buried and surrounded by other blocks aren't rendered at all saving us time. Create a function like this:
Edit: as ratnushock pointed out we need to add this variable to the script first, this defines the chunk size:
public int chunkSize=16;


void GenerateMesh(){
  
  for (int x=0; x<chunkSize; x++){
   for (int y=0; y<chunkSize; y++){
    for (int z=0; z<chunkSize; z++){
     //This code will run for every block in the chunk
     
     if(world.Block(x,y,z)!=0){
      //If the block is solid

      if(world.Block(x,y+1,z)==0){
       //Block above is air
       CubeTop(x,y,z,world.Block(x,y,z));
      }
      
      if(world.Block(x,y-1,z)==0){
       //Block below is air
       CubeBot(x,y,z,world.Block(x,y,z));
       
      }
      
      if(world.Block(x+1,y,z)==0){
       //Block east is air
       CubeEast(x,y,z,world.Block(x,y,z));
       
      }
      
      if(world.Block(x-1,y,z)==0){
       //Block west is air
       CubeWest(x,y,z,world.Block(x,y,z));
       
      }
      
      if(world.Block(x,y,z+1)==0){
       //Block north is air
       CubeNorth(x,y,z,world.Block(x,y,z));
       
      }
      
      if(world.Block(x,y,z-1)==0){
       //Block south is air
       CubeSouth(x,y,z,world.Block(x,y,z));
       
      }
      
     }
     
    }
   }
  }
  
  UpdateMesh ();
 }

Now this will cycle through every block in the chunk, if the block is not air it will run through all the faces of the block and check adjacent blocks. For each adjacent air block it will create a face for that side. We just check if the block at x,y,z + 1 in whatever direction we're checking is air and if so we run the function for that face. At the end of the function we call UpdateMesh() to set the mesh to the new one.

Replace the code that generated a block in the start function (all the CubeTop, CubeNorth, etc. and the UpdateMesh() ) with GenerateMesh();

If you run now you should get a flat mesh showing the tops of 16x16 blocks.

You should see this!
So this is pretty cool, it's terrain with a collision mesh and you can update it at any time by calling GenerateMesh(); You can also change the creation of the data array contents to get something more interesting than a plane. The problem here is that we've written the code so far just to make this one chunk but ideally we would have lots of chunks for more advanced terrain. Why don't we do that now.

What we'll do is just use the world gameobject for initialization and it will generate the chunk gameobjects as needed. Take your chunk gameobject in unity and drag it into the project window (into a prefabs folder if you want to keep it neat) and this will make a prefab of it. Now you can delete it from the scene. Switch to the world script and we can get going.

First add some variables to the world script:
public GameObject chunk;
public GameObject[,,] chunks;
public int chunkSize=16;

And go into unity and set the chunk gameobject to be the prefab we just created.

After we generate the contents of the data array we'll generate and array of chunk gameobjects:
chunks=new GameObject[Mathf.FloorToInt(worldX/chunkSize),
 Mathf.FloorToInt(worldY/chunkSize),
 Mathf.FloorToInt(worldZ/chunkSize)];
  
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++){
   
   chunks[x,y,z]= Instantiate(chunk,
    new Vector3(x*chunkSize,y*chunkSize,z*chunkSize),
    new Quaternion(0,0,0,0)) as GameObject;

   Chunk newChunkScript= chunks[x,y,z].GetComponent(\"Chunk\") as Chunk;

   newChunkScript.worldGO=gameObject;
   newChunkScript.chunkSize=chunkSize;
   newChunkScript.chunkX=x*chunkSize;
   newChunkScript.chunkY=y*chunkSize;
   newChunkScript.chunkZ=z*chunkSize;
   
  }
 }
}

What this does is generate enough chunks to display the contents of the data array and then sets them up with the variables needed. First we initialize the chunks array taking the size of the level and dividing each dimension by the chunk size so that we get the right amount of chunks based on the size of our world and we turn them into ints rounding down so that if they don't match up we get fewer chunks rather than chunks that aren't filled. This means that we never have to define how many chunks we want instead we just define the size of the level and the size of the chunks to represent them.

Then we go through and for each slot in the chunks array we instantiate a new chunk prefab. They are positioned with a new one every chunksize so we get their position with dimension*chunksize. Then we get access to their Chunk script and then set some variables. The worldGO, the chunk size and then some new variables; the coordinates of the chunk so that it can know which blocks it represents.

Go into the chunk script because we have to make some changes here too. Add these new variables for the position that we'll use to access the right blocks from the data array.
public int chunkX;
public int chunkY;
public int chunkZ;

Secondly we're going to add yet another step to our getting block data method, create a new function called Block:
byte Block(int x, int y, int z){
 return world.Block(x+chunkX,y+chunkY,z+chunkZ);
}

All this does is add on the position of the chunk to the position of the block we're accessing so that the chunk further to the center of the level accesses the blocks at it's location.

To use this function we have to change the way we accessed the blocks before so find are replace occurrences of world.Block with just Block. However don't replace the one in the function we just added.
byte Block(int x, int y, int z){
 return world.Block(x+chunkX,y+chunkY,z+chunkZ); // Don't replace the world.Block in this line!
}

So you can either click replace for each instance and skip the one return line we want to keep or you can replace all then remember to change back that one line.
You open this menu by pressing ctrl+f and then pressing the down arrow on the left to get the replace box
Now we can set the size of the world in the world object and leave the chunk size at 16. If you set the size of the world to 64x64 it should generate 4x4 chunks!

Now we're getting somewhere
But the problem now is that this terrain is super boring, why don't we fix that? To the World Script!
This is very similar to the way we added noise to the 2d level, we'll be generating perlin noise for each column.

First of all we now need more advanced noise than the standard unity perlin noise can offer as far as I know so we'll be using someone else's implementation of Perlin's Simplex noise in c#: Perlin Simplex Noise for C# and XNA. What you'll need to do is create a new C# script, call it "Noise" and remove everything but:
using UnityEngine;
using System.Collections;

Then after that paste in the code from simplex noise implementation linked as Download at the bottom of the post I just linked. Just save this file and we can access it from the World script.

Now we'll create a new function in the world script to retrieve the noise:
int PerlinNoise(int x,int y, int z, float scale, float height, float power){
 float rValue;
 rValue=Noise.GetNoise (((double)x) / scale, ((double)y)/ scale, ((double)z) / scale);
 rValue*=height;
 
 if(power!=0){
  rValue=Mathf.Pow( rValue, power);
 }
 
 return (int) rValue;
}

Now we can call Perlin noise with some extra variables like scale and height and power like in the 2d version but now we have a whole new dimension. Up in the start function where we created a floor of stone let's replace that with something a little more interesting.
for (int x=0; x<worldX; x++){
 for (int z=0; z<worldZ; z++){
  int stone=PerlinNoise(x,0,z,10,3,1.2f);
  stone+= PerlinNoise(x,300,z,20,4,0)+10;
  int dirt=PerlinNoise(x,100,z,50,2,0) +1; //Added +1 to make sure minimum grass height is 1
  
  for (int y=0; y<worldY; y++){
   if(y<=stone){
    data[x,y,z]=1;
   } else if(y<=dirt+stone){ //Changed this line thanks to a comment
    data[x,y,z]=2;
   }
   
  }
 }
}

This should add a little noise to the surface and in addition add a new block. If you run now the surface should be noisy but everything will be the same block. What you can do now is add different textures for blocks.

Back in the chunk script in the code for each face we decided what texture to use but just set it to stone, now let's change that.

I created a new variable called tGrassTop to set a texture based on the face of the block:
private Vector2 tGrassTop = new Vector2 (1, 1);

Then in the CubeTop function I use this:
Vector2 texturePos=new Vector2(0,0);
  
 if(Block(x,y,z)==1){
 texturePos=tStone;
} else if(Block(x,y,z)==2){
 texturePos=tGrassTop;
}

Now the sides of the blocks are often going to be the same so if you want you can set up a common function to set the textures for the sides of the blocks but what I've done is used this for all of them and the bottom:
if(Block(x,y,z)==1){
 texturePos=tStone;
} else if(Block(x,y,z)==2){
 texturePos=tGrass;
}

If you run this you should get some ugly terrain with our placeholder textures but it should work as a prototype:


Now it needs some remove and place block functions and some more block types but you can see how it's done. Next time I think we'll be able to place and remove blocks and come up with a better way to generate the chunks than before the game loads.

Here's an example of what you could do with what we've made so far, with some new textures and directional lights it's starting to look professional.



Until then though good luck, even with just this I think you have a good start to work from if you feel like you don't want to rely on my tutorial. Feel free to follow me on twitter or g+ and as always of you have a problem please let me know and I'll do my best to fix it.


Part 7: Modifying the terrain

77 comments:

  1. Can you link to a complete file for both scripts? I'm getting an error about chunkSize in Chunk that I can;t track down

    ReplyDelete
    Replies
    1. Here are both of them: http://studentgamedev.blogspot.no/p/chunk.html
      Could you post the error and where you were when it happened so I can try and fix it and the tutorial?

      Delete
  2. Hi again Alexander,

    The error Mark is complaining about is after setting up GenerateMesh().

    GenerateMesh needs the variable "chunkSize" which is set as "public int chunkSize=16;" but later if anyone follows the tutorial will get later, just after the 16x16x16 plane demo.

    I made an image which will explain it a lot better: http://imgur.com/5zi81Sw

    As in last part, if I find any more errors I'll post a comment.

    ReplyDelete
    Replies
    1. TYPO: "Alexander -> Alexandros" Sorry.

      Delete
    2. Thanks ratnushock, I added this line earlier in the tutorial. Also, Alex, Alexandros or Alexander, it doesn't matter :)

      Delete
  3. Hi, great tutorial. There's a different algorithm in your example code vs your full linked code when generating the perlin noise data. In the code above you have:

    } else if(y<=dirt){

    in your linked code:
    } else if(y<=dirt+stone){

    I didn't see dirt without the code change! Thanks again for the tutorial, I'm learning a lot of interesting techniques.

    ReplyDelete
    Replies
    1. Thanks for pointing this out, changing that line now. Happy to hear you like it!

      Delete
  4. Hi, probably my bad but couldnt find simplex noise link in here, the one i found generates the same terrain.

    btw nice tutorial

    ReplyDelete
    Replies
    1. I can't seem to find the link either...

      Delete
    2. Actually I think I found it thanks to Bob Johnson's quote below. I googled that and came up with this. I believe it is the right link.

      http://cabbynode.net/downloads/noise.txt

      Delete
  5. Thats awesome, now I understand how minecraft can run so fast. Thanks heaps. Wonder if its very hard to add slopes and the such.

    ReplyDelete
    Replies
    1. No problem Jay, glad you like it! I've been adding slopes and special blocks in my own version lately and it's not that hard, the biggest change is checking whether or not to render faces facing the ramp.

      Delete
  6. Great tutorial! Question about the noise. How do you go about randomizing it? Right now the same terrain is generated every time.

    ReplyDelete
    Replies
    1. Got it. Read the Perlin Simplex page.

      "if you want a different pattern, all you need to do is modify any/all of the values"

      Delete
  7. This comment has been removed by the author.

    ReplyDelete
  8. Thanks for the tutorial, very simple and easy to understand.

    I'm currently encountering an issue where the edges of blocks show visible seams. Went through the whole tutorial once and had the issue, and am now doing it in my own manner, and both projects have the issue.

    I'm using temporary textures from a Minecraft texture pack, and running on an AMD Radeon HD 6850. Any ideas? Should I increase the mesh bounds by a tiny amount?

    Apologies for the deleted comment, it was bothering me that the apostrophe in my name wasn't showing up and changing my profile name didn't take effect after a refresh, so I figured I'd delete the comment. Turns out deleted comments stick around still.....

    ReplyDelete
    Replies
    1. Hey Veovis, what could be happening is that your textures are 'bleeding' from one tile to another. Try setting the import settings for your texture to advanced, then turn off mipmaps and set filter mode to point. This should render the texture without any blur preserving the edge between tiles.

      Delete
    2. Point filtering did it, thank you very much! I think I did that in the 2d project and forgot to redo it in the 3d project.

      Delete
  9. Hey, ran into a minor problem while following along with the tutorials. I eventually figured out this line:

    int dirt=PerlinNoise(x,100,z,50,2,0);

    Should be:

    int dirt=PerlinNoise(x,100,z,50,2,0)+1;

    Without the +1 it caused a large portion of the exposed blocks to be stone instead of dirt as in your example image.

    Also thanks a lot for these tutorials, they were pretty much exactly what I needed to wrap my head around building randomized voxel terrain.

    ReplyDelete
    Replies
    1. Thanks Sark, I changed this in the tutorial.

      Delete
  10. Hi, great Tutorial, but I think I did something wrong and I don't know where. Do know what it may cause this ? IMAGE

    I've used the same values for the Noise you did, and that is what I get. Tiles that doesn't show !

    Do you know what it might cause that ? :)

    Thank you !

    ReplyDelete
    Replies
    1. Hey Catalin, it looks like blocks above a certain y are being cut off. This could be because you are not generating enough chunks upwards. The variable that controls this I think is worldY and it's public so you can change it in the editor on the terrain object. Just increase it until your terrain is shown each one is 16 blocks. If they doesn't do it you might need to post your code and I'll take a look.

      Delete
    2. Yep, remember everyone: if you have a public variable that shows in the inspector, the INSPECTOR value will override any changes you make to the variable in code! This bit me more than once.

      Delete
  11. Hi, Great tutorial! I have run it to an issue tho were the chunks are spawning but some of the grass block tops are showing the side texture and some faces are not even spawning at all :/, I have tried your code in unity and the same issue is occurring :/ any help would be amazing :D

    ReplyDelete
    Replies
    1. That's strange, I'm on my phone so I can't check the example code but it might be your texture sheet that's the problem. Maybe the side texture is in the place of the top texture and one of the other texture positions is transparent. Try changing the texture coordinates for grass top and see if that helps. Also is there any pattern as to which faces aren't spawning? Maybe it's the rock for example and that texture position variable is set to an empty spot. You could also select the mesh in the editor and see if the wireframe shows a mesh there or not or you could throw on a flat texture and see if they show up. I'll check the example code if this doesn't work.

      Delete
    2. thank you for the reply. The empty space around were I haven't made a texture black so there isn't any transparency. Wire frame isn't generating on the highest level :/ also there is normal stone block being mad as well as grass blocks, there are only a few problematic ones.

      Delete
    3. Then I'll check the example code once I get home this is an interesting glitch and I'd like to take a look.

      Delete
    4. Sorry for the late reply, I just finished work. And thank you so much :)

      Delete
    5. I had the same issue with the top level not generating. I found that it is the same problem T Catalin was having. I increased my World Y on World gameobject in the inspector to 32 and it works after that.

      Just not sure why it isn't the same as Alex's results.

      Delete
    6. Sorry it took me so long but I've finally gotten a chance to have a look at the example code and I can't seem to replicate the glitch. Try increasing worldY by 16 and see if that helps any of the problems like Ryan suggested but this wouldn't be causing textures to appear on the wrong faces, with the example code I used World X: 128, World Y 32 and World Z 128 and everything rendered fine. If the problem keeps showing up even with the example code and these variables and it's not the texture I don't know what it could be because then we're running the same thing. Maybe if you post a screenshot I could get a better idea of what's going on.

      Delete
    7. I'll post here as well as the comment above, because I wrangled with this longer than I should have. Like me, Jonathan is probably trying to change the values in code instead of in the inspector.

      Values typed into the inspector override any value in code, even if you try to change the values in code later. You'll either have to make your changes in the inspector or make the code variables private so they can't get overridden.

      Delete
  12. Hey Alexandros,
    Very nice series of tutorial. I came here to learn how to make a mesh procedurally in Unity and stayed for the rest!

    Voxels and Perlin noise are cool to make what is probably now known as a "Minecraft" world.

    I'm curious to know if there are any other techniques for generating worlds, and if you know any by name could you point them out so I can do some more research?

    Can Perlin noise be used to generate something else than a voxel world?

    ReplyDelete
    Replies
    1. Well Perlin noise can be used for a whole lot, within voxels you could look at marching cubes for an algorithm on voxel smoothing, otherwise you could try using heightmap terrain with Perlin noise which is great for more static worlds. A lot of dungeon generators use random numbers to generate rooms and tunnels between them. You could also get into component based dungeons where you create rooms with doors, then for each door you add another room from a group of rooms to match up to it.

      Perlin noise is really great for a lot of things, originally one of it's main applications was procedural texture creation, libnoise has some great examples: http://libnoise.sourceforge.net/examples/index.html

      Delete
  13. Hey Alexandros! great tutorial but I seem to be having some sort of issue, none of the blocks are showing up at all. Nothing is being drawn to the screen at all. The only thing I can see are the little gizmos for the transform in the scene view and nothing appears at all in the game view. I am familiar enough with Unity to mess around with some things and nothing I did fixed it. Thinking I ,must have made a code mistake somewhere I copied and pasted all the code and went through the whole tutorial again :/ unfortunately I still can not get anything to draw on the screen. I am not encountering any errors that the compiler is picking up so I am a bit at a loss for what is wrong. Any help or suggestions would be greatly appreciated!

    ReplyDelete
    Replies
    1. Hmm, there isn't much to go on but if you're not seeing anything of the mesh in the scene view and the game view it must not be getting built so there are a few things that could be happening.

      First of all check if the gameobject has a mesh renderer and a mesh filter component, if these are missing add them to the chunk prefab.

      If that wasn't it make sure that your chunk is not empty or completely full, you could replace the terrain generation code with just some simple flat terrain or even just a single block to check this.
      If it's not that you can check if code is being run for any faces in the mesh building step by printing the faceCount variable at the end of GenerateMesh but before you call UpdateMesh. If that's above zero then the code is being run for the faces otherwise something is happening so that the faces of the blocks aren't being detected. Maybe the solid block and air block checks aren't working or the functions for each face aren't being run.
      If that's not it either then start checking if the mesh we've generated is being applied. Check that UpdateMesh looks the way it should and that newVertices and the other new* variables aren't empty with newVertices.Count (returns the length of the list, it should be much larger than 0). If these are empty and the facecount wasn't 0 then the functions are being run but nothing is being saved to the new* variables.

      Let me know if this helps you :)

      Delete
  14. Hi Alexandros!
    I'd like to thank you for your great tutorials. Finding them very informative, and well paced.
    There's a small detail which could trip people up in this post. In the section before you move onto perlin noise, you add the Block function to Chunk.cs:
    byte Block(int x, int y, int z){
    return world.Block(x+chunkX,y+chunkY,z+chunkZ);
    }
    and the next thing you do is find+replace world.Block with Block. This causes the world.Block in the Block function to get replaced to which would lead to a recursion overflow.
    Hope this helps!

    ReplyDelete
    Replies
    1. Oh wow, thanks for pointing this out! I'll add a note in the tutorial to not replace that instance.

      Delete
    2. Maybe this is what happened to me? Because I was getting an error of a stack overflow! I think I will go try this out first and make sure that the correct code is in the World.cs file. Cause that would certainly cause issues. If that fix doesn't work, I will go check out the other things you mentioned Alex, thank you!

      Delete
  15. I got it to work! Thanks Alex, I checked up on some things and fixed that recursive error! :) Great tutotrial

    ReplyDelete
  16. U mentioned at the beginning of this tutorial that there are two ways to store the chunk data. You chose to save the chunk data in a world array. Im curious as to how to store the information in the chunk. Can you elaborate on this at all Alex?

    ReplyDelete
  17. Need to link to Part 7 ! Awesome tutorial series... :)

    ReplyDelete
  18. I get the following error:

    NullReferenceException: Object reference not set to an instance of an object
    (wrapper managed-to-managed) object:ElementAddr (object,int,int,int)

    Just right before trying to get the first flat 16x16 render :-(

    It comes from the Block function, while returning the data[x, y, z].

    ReplyDelete
    Replies
    1. I ran into this same issue, which is an order-of-operations problem.

      Both the Chunk script and the World script initialize themselves in their Start() function, but Chunk is depending on World to run first, which is not guaranteed. If Chunk initializes first, it runs its GenerateMesh() function which calls world.Block() which then tries to return the contents of its data array, which is still null.

      To fix this, simply tell the World script to do its initialization in the Awake() function, which is guaranteed to finish before any Start() functions are called.

      So in the World script: "void Start()" becomes "void Awake()".


      Delete
    2. Also note this issue disappears in the very next step since chunks are subsequently instantiated by the World object.

      Delete
  19. In the tutorial where you link to the noisegen class on the other site, the file you link to contains a namespace, so if you just copy and paste it doesn't work, you have to remove the namespace.

    ReplyDelete
    Replies
    1. Or you can use Noise.Noise.GetNoise
      Not sure what is best. Article mentioned it was updated prob whay tutorial dont match exactly.

      Delete
  20. Hi,
    I know it might sound dumb, but in the part with the flat mesh, I was not able to see the mesh at all.
    It turns out you have to zoom out quite a bit to actually see something, took me a while to figure out. I hope this helps anyone having this issue

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

    ReplyDelete
  22. Hello again,
    hate to be picky, but I have had some errors with the way strings are declared in your awesome tutorial.
    You put "(\"Chunk\")", but it gives me an error so I had to change it to "("Chunk")" for it to work.

    Also, when you wrote "rValue=Noise.GetNoise (..." I had to change it to "rValue=Noise.Noise.GetNoise (" to make it work for me.

    Very very very good tutorial series by the way!!!!

    ReplyDelete
    Replies
    1. I had the same issue, I don't understand why no one else seems to, but I'm glad you solved it and posted here.

      Delete
  23. Hey mate. Nice Tutorial Series. I was making my own voxelengine but couldn't get it as fast as you.
    All works fine at the moment but i have some question, about something i cant figure out by myself.

    You have a limited area defined by the worldsize. The big thing there is, that you are not spawning chunks into minus coordinates and if you reachs the end of the worldsize, it wont spawn chunks either.

    Is there a way to completly ignore this and always spawn chunks?

    ReplyDelete
  24. Hey Alexandros, Im loving these tutorials but one question.. Where did you get your specific perlin noise script from? I am using the one Ryan Cody linked but if possible, id prefer to use the same one. That is assuming it isnt the same one...

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

    ReplyDelete
  26. I have a little problem. For some reason all the hills etc are streched weirdly. Their are all really long in the X direction, but totally fine in the Z direction. I don´t know why. Apparantly I haven´t understood Perlin Noise yet properly.

    ReplyDelete
  27. im getting an error
    NullReferenceException: Object not set to an instance of an object Chunks.Block(int32 x, Int32 y, Int32 z)
    I have been reading over both my code and your tutorial but I am unable to figure out what I did wrong that is causing this error. Can you help me out?

    ReplyDelete
    Replies
    1. The issue your facing is hard to tell without the particular line of code, however, my guess is the issue here is you havent set the gameObject in the world GameObject where you need to set the Chunk variable to "Chunk" inside the inspector

      Delete
  28. I can't believe I did that, thank you so much I have been racking my brain staring at my code for the past 3 days trying to figure that out. I guess I over looked the part about setting the chunk variable in the world gameobject. I feel so dumb, thanks again.

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

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

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

      Delete
  31. Enjoying the tutorial!

    Thought I'd post that I was getting a null reference exception every time world.Block(x,y,z) was called, when I first ran through this part of the tutorial; it turned out that Start() in World.cs was being called *after* Start() in Chunk.cs, and so data[,,] wasn't initialized until after the world.Block(x,y,z) call, hence the null reference exception. I changed Start() to Awake() in World.cs, and this seems to have fixed the problem.

    Although I have some other problems now, hah. Debugging, debugging...

    ReplyDelete
    Replies
    1. Figured out my other problem: although CubeTop, CubeBot, etc. were being called properly (after the Start -> Awake fix indicated above), I wasn't seeing any of the cube faces rendered in either the Game or Scene views; as it turns out, I had an extra call to UpdateMesh() at the end of my Start() function in Chunk.cs. Since previous calls to UpdateMesh() at the end of GenerateMesh() delete the current vertices/triangles/etc, this last UpdateMesh() call was generating an empty mesh, and overwriting all the cube tops etc. with empty space.

      Not sure if you told us somewhere to remove that extra UpdateMesh() call from Start(); figured I'd mention this in case you didn't. I certainly overlooked it!

      Delete
    2. Another problem! The simplex noise algorithm you linked us to seems to have been updated substantially since it was originally posted; to access the GetNoise method, rather than doing Noise.GetNoise, I had to declare a new instance of the class Noise.NoiseGen, and then call its GetNoise() method. So, this:

      Noise.NoiseGen noiseGenerator = new Noise.NoiseGen();
      rValue=noiseGenerator.GetNoise(((double)x) / scale, ((double)y)/ scale, ((double)z) / scale);

      Rather than:

      rValue=Noise.GetNoise(((double)x) / scale, ((double)y)/ scale, ((double)z) / scale);

      I believe this is because rather than the old simplex code, which seems to be here:

      http://cabbynode.net/downloads/noise.txt

      The code linked to at the end of the page you linked us to is now:

      http://cabbynode.net/downloads/Noise.cs

      which declares a full namespace and, inside of it, a class, rather than just declaring a static class outright.

      Delete
    3. Hi !
      Thank you for noticing this.
      But I have a problem which seems to be related to yours. I have holes in the map :

      http://img4.hostingpics.net/pics/880944cube.png

      Do you know how I could solve this ?

      Thx !

      Delete
    4. It seems to have a problem with this line :

      stone+= PerlinNoise(x,300,z,20,4,0) + 10;

      withtout the +10 this works fine but there is no stone on the map. So the "holes" are in fact the stone which doesn't render.

      Delete
    5. try changing this :

      if( x>=worldX || x<0 || y>=worldY || y<0 || z>=worldZ || z<0){
      return (byte) 0;
      }

      Delete
  32. (The answer chat is a bit buggy, it's the 3rd message I try to post O_O)
    First of all, fantastic tutorial :D But, I have a problem with collisions. Actually, the mesh collider isn't calculated, but when set in convex, it truly represents the convex collider of what should be the collider. Do you have any ideas ? :/ Without collisions, I can't continue your lesson...

    ReplyDelete
    Replies
    1. Oh, apparently, for people who would have problems with collisions, don't try to use big speeds or your objects will pass through the terrain !

      Delete
  33. Loving the tutorials so far. However I'm getting a problem with the code I put in. The code that's here causes me 25 errors? Most of them are the fact that it can't reference the objects it wants to so:
    I set WorldX, WorldY, WorldZ, and the Block function to be static this fixes 24 of my errors, the only one that remains is this one:

    Assets/Scripts/World.cs(30,24): error CS0120: An object reference is required to access non-static member `World.data'

    This is a problem within the block function that's now static, it specifies the line:
    'return data [x, y, z];'
    It's a problem I can't seem to get rid of and I'm not sure what to do about it. If someone could point out where I've gone wrong then that would be really appreciated :)

    ReplyDelete
    Replies
    1. Hi Daniel, I think what's causing these errors is that the generated chunks don't have a reference to the world script. Because of this making the function static works for some of the errors because you don't need a reference to access a public static function but this function won't work as a static function unfortunately but we should be able to fix it. So remove the static term from the function and try this.

      Firstly check that the Block function in the chunk.cs script is correct to this one:
      byte Block(int x, int y, int z){
      return world.Block(x+chunkX,y+chunkY,z+chunkZ);
      }

      Especially make sure you're using a lowercase w in "world".

      Next, in the part of the world.cs script where we instantiate chunks and initialize them make sure that you have the line: "newChunkScript.worldGO=gameObject;"
      It should be positioned after instantiating the chunk and after this line: " Chunk newChunkScript= chunks[x,y,z].GetComponent("Chunk") as Chunk;"

      This line gives each chunk a reference to the world game object and if it's missing it could be the source of your problems however if it's there the problem may be further on.

      Lastly, in the chunk.cs script in the Start function we set the variable world to the world script using the worldGO variable we set in the world.cs script. That "world" variable is the one that we use to call functions in the world script so if its missing you'll get reference errors so make sure this line is in the Start function: "world=worldGO.GetComponent("World") as World;"

      So these are all the steps in setting up chunks with a reference to the world so that they can call the world script's Block function and that's why I'm asking you to make sure they're all correct. If all of them match and it still doesn't work let me know.

      Good luck!

      Delete
    2. Thanks for the reply, I really appreciate it as I don't usually have such luck on other forums :) I think I may have been unclear in my first comment, I should have mentioned that the part I'm stuck at is the part where you say 'If you run now you should see a mesh showing the tops of 16x16 blocks' So a lot of what you suggested I haven't written yet, I've only written the code that preceded the photo, but if the things you mentioned will still fix my problems then I'll add them. One thing I think I should mention is that I can't attach the 'world' Game object to the WorldGO variable within the unity inspector of the chunk Game object because when I load the script it says 'due to compiler errors it isn't a valid script'. With the errors part, the errors follow a main pattern of mostly these 3 keeping being pulled from various different instances:

      "Assets/Scripts/Chunk.cs(124,77): error CS0120: An object reference is required to access non-static member `World.Block(int, int, int)'"
      "Assets/Scripts/Chunk.cs(124,57): error CS1502: The best overloaded method match for `Chunk.CubeTop(int, int, int, byte)' has some invalid arguments'"
      "Assets/Scripts/Chunk.cs(124,57): error CS1503: Argument `#4' cannot convert `object' expression to type `byte'"

      referring to this: "if(World.Block(x,y+1,z)==0){
      CubeTop(x,y,z,World.Block(x,y,z));
      }"

      These errors are essentially repeated for the next 5 faces. I've searched for help with the errors but none of the answers I've found have been helpful.

      Thanks for taking the time to help me out.

      Delete
  34. Hi Alexandros,

    Thank you very much for these tutorials, they are excellent. I've hit one minor snag here in Tutorial 6 that is driving me a bit crazy, this picture shows the issue I'm having:

    http://imgur.com/VXW57kf

    Basically, the dirt sections are only being drawn along the diagonal blocks, where x is equal to z. I have no idea why. I've been scratching around in this part of the World script to try to figure it out, but to no avail.

    data = new byte[worldX,worldY,worldZ];

    for (int x = 0; x < worldX; x++) {
    for (int z = 0; z < worldZ; z++) {
    int stone = PerlinNoise (x, 0, z, 10, 3, 1.2f);
    stone += PerlinNoise (x, 300, z, 20, 4, 0) + 10;
    int dirt = PerlinNoise (x, 100, z, 50, 3, 0) + 1;

    for (int y = 0; y < worldY; y++){
    if (y <= stone) {
    data [x, y, z] = 1;
    } else if (y <= dirt + stone){
    data [x, y, x] = 2;
    }
    }
    }
    }

    It appears to be going wrong as part of the else if, where the data byte is being set to 2 if y <= dirt + stone, but I've exhausted my know-how trying to figure it out. Any help would be much appreciated. Thanks again!

    ReplyDelete
  35. chunks=new GameObject[Mathf.FloorToInt(worldX/chunkSize),
    Mathf.FloorToInt(worldY/chunkSize),
    Mathf.FloorToInt(worldZ/chunkSize)];
    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++){

    chunks[x,y,z]= Instantiate(chunk,
    new Vector3(x*chunkSize,y*chunkSize,z*chunkSize),
    new Quaternion(0,0,0,0)) as GameObject;

    Chunk newChunkScript= chunks[x,y,z].GetComponent(\"Chunk\") as Chunk;

    newChunkScript.worldGO=gameObject;
    newChunkScript.chunkSize=chunkSize;
    newChunkScript.chunkX=x*chunkSize;
    newChunkScript.chunkY=y*chunkSize;
    newChunkScript.chunkZ=z*chunkSize;

    }
    }
    }


    Where do i put these lines of code?

    ReplyDelete
  36. I have done everything in this tutorial, but i dont know where to put these lines of code, and i cant figure it out. I cant continue any further, someone please help?

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

    ReplyDelete
    Replies
    1. Sorry, for deleted comment, browser glitched out. What I was trying to say was I am really enjoying the tutorials, But I am having an issue after entering all code, where I get the following error in console:

      NullReferenceException: Object reference not set to an instance of an object
      Chunk.GenerateMesh () (at Assets/Chunk.cs:32)

      I went to line 32, and this is what I have (lines 30-35):

      void GenerateMesh(){

      for (int x=0; x<chunkSize; x++){
      for (int y=0; y<chunkSize; y++){
      for (int z=0; z<chunkSize; z++){
      //This code will run for every block in the chunk

      Not sure what is causing the issue, or what I need to do to fix this. I even tried copying the code straight from tutorial, and it still gives same error.

      Delete
  38. These are great even years later. You did awesome on this!

    ReplyDelete