Tampilkan postingan dengan label Terrain Generation. Tampilkan semua postingan
Tampilkan postingan dengan label Terrain Generation. Tampilkan semua postingan
Senin, 14 Oktober 2013

Unity Voxel Tutorial Part 6: Building 3d Voxels


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
templates-office.com Terrain Generation, Tutorial, Unity, Voxel Tut, Voxels
Senin, 02 September 2013

Unity Voxel Tutorial Part 3: Perlin noise for terrain


Welcome to part three of the voxel tutorial. We'll be setting up the 2d game with some perlin noise to give the terrain some shape.
We'll get started by using perlin noise to create some varied terrain and caves from our blocks. Start by increasing the size of the block array to 96x128 so that we have some more room to work with. This is defined in the GenTerrain function. Also take away the old code that we used to generate terrain, the one that gave us 5 rows of blocks.

void GenTerrain(){
blocks=new byte[96,128];

for(int px=0;px<blocks.GetLength(0);px++){

for(int py=0;py<blocks.GetLength(1);py++){

}
}
}

To use perlin noise I like to use a separate function to call the Mathf.PerlinNoise unity function so that I can include things like scale and apply exponents with the parameters of my function. Here's that function:
int Noise (int x, int y, float scale, float mag, float exp){

return (int) (Mathf.Pow ((Mathf.PerlinNoise(x/scale,y/scale)*mag),(exp) ));

}

Perlin noise is an algorithm created by Ken Perlin to create gradient noise. Other people can explain it much better than me: This is a good source. But all you really need to know is that it returns values between 1 and 0 based on the values you enter. It can be used for numbers of dimentions far beyond what we need but the Unity function only takes two. For now that will be fine because this example is 2d.

What the function above does is it takes coordinates for x and y to sample for noise, then it calls the perlin noise function with those divided by scale. Because perlin noise isn't random but bases itself on the coordinates supplied then the closer those coordinates are to each other the more similar the values it returns. So when we divide the coordinates by a number they end up as smaller numbers closer to each other. (1,0) and (2,0) might return 0.5 and 0.3 respectively but if we divide them by two calling perlin noise for (0.5,0) and (1,0) instead the numbers might be 0.4 and 0.5. This will be more clear once we apply it to the terrain.

Then we take the value we get from perlin noise and multiply it by the magnitude "mag" because perlin noise returns a value between 0 and 1 and we are going to want noise that creates hills that vary in height by larger sizes like between 0 and 10. Then we take the result and put it to the power of the exponent "exp". This is useful for mountains and things. Lastly we convert the float returned into an int.

We'll apply this to the GenTerrain function column by column. By getting a number for perlin noise in the first loop (for each x) and then using that number in the y loop as the height of the terrain:

void GenTerrain(){
blocks=new byte[96,128];

for(int px=0;px<blocks.GetLength(0);px++){
int stone= Noise(px,0, 80,15,1);
stone+= Noise(px,0, 50,30,1);
stone+= Noise(px,0, 10,10,1);
stone+=75;

int dirt = Noise(px,0, 100,35,1);
dirt+= Noise(px,0, 50,30,1);
dirt+=75;

for(int py=0;py<blocks.GetLength(1);py++){
if(py<stone){
blocks[px, py]=1;


} else if(py<dirt) {
blocks[px,py]=2;
}


}
}
}


We create a stone int and a dirt int and using a few layers of perlin noise they get more textured values. Because this is essentially a 1d heightmap we only need x and the y variable can be used just to sample from a different area to make sure the results aren't the same. You can see the stone is three noise layers with different values.

Layer 1:
int stone= Noise(px,0, 80,15,1);

Layer one has a scale of 80 making it quite smooth with large rolling hills, the magnitude is 15 so the hills are at most 15 high (but in practice they're usually around 12 at the most) and at the least 0 and the exponent is 1 so no change is applied exponentially.

Layer 2:
stone+= Noise(px,0, 50,30,1);

The next layer has a smaller scale so it's more choppy (but still quite tame) and has a larger magnitude so a higher max height. This ends up being the most prominent layer making the hills.

Layer 3:
stone+= Noise(px,0, 10,10,1);

The third layer has an even smaller scale so it's even noisier but it's magnitude is 10 so its max height is lower, it's mostly for adding some small noise to the stone to make it look more natural. Lastly we add 75 to the stone to raise it up.

The dirt layer has to be mostly higher than the stone so the magnitudes here are higher but the scales are 100 and 50 which gives us rolling hills with little noise. Again we add 75 to raise it up.

The result is a noisy stone layer with highs and lows and a smooth dirt layer on top that's usually higher than the stone layer but sometimes the stone sticks out. You could also change the y value to offset the location of the noise sample. This is applied in the y loop where we change all blocks with a y below the stone int to stone and if they're higher than the stone (else) we check if y is below the dirt in and if so change the blocks to dirt.

Stone and dirt noise.
Also with noise we will add caves and spots of dirt in the stone. Surprisingly this is simpler than the dirt and stone layers. What we do is that in the if function for creating stone to a certain height we add another two ifs for caves and dirt so if the block is made into stone check to see if it should be a cave or a dirt spot instead. Here we'll use both x and y because the noise should be 2d.

void GenTerrain(){
blocks=new byte[96,128];

for(int px=0;px<blocks.GetLength(0);px++){
int stone= Noise(px,0, 80,15,1);
stone+= Noise(px,0, 50,30,1);
stone+= Noise(px,0, 10,10,1);
stone+=75;

print(stone);

int dirt = Noise(px,0, 100f,35,1);
dirt+= Noise(px,100, 50,30,1);
dirt+=75;


for(int py=0;py<blocks.GetLength(1);py++){
if(py<stone){
blocks[px, py]=1;

//The next three lines make dirt spots in random places
if(Noise(px,py,12,16,1)>10){
blocks[px,py]=2;

}

//The next three lines remove dirt and rock to make caves in certain places
if(Noise(px,py*2,16,14,1)>10){ //Caves
blocks[px,py]=0;

}

} else if(py<dirt) {
blocks[px,py]=2;
}


}
}
}

So you see inside the stone if ( if(py<stone) ) we also have an if that compares noise with 10 so if the noise we return is larger than 10 it turns the block to dirt instead of stone. The magnitude of the noise value is 16 so it reruns a over 10 only a little of the time and the scale is fairly low so the spots are pretty small and frequent. We're using x and y here and running the if for every block so the dirt is distributed through the whole array.

After that we add caves with a similar function but we multiply y by two to stretch out the caves so they are wider than they are tall and we use a larger scale to make larger less frequent caves and the magnitude is lower to reduce the size of the caves that was increased by the scale.

Now you should get caves and dirt spots.
The caves and spots are pretty evenly distributed, I like to use y to change the scale and magnitude for both to make caves more likely and large the lower you go and the opposite for dirt spots but that's all taste. You can also use similar functions to add things like ores and things.

Thanks for reading part 3, let me know about any problems you find or feedback you think of. Follow me on twitter (@STV_Alex) or G+ to get updated when I post part four. It should be out very shortly, part three was actually going to include it but I decided to split them up, anyway in that one we'll be destroying and placing blocks!

Edit: Thanks again to Taryndactyl! Taryndactyl's post: Link

Part 4
templates-office.com Terrain Generation, Tutorial, Unity, Voxel Tut, Voxels
Senin, 22 April 2013

Still not voxel dwarf game

Progress is still on hold while I complete course work and I missed screenshot Saturday :( However I'll be done by the end of the week and done with exams in two weeks. Anyway, island terrain generation is still damn fun. I've added variables for customization for a little variation on the islands.

This is the player island, blue squares mark available slots for boats and light colored chunks of terrain are available to place buildings like farms and factories.

Extra large beaches, extra large island, less exponential terrain

Small beaches, smaller island, 
So, why not use unity terrain? I chose early on to generate this terrain and build the meshes with code while unity terrain sounds like a simpler choice. A few factors led to this, of course one is that I love procedural terrain. Otherwise though: first, once written the code can generate any amount of islands. Although we're not using more than four or five we have made a lot of changes to what we wanted from these islands. The size was originally going to be much smaller but we increased it as we needed more land to work with. I can easily change the size of my islands without rebuilding anything. Secondly I base where you can build on heightmap data, something that I have simple access to in my own code. And lastly the way I've implemented these meshes has let me create a simplified pixel graphic look for the terrain that is minimalist and almost cartoony. This lets us get away without breathtaking visuals while still looking good.

With all this in place it's fun to think about larger applications of it, maybe getting to sail around an infinite sea of islands, trading with them, maybe fighting off pirates and eventually running your own island. I wish I had time to program everything I think of.


templates-office.com Island Game, Terrain Generation
Selasa, 16 April 2013

Not Voxel Dwarf Game

I've been busy with assignments lately so I haven't been able to dedicate so much time to my own project but this blog is called Student Game Dev after all. Lucky for me the assignments I've been working on are actually relevant, me and two friends are making an island management /resource management game for kids where they control the production of an island and trade with their neighbors.

The islands are procedurally generated of course.



The best part is that just a few hours work to make this island and I can apply the code as many times as I want. If I add some random variations to things like beach size, mountain likeliness and island noise I could get some very varied outcomes.


Anyway I'll be back to work on the voxel dwarf game soon and with any luck some of the trade and resource management elements of the island game will be useful to the dwarves.


Update:


templates-office.com Island Game, Terrain Generation
Selasa, 02 April 2013

Archipelago

Replacing the bottom layer of dirt with water to test out an archipelago type terrain generation. I wrote off oceans and islands early on because I didn't feel large expanses of water mixed all that well with the dwarves but the way this turned out it making me doubt my choice.

Missing the reflections water is supposed to have but still.


View from the editor

templates-office.com Terrain Generation, Voxel Dwarf Game
Sabtu, 30 Maret 2013

It's been quiet here... too quiet

I've been quiet for a while as development slowed down while I studied for an exam in c++ but I'm back on track and have rewritten a lot of code which now runs much faster and uses much less ram. The old code was storing some block data more than once as a sort of overlap between chunks where one chunk would also contain the data of the blocks bordering it from other chunks. Now the chunk code can efficiently access other locks and pull block data to know what it's bordering on. The result is that I can generate faster and generate a lot more. Also the way chunks are sorted and stored has been updated which now makes it possible to generate infinite terrain instead of the fixed map size I used to have. I haven't implemented this because I don't have any use for it but with a function to unload chunks too far away this would be possible.


Ingame shot with Morning sunrise - medium fog preset. Ingame the sun will move over time and ambient light and fog will change to match time of day.

Scaling the textures down to 4x4 pixels resulted in this. This is probably not viable for actual gameplay because block types need to be distinguishable and the textures are gorgeous at full 32x32 resolution but it looks very cute.

Noisy terrain in sunset lighting
Daytime lighting with 32x32 resolution textures


One thing I'm working with right now is how to make terrain easier to discern, this proves to be difficult in some cases partially because of the very noisy terrain with few flat faces and partially because of the distance of the camera from the terrain and the angle. Hopefully I'll find a solution to this without toning down the noise and detail of the terrain.

Next on the programming to do list is the camera controls and the cutaway view for the player to view underground structures. Players will be able to scroll through and set a max height above which all terrain will be cut off and not rendered. This will let players see what's under the terrain so long as it's lit well enough.
templates-office.com Terrain Generation, Voxel Dwarf Game
Kamis, 03 Januari 2013

Terrain Generation

I've started using Perlin noise instead of a height map to generate the terrain to have infinite generation and be able to generate caves and caverns as well as the surface of the terrain. The results have been encouraging but there's still a lot to be done to ensure that it creates a realistic surface with mountains and plains and other natural elements such as rivers and lakes.

First thing I generated was some rolling hills with a 2d Perlin noise function, looks good but too boring for gameplay.

Then I generated some 3d noise which made this alien landscape.

I modified the 3d noise function to create a slightly more realistic terrain, here by changing the thresholds for what makes a solid block dependent on elevation so that the lower levels are almost solid with more caves on the way up to the surface, then the surface gives way to mountains and floating islands. It's not realistic enough but if I hadn't used a linear change it might have worked better. 


Lastly: Layers upon layers of 2d Perlin noise generated terrain on top of each other, this is how I plan to make realistic terrain. Starting with a mountain layer I got tall mountains and large valleys, then I placed a layer of stone starting from the bottom replacing all air blocks in the way up to around 30 meters, then a layer of dirt replacing air up to 34 meters and lastly adding a layer of dirt on top of everything with a between 0 and 4 blocks deep to put dirt on the mountains and add some more noise to the surface.


templates-office.com Terrain Generation, Voxel Dwarf Game