Procedurally generated world maps on Unity C #, part 1

Original author: Jon Gallant
  • Transfer
  • Tutorial
image

In this series of articles, we will learn how to create procedurally generated world maps using Unity and C #. The cycle will consist of four articles.

Contents

Part 1 (this article):

Introduction
Generating noise
Getting started
Generating a height map

Part 2 :

Minimize a map on one axis
Minimize a map on both axes
Find adjacent elements
Bitmasks
Fill

Part 3 :

Generate a heat map
Generate a humidity map
Generate rivers Generate

Part 4 :

Generate Biomes
Spherical Map Generation

Introduction

In these tutorial articles, we will create procedurally generated maps that are similar to the

image

following : Here are the following maps:
  • heatmap (top left)
  • height map (upper right)
  • humidity map (bottom right)
  • biome map (bottom left)

In the following articles in this series, we will learn how to manage the data of these maps. We will also consider a method of projecting maps onto spherical surfaces.

Noise

generation There are many different noise generators on the Internet, most of which are open source, so there is no need to reinvent the wheel. I borrowed a ported version of the Accidental Noise library .

Porting to C # is done by Nikolaj Mariager .

For proper operation in Unity, minor changes have been made to the ported version.

You can use any noise generator you like. All techniques listed in the article can be applied to other noise sources.

Beginning of work

First, we need to create a container for storing the data that we will generate.

Let's start by creating the MapData class. The variables Min and Max are needed to track the lower and upper limits of the generated values.

public class MapData { 
    public float[,] Data;
    public float Min { get; set; }
    public float Max { get; set; }
    public MapData(int width, int height)
    {
        Data = new float[width, height];
        Min = float.MaxValue;
        Max = float.MinValue;
    }
}

We will also create the Tile class, which will later be used to create Unity game objects from the generated data.

public class Tile
{
    public float HeightValue { get; set; }
    public int X, Y;
    public Tile()
    {
    }
}

To see what happens, we need a graphical representation of the data. To do this, we will create a new TextureGenerator class.

So far, this class will create a black and white display of our data.

using UnityEngine;
public static class TextureGenerator {
    public static Texture2D GetTexture(int width, int height, Tile[,] tiles)
    {
        var texture = new Texture2D(width, height);
        var pixels = new Color[width * height];
        for (var x = 0; x < width; x++)
        {
            for (var y = 0; y < height; y++)
            {
                float value = tiles[x, y].HeightValue;
                //Set color range, 0 = black, 1 = white
                pixels[x + y * width] = Color.Lerp (Color.black, Color.white, value);
            }
        }
        texture.SetPixels(pixels);
        texture.wrapMode = TextureWrapMode.Clamp;
        texture.Apply();
        return texture;
    }
}

Soon we will expand this class.

Generating a height map

I decided that the maps will be of a fixed size, so I need to specify the Width and Height of the map. We will also need customizable parameters for the noise generator.

We will make this data visible in the Unity Inspector so that customizing maps is much easier.

The Generator class initializes the Noise module, generates DEM data, creates an array of tiles, and then generates a texture representation of this data.

Here is the code with comments:

using UnityEngine;
using AccidentalNoise;
public class Generator : MonoBehaviour {
    // Настраиваемые переменные для Unity Inspector
    [SerializeField]
    int Width = 256;
    [SerializeField]
    int Height = 256;
    [SerializeField]
    int TerrainOctaves = 6;
    [SerializeField]
    double TerrainFrequency = 1.25;
    // Модуль генератора шума
    ImplicitFractal HeightMap;
    // Данные карты высот
    MapData HeightData;
    // Конечные объекты
    Tile[,] Tiles;
    // Вывод нашей текстуры (компонент unity)
    MeshRenderer HeightMapRenderer;
    void Start()
    {
        // Получаем меш, в который будут рендериться выходные данные
        HeightMapRenderer = transform.Find ("HeightTexture").GetComponent ();
        // Инициализируем генератор
        Initialize ();
        // Создаем карту высот
        GetData (HeightMap, ref HeightData);
        // Создаем конечные объекты на основании наших данных
        LoadTiles();
        // Рендерим текстурное представление нашей карты
        HeightMapRenderer.materials[0].mainTexture = TextureGenerator.GetTexture (Width, Height, Tiles);
    }
    private void Initialize()
    {
        // Инициализируем генератор карты высот
        HeightMap = new ImplicitFractal (FractalType.MULTI, 
                                       BasisType.SIMPLEX, 
                                       InterpolationType.QUINTIC, 
                                       TerrainOctaves, 
                                       TerrainFrequency, 
                                       UnityEngine.Random.Range (0, int.MaxValue));
    }
    // Извлекаем данные из модуля шума
    private void GetData(ImplicitModuleBase module, ref MapData mapData)
    {
        mapData = new MapData (Width, Height);
        // циклично проходим по каждой точке x,y - получаем значение высоты
        for (var x = 0; x < Width; x++)
        {
            for (var y = 0; y < Height; y++)
            {
                //Сэмплируем шум с небольшими интервалами
                float x1 = x / (float)Width;
                float y1 = y / (float)Height;
                float value = (float)HeightMap.Get (x1, y1);
                //отслеживаем максимальные и минимальные найденные значения
                if (value > mapData.Max) mapData.Max = value;
                if (value < mapData.Min) mapData.Min = value;
                mapData.Data[x,y] = value;
            }
        }   
    }
    // Создаем массив тайлов из наших данных
    private void LoadTiles()
    {
        Tiles = new Tile[Width, Height];
        for (var x = 0; x < Width; x++)
        {
            for (var y = 0; y < Height; y++)
            {
                Tile t = new Tile();
                t.X = x;
                t.Y = y;
                float value = HeightData.Data[x, y];
                //нормализуем наше значение от 0 до 1
                value = (value - HeightData.Min) / (HeightData.Max - HeightData.Min);
                t.HeightValue = value;
                Tiles[x,y] = t;
            }
        }
    }
}

After running the code, we get the following texture:

image

It does not look very interesting so far, but a start has been made. We have a data array containing values ​​from 0 to 1, with a very interesting pattern.

Now we need to give significance to our data. For example, let anything less than 0.4 be considered water. We can change the following in our TextureGenerator by setting all values ​​below 0.4 to blue and above to white:

if (value < 0.4f)
    pixels[x + y * width] = Color.blue;
else
    pixels[x + y * width] = Color.white;

After that, we got the following final image:

image

We are already getting something. Shapes appear matching this simple rule. Let's take the next step.

Add other custom variables to our Generator class. They will indicate the parameters with which the heights are associated.

float DeepWater = 0.2f;
float ShallowWater = 0.4f;  
float Sand = 0.5f;
float Grass = 0.7f;
float Forest = 0.8f;
float Rock = 0.9f;
float Snow = 1;

Also add new colors to the texture generator:

private static Color DeepColor = new Color(0, 0, 0.5f, 1);
private static Color ShallowColor = new Color(25/255f, 25/255f, 150/255f, 1);
private static Color SandColor = new Color(240 / 255f, 240 / 255f, 64 / 255f, 1);
private static Color GrassColor = new Color(50 / 255f, 220 / 255f, 20 / 255f, 1);
private static Color ForestColor = new Color(16 / 255f, 160 / 255f, 0, 1);
private static Color RockColor = new Color(0.5f, 0.5f, 0.5f, 1);            
private static Color SnowColor = new Color(1, 1, 1, 1);

By adding new rules in this way, we get the following results:

image

We have an interesting vertex map with a texture representing it.

You can download the source code for the first part from here: World Generator Part1 .

The second part .

Also popular now: