Wednesday, June 15, 2011

Terrain Generation Explained

Hey guys I have been putting off writing this kind of a post for awhile (mostly because I didn't feel that my code was 'good enough' to show off), but I've had a request and so I will try and explain my method for terrain generation using a mixture of different techniques.

Let me start by saying that my way is definitely not the only way, or even the best way to create terrain; it just happens to work for me (for now). At first, what I tried to use was the Midpoint Displacement Algorithm for creating a rough sketch of ground-level blocks. This method was ok for the time, but I needed something else that gave me a more 'natural' look. This led me to the Perlin Noise algorithm, which turned out to be fairly easy for me to understand from reading a few good explanations of it. Before I put the code into my game, I also looked at techniques for cave generation, which was even easier for me to understand than the Perlin Noise.

For this reason, I decided to integrate the cave generation first as it was the easier of the two. My method for cave generation involves the classic 4-5 rule which states that a block is a wall if the 3x3 region centered on it contains at least 5 walls. Repeating this process n times gives decent looking caves in the map. The entire process is modified slightly in the last two iterations to further refine the shape of the caves and make them appear more 'natural'. Here is some code for example:

 private void CreateCaves(int i) { // i == Number of iterations  
      if (i == 1) {          
         for (int y = Height / 10; y < Height; y++) {            
           for (int x = 0; x < Width; x++) {  
             // Percentage needs to be 60% to make 40% filled  
             // NOTES 06/10: Perlin Noise method functional and not bad,  
             // "Poking holes" cave creation method pretty good.  
             // NOTES 06/14: Making the percentage 55% / 45% is pretty good  
             // with the addition of possibly skipping over the block if the   
             // depth is too little  
             if (rand.Next(100) <= 55) {  
               // PUTTING HOLES IN COMPLETELY FILLED MAP  
               // AS OPPOSED TO PUTTING BLOCKS IN EMPTY MAP  
               blocks[x, y] = LoadBlock('.', x, y);  
             }  
             else if (y < Height / 7)  
               continue;  
           }  
         }  
       }  
       else {  
         int neighboringWalls = 0;  
         string blockName = "";  
         for (int y = Height / 10; y < Height - 1; y++) {  
           for (int x = 1; x < Width - 1; x++) {  
             if (blocks[x - 1, y - 1].Texture != null) {  
               blockName = blocks[x - 1, y - 1].Name;  
               neighboringWalls++;  
             }  
             /////// Some block tests removed to shorten code \\\\\\\\\  
             if (blocks[x - 1, y].Texture != null) {  
               blockName = blocks[x - 1, y].Name;  
               neighboringWalls++;  
             }  
             if (i >= 4) {  
               if (neighboringWalls >= 6)  
                 blocks[x, y] = blocksDictionary[blockName + "SingleTop"];  
             }  
             else {  
               if (neighboringWalls >= 5)  
                 blocks[x, y] = blocksDictionary[blockName + "SingleTop"];  
               else if (neighboringWalls <= 2)  
                 blocks[x, y] = LoadBlock('.', x, y);  
             }  
             neighboringWalls = 0;  
           }  
         }  
       }  
     }  

The main difference between my code and the algorithm in the link I provided is that instead of filling in an empty map, I'm poking holes in a completely filled in map. This in my opinion gives different and better results for me.

After I added the cave generation to the game, I felt confident enough to tackle the Perlin Noise algorithm. Using the method provided in the link I gave above, I produced this code to create my surface terrain:

 private int[] CreatePerlinNoise() {  
       float[] values = new float[Width];  
       int[] realValues = new int[Width];  
       for (int i = 0; i < values.Length; i++) {  
         values[i] = PerlinNoise(i);  
         values[i] *= 10;  
         realValues[i] = (int)Math.Ceiling(values[i]);  
       }  
       return realValues;  
     }  
 private float Noise(int i, int x) {  
     if (i == 0) {  
       x = (x << 13) ^ x;  
       return (float)(1.0 - ((x * (x * x * 557 + 1049) + 2411) & 0x7fffffff) / 1073741824.0);  
     }  
     else if (i == 1) {  
       x = (x << 13) ^ x;  
       return (float)(1.0 - ((x * (x * x * 1303 + 2473) + 3229) & 0x7fffffff) / 1073741824.0);  
     }  
     else if (i == 2) {  
       x = (x << 13) ^ x;  
       return (float)(1.0 - ((x * (x * x * 4441 + 6277) + 7549) & 0x7fffffff) / 1073741824.0);  
     }  
     else {  
       x = (x << 13) ^ x;  
       return (float)(1.0 - ((x * (x * x * 4663 + 6007) + 6961) & 0x7fffffff) / 1073741824.0);  
     }  
 }  
 private float SmoothedNoise(int i, float x) {  
     return Noise(i, (int)x) / 2 + Noise(i, (int)x - 1) / 4 + Noise(i, (int)x + 1) / 4;  
 }  
 private float InterpolatedNoise(int i, float x) {  
     int y = (int)x;  
     float fractionalX = x - y;  
     float v1 = SmoothedNoise(i, y);  
     float v2 = SmoothedNoise(i, y + 1);  
     return MathHelper.Lerp(v1, v2, fractionalX);  
 }  
 private float PerlinNoise(float x) {  
     float total = 0;  
     float p = 0.5f; // persistance  
     int n = 4; // four octaves  
     for (int i = 0; i <= n; i++) {  
       float frequency = 2 ^ i;  
       float amplitude = (float)Math.Pow(p, i);  
       total += InterpolatedNoise(i, x * frequency) * amplitude;  
     }  
     return total;  
 }  

A high-level explanation of what happens in a Perlin Noise algorithm (as I understand it) is that the final product is an array of y values (for height) that is the size of all the x values in the map. To get these values, we basically generate random numbers for every x value in the array and linearly interpolate between them to 'smooth out' the shape of the terrain. The link I provided above was, for me, the best explanation I could find about how to make a Perlin Noise algorithm.

I am by no means the expert on Perlin Noise but if you have any questions please feel free to leave a comment below. This is a very basic overview of my technique for creating terrain and it is by no means the only way or the best way. Also, I intend to modify this technique before my game is complete, so feel free to use my code in your own projects, although it may not work with your specific game.

As always, thanks for reading and I will give a game update as soon as I have one.

1 comment:

  1. "I didn't feel that my code was 'good enough' to show off"

    Nonsense, I've been reading your posts, and I'm in the same frame of mind. I've been playing Minecraft and Terraria and it has encouraged me to start developing a 2D Tile game. I'm not as advanced programmer as you, but I am getting there. I would love to see more of your code to see how you do it. I know there will be other ways to do it, but if your way works, let's see it!

    I think what you're doing is great, keep up the good work! I'll definitely be checking back!

    ReplyDelete