2d Terrain generation using Perlin Noise (Tile Based)

Elgoog picture Elgoog · Apr 19, 2012 · Viewed 9.1k times · Source

I have been looking into perlin noise for an application I'm working on its basically a '2 dimensional side view' looking game (simulation). I don't fully understand how perlin noise works but get the general idea.

I have found this article with code Perlin Noise and decided to give it ago, the way I have world gen set up at the moment is each 'chunk' has 16x16 tiles and I have just stated that if Y >= HEIGHT / 2 make all tiles 'solid' else 'air' so that's working.. but the land is very flat as you would imagine. Thats where I thought the perlin noise would come in, so I used the code found at the above website, but I am not to sure on how to translate the noise to solid/air tiles.

As you can see not very realistic terrain enter image description here

Im more looking for the terrain to be like below. should look like mountains and rolling hills) Im not worried about the sharp edges (square) enter image description here

Im not sure how to iterate through the tile array to turn the noise into above

At the moment I am iterating through the tile array (2d) like so but as you can see from the first picture it just randomizes whether the tile is solid or not, It does not create any rolling hills or mountains.

for(int Y = 0;Y < HEIGHT;++Y) {
    for(int X = 0;X < WIDTH;++X) {
        Tile Tile;
        if(noise(X,Y) < 0) {
            Tile.Type = BLOCK;
        } else {
            Tile.Type = Air;
        }

        // Then I push the Tile on to a vector

I should also mention that to the right of this 16x16 'chunk' there is another chunk which needs to be match up with this one and same goes with the next chunk after that. Chunks shouldn't be the same as previous just make it look like they are not separate chunks, if that makes sense.

Any help is greatly appreciated! Thank you

UPDATE

I have changed my perlin functions to that of this and I have implemented what phresnel suggested:

    PerlinNoise *test=new PerlinNoise(0.25, 6);    //0.25 is Persistance 6 is Octaves

        float f,h;
        float seed = 804.343;

        for (int X=0; X!=MAP_WIDTH; ++X) {

            f = test->GetNoise((X * ChunkX) * seed);

            h = f * MAP_HEIGHT + (MAP_HEIGHT / 2);

            for (int y=0; y<h; ++y) {
                Tile tempTile;
                tempTile.Tile = TILE_NONE;
                Tiles[X][y] = tempTile;
            }
            for (int y=h; y<MAP_HEIGHT; ++y) {
                Tile tempTile;
                tempTile.TileID = TILE_BLOCK;
                Tiles[X][y] = tempTile;
            }
        }

After doing this it looks a lot better, I X * ChunkX for each noise as if I didn't every chunk would be the same, so now it looks like the following enter image description here

As you can see the terrain is more of what I want than before but still doesn't much look like real terrain, as in its not very smooth, is there anything I can do to smooth the overall look of it to create Rolling hills and plains

I removed the creation of tiles under that noise values so its easier to see the range enter image description here

As you can see it looks OK, the values just need to be 'smoothed' out so they don't look like pseudo-random values. I'm also wondering why the first chunk as seen on the left has the same values right the way through.

Thanks.

Answer

Sebastian Mach picture Sebastian Mach · Apr 19, 2012

Your results are fine in that you have islands, continents, ponds and seas. Perlin noise helps you in generating random terrain of non-random character.

You basically reduce the (mathematically) infinite resolution of the noise function into a discrete set of just two different tiles, together with a very limited resolution of the block. Given that, your result seems fine.

Different things may improve your result. For example, lerp between your tile graphics:

// Exemplary p-code, fine-tuning required
Color color_at (x, y) {
    Real u = x / width, v = y / height
    Real  f  = min (max(noise(u,v), 0), 1);
    Color c  = water*f + hill*(1-f);
    return c;
}

You could generalize your lerp function for any tile-count.

Another possibility for sharp edges is inspired by Voronoi diagrams (1). You have the same function as above, but instead of interpolating, you take the tile that is nearest to the noise' value:

Color color_at (x, y) {
    Real u = x / width, v = y / height
    Real  f  = min (max(noise(u,v), 0), 1);
    if (f<0.5) return water;
    else return hill;
}

This is the same as yours, except that you do it pixel-wise. Again, this could be generalized.

Once you have a nice setup, you can use noise for everything: Plant/Tree distribution, Enemy distribution, animations (this would be a 1d-noise function), etc.


Edit as for your comment:

Contiguous 1d-terrain:

If you need a side-view like in a classic 2d-jump and run, think of a 1d-noise function, iterate from 0 to image-width, at each stop take a sample f of the noise function, transform f into screenspace, then every pixel above will be part of the sky, every pixel below will be taken from your tiles:

for (int x=0; x!=width; ++x) {

    // Get noise value:
    f = noise1d (x/width);
    h = f * height + (height/2);  // scale and center at screen-center

    // Select tile:
    Tile *tile;
    if (h < waterLimit) {
        h    = waterLimit;
        tile = waterTile;
    } else {
        tile = terrainTile;
    }

    // Draw vertical line:
    for (int y=0; y<h; ++y)
        setPixel (x, y, skyColor(x,y));
    for (int y=h; y<height; ++y)
        setPixel (x, y, tileColor(x,y, tile));
}

Again, exemplary pseudo code.

Full 2d-levels in your current setup:

The noise function is very flexible. If you want less blue and more green, just lower the constant term, i.e. instead of if(h < 0) -> blue, do if(h < -0.25) -> blue.


1: I have never tried this approach as I am into 3d-terrain rendering (picogen.org)