Thursday 11 August 2016

Landscape generation with Perlin noise

Unless you have been living under a rock, you have probably heard of a game called Minecraft.
I'm a fan of the game and I was always fascinated by the idea of creating an infinite, randomized world. Minecraft uses something called Perlin noise to procedurally create the landscape.

This type of noise was created in 1983 by Ken Perlin, who ended up winning an Academy Award for technical achievement for developing the algorithm.

Perlin noise can be use with different purposes. If you look at a texture image of perlin noise in black and white you will quickly notice that it looks like a heightmap. A height map is just a representation of the different heights of a terrain and it's useful too display mountain chains, abysses, valleys, ecc.

Because of this property, this type of noise becomes extremely useful when creating random terrain.

Luckily for us, Unity has already a function which enables us to use Perlin noise without having to re write the whole algorithm ourselves. The method is Mathf.Perlin(float x, float y).

We are now going to create a Minecraft-like landscape using cubes and Perlin noise.

First, create a simple cube and apply a green material to it. This is going to be our "grass" cube.



Save it as a prefab as this is going to be the basic building block for the landscape.

Now, the script. You will be surprised how easy it can be to achieve such thing. Create an empty game object and a new C# script called LandscapeGenerator and attach it to it.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class LandscapeGenerator : MonoBehaviour {

 public static LandscapeGenerator instance;

 public GameObject grass;
 public int mapSizeX = 50;
 public int mapSizeZ = 50;

 public float heightScale = 20f;
 public float detailScale = 20f;

 float seed;
 // Use this for initialization
 void Start () {

  if (instance == null)
   instance = this;  
 
  

  for (int x = 0; x < mapSizeX; x++) {
   for (int z = 0; z < mapSizeZ; z++) {
    float y = 0;
    GameObject g = Instantiate (grass) as GameObject;
    g.transform.position = new Vector3 (x, y, z);
    g.transform.SetParent (this.transform);
   
   }
  }
 
 }
 
 // Update is called once per frame
 void Update () {

 }
}

I think the script is pretty easy and at this stage it won't do anything special, it simply lays out a basic,flat map for us.

As the cubes are all 1 by 1 in size, the 2 variables x and z are incremented by one, so the blocks are going to be placed one next to each other without any gap between them or overlapping.

This is the result you should see:


Rather boring. Imagine if the whole world of Minecraft was like this.

You might be tempted to modify line 23 to add some randomness to the Y value in order to create height for the plane. Using pure random functions, the line could become something like this:


23 
float y = Random.Range (0, 20);

With the value of 20 being the maximum height possible for a cube.

However, this is pretty much what you are going to end up with:


I don't think you would be able to have players walking around this "world".

This is because pure randomness is simply not suitable for our purpose, and this is where Perlin noise come in. The randomness of this algorithm is smoother and will give us the effect we trying to achieve.

Let's modify that line again implementing the Perlin noise function:

23
int y = (int)(Mathf.PerlinNoise ((x + seed) / detailScale, (z + seed) / detailScale) * heightScale);

This is a standard implementation of the Perling function. As you can see, there is also a seed variable added to the x  and y parameters. If you omit that, every time you run the game you will obtain the same exact landscape. The Perlin function will return a value based on the 2 float parameters passed in. If such parameters are always the same, the value returned will be the same each time. The seed variable is defined as such:


seed = (float)Network.time;

This simply generate a random value which is then added to the 2 parameters.

I could go on and explain the purpose of the 2 parameters detailScale and heightScale, but I believe I will just confuse you, therefore, I created a simple scene which will let you see the effects that thos 2 variables have when used in our method.

Check it out here.

In the end, what you should see is something like this:


I think you will agree with me when I say that this looks much more playable than the previous one.

We can take things a bit further by adding different block types, let's say snow and sand.

Just create 2 other prefabs made of a cube and 2 materials, one yellow(ish) and one white.


We can then modify the Start function of our script using if statements to check the Y value returned by the Perlin function and instantiate a different block prefab accordingly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 int y = ()int(Mathf.PerlinNoise ((x + seed) / detailScale, (z + seed) / detailScale) * heightScale);
 GameObject g;

 if(y<=5)
 g = Instantiate (sand) as GameObject;

 else if(y>=(heightScale-5))
  g = Instantiate (snow) as GameObject;

 else
  g = Instantiate (grass) as GameObject;
    
 g.transform.position = new Vector3 (x, y, z);
 g.transform.SetParent (this.transform);

As you can see, all we do is to check the y value returned by the Perlin method and we instantiate different types of cubes depending on their Y position.

Now we have a more colorful landscape:


Just as a final touch, we could add some water. Create a water prefab cube, set its collider to trigger (so the player can "walk in it") and adjust its color with a slight transparency.

Then we can add the code to spawn water in the landscape. Please bear in mind that this is quick and dirty way to do it, just for the sake of having some water in our scene.

While referring to the previous script, I modified the code starting from line 4, like this:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
 if (y <= 5) {
   g = Instantiate (sand) as GameObject;
   g.transform.position = new Vector3 (x, y, z);
   int tempY = y+1;

   while (tempY < 5) {
    GameObject w = Instantiate (water) as GameObject;
     
     
    Vector3 pos = new Vector3 (g.transform.position.x, tempY, g.transform.position.z);
    w.transform.position = pos;
    tempY++;
   }

  }

All this does is to instantiate the water gameobject on top of the sand blocks. A very cheap way to add some water, but it will work for our purpose.

Here's the scene now:


Conclusion

This is a decent example on how to implement Perlin noise to create a random landscape. Minecraft uses the exact same technique to achieve the effect, and as you can see, it works quite well. We could expand on this (and perhaps, I will) by adding cave generation and digging system, something I might consider doing in the future. To conclude, I just added the FPS controller which you can find in the Standard Assets and I put a nice skybox (this one) so you can test the scene out here.

No comments:

Post a Comment