Search code examples
c#unity-game-engineterrainperlin-noiseprocedural-generation

Procedural Island Terrain Generation


Edit: Rewrote my question after trying a few things and made it more specific.

Hi, so I'm creating a mobile RTS game with procedurally generated maps. I've worked out how to create a terrain with a basic perlin noise on it, and tried to integrate https://gamedev.stackexchange.com/questions/54276/a-simple-method-to-create-island-map-mask method to creating an island procedurally. This is the result so far:

enter image description here

The image below from http://www-cs-students.stanford.edu/~amitp/game-programming/polygon-map-generation/ shows the kind of terrain I'm after. The tutorial there is great but would be too intensive, thus the post.

I want the Random Shaped island with Perlin noise generated land mass, surrounded by water.

This is the kind of shaping I want

edit: Basic Perlin terrain gen working now =)

Here is my code. A script attached to a null with a button to activate Begin():

using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;

public class Gen_Perlin : MonoBehaviour {

public float Tiling = 0.5f;
private bool active = false;
public int mapHeight = 10;

public void Begin()
{
    if (active == false) {
        TerrainData terrainData = new TerrainData ();
        const int size = 513;
        terrainData.heightmapResolution = size;
        terrainData.size = new Vector3 (2000, mapHeight, 2000);

        terrainData.heightmapResolution = 513;
        terrainData.baseMapResolution = 1024;
        terrainData.SetDetailResolution (1024, 1024);

        Terrain.CreateTerrainGameObject (terrainData);
        GameObject obj = GameObject.Find ("Terrain");
        obj.transform.parent = this.transform;

        if (obj.GetComponent<Terrain> ()) {
            GenerateHeights (obj.GetComponent<Terrain> (), Tiling);
        }
    } else {
        GameObject obj = GameObject.Find ("Terrain");
        if (obj.GetComponent<Terrain> ()) {
            GenerateHeights (obj.GetComponent<Terrain> (), Tiling);
        }
    }
}

public void GenerateHeights(Terrain terrain, float tileSize)
{
    Debug.Log ("Start_Height_Gen");
    float[,] heights = new float[terrain.terrainData.heightmapWidth, terrain.terrainData.heightmapHeight];

    for (int i = 0; i < terrain.terrainData.heightmapWidth; i++)
    {
        for (int k = 0; k < terrain.terrainData.heightmapHeight; k++)
        {
            heights[i, k] = 0.25f + Mathf.PerlinNoise(((float)i / (float)terrain.terrainData.heightmapWidth) * tileSize, ((float)k / (float)terrain.terrainData.heightmapHeight) * tileSize);
            heights[i, k] *= makeMask( terrain.terrainData.heightmapWidth, terrain.terrainData.heightmapHeight, i, k, heights[i, k] );
        }
    }
    terrain.terrainData.SetHeights(0, 0, heights);
}

public static float makeMask( int width, int height, int posX, int posY, float oldValue ) {
    int minVal = ( ( ( height + width ) / 2 ) / 100 * 2 );
    int maxVal = ( ( ( height + width ) / 2 ) / 100 * 10 );
    if( getDistanceToEdge( posX, posY, width, height ) <= minVal ) {
        return 0;
    } else if( getDistanceToEdge( posX, posY, width, height ) >= maxVal ) {
        return oldValue;
    } else {
        float factor = getFactor( getDistanceToEdge( posX, posY, width, height ), minVal, maxVal );
        return oldValue * factor;
    }
}

private static float getFactor( int val, int min, int max ) {
    int full = max - min;
    int part = val - min;
    float factor = (float)part / (float)full;
    return factor;
}

public static int getDistanceToEdge( int x, int y, int width, int height ) {
    int[] distances = new int[]{ y, x, ( width - x ), ( height - y ) };
    int min = distances[ 0 ];
    foreach( var val in distances ) {
        if( val < min ) {
            min = val;
        }
    }
    return min;
}

}


Solution

  • Yeah. The article in question is using a waaay complex method.

    The best way of doing this is to take a function that represents the shape of your basic island, with height values between 0 and 1. For the type of island in the picture, you'd basically want something which smoothly rises from the edges, and smoothly dips back to zero where you want lakes.

    Now you either add that surface to your basic fractal surface (if you want to preserve spikiness at low elevations) or you multiply it (if you want lower elevations to be smooth). Then you define a height, below which is water.

    Here is my very quick go at doing this, rendered with Terragen:

    Ring Island - Quick and Dirty attempt

    I used a function that rises in a ring from the edge of the map to halfway to the middle, then drops again, to match a similar shape to the one from the article. In practice, you might only use this to get the shape of the island, and then carve the bit of terrain that matches the contour, and bury everything else.

    I used my own fractal landscape generator as described here: https://fractal-landscapes.co.uk for the basic fractal.

    Here is the C# code that modifies the landscape:

    public void MakeRingIsland()
    {
        this.Normalize(32768);
        var ld2 = (double) linearDimension / 2;
        var ld4 = 4 / (double) linearDimension;
        for (var y = 0u; y < linearDimension; y++)
        {
            var yMul = y * linearDimension;
            for (var x = 0u; x < linearDimension; x++)
            {
                var yCoord = (y - ld2) * ld4;
                var xCoord = (x - ld2) * ld4;
                var dist = Math.Sqrt(xCoord * xCoord + yCoord * yCoord);
                var htMul = dist > 2 ? 0 : 
                    (dist < 1 ? 
                        dist + dist - dist * dist : 
                        1 - (dist - 1) * (dist - 1));
                var height = samples[x + yMul];
                samples[x + yMul] = (int) (height + htMul * 32768);
            }
        }
    }