Contact Home

Procedurally Generating Wrapping World Maps in Unity C# – Part 1

Posted on: January 7th, 2016 by admin 25 Comments

Table of Contents

In Part 1 (this article):

  1. Introduction
  2. Noise Generation
  3. Getting Started
  4. Generating the Height Map

In Part 2:

  1. Wrapping the Map on One Axis
  2. Wrapping the Map on Both Axis
  3. Finding Neighbors
  4. Bitmasking
  5. Flood Filling

In Part 3:

  1. Generating the Heat Map
  2. Generating the Moisture Map
  3. Generating Rivers

In Part 4:

  1. Generating Biomes
  2. Generating Spherical Maps


Introduction

I always like to start these tutorials with an example of what the final output will resemble:

map

The map representations you see above are:


  • Heat Map (Top Left)
  • Height Map (Top Right)
  • Moisture Map (Bottom Right)
  • Biome Map (Bottom Left)

These are the kind of maps I will be showing you how to create, with these tutorials


Noise Generation

There are a multitude of different noise generators on the internet, most of which are open sourced. There is no need to re-invent the wheel here, so I opted to use a custom port of the Accidental Noise library.

The C# port was done by Nikolaj Mariager.

Some minor adjustments were made to his port in order to get it to work properly in Unity.


Getting Started

First, we need some kind of container to store the data that we are going to generate.

So, let’s start off by creating a MapData class. The Min and Max variables will serve as a way to keep track of our generated upper and lower limits.

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 are also going to create a Tile class, which will be used to eventually create our Unity gameobjects, from our generated data.

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

In order to see what is going on, we will need some sort of visual representation of the data. For this we create a new TextureGenerator class.

For the time being, this class will simply generate a black and white representation 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;
	}
	
}

We will expand on this Texture Generator soon.


Generating the Height Map

Since I decided that the maps are going to be fixed size, we need to set a map Width and Height. We also need a few adjustable parameters for the noise generator.

We are going to expose these variables to the Unity Inspector, as it will make tuning the maps a lot easier.

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

Have a look at the code, along with the comments:

using UnityEngine;
using AccidentalNoise;

public class Generator : MonoBehaviour {

	// Adjustable variables for Unity Inspector
	[SerializeField]
	int Width = 256;
	[SerializeField]
	int Height = 256;
	[SerializeField]
	int TerrainOctaves = 6;
	[SerializeField]
	double TerrainFrequency = 1.25;

	// Noise generator module
	ImplicitFractal HeightMap;
	
	// Height map data
	MapData HeightData;

	// Final Objects
	Tile[,] Tiles;
	
	// Our texture output (unity component)
	MeshRenderer HeightMapRenderer;

	void Start()
	{
		// Get the mesh we are rendering our output to
		HeightMapRenderer = transform.Find ("HeightTexture").GetComponent<MeshRenderer> ();

		// Initialize the generator
		Initialize ();
		
		// Build the height map
		GetData (HeightMap, ref HeightData);
		
		// Build our final objects based on our data
		LoadTiles();

		// Render a texture representation of our map
		HeightMapRenderer.materials[0].mainTexture = TextureGenerator.GetTexture (Width, Height, Tiles);
	}

	private void Initialize()
	{
		// Initialize the HeightMap Generator
		HeightMap = new ImplicitFractal (FractalType.MULTI, 
		                               BasisType.SIMPLEX, 
		                               InterpolationType.QUINTIC, 
		                               TerrainOctaves, 
		                               TerrainFrequency, 
		                               UnityEngine.Random.Range (0, int.MaxValue));
	}
	
	// Extract data from a noise module
	private void GetData(ImplicitModuleBase module, ref MapData mapData)
	{
		mapData = new MapData (Width, Height);

		// loop through each x,y point - get height value
		for (var x = 0; x < Width; x++)
		{
			for (var y = 0; y < Height; y++)
			{
				//Sample the noise at smaller intervals
				float x1 = x / (float)Width;
				float y1 = y / (float)Height;

				float value = (float)HeightMap.Get (x1, y1);

				//keep track of the max and min values found
				if (value > mapData.Max) mapData.Max = value;
				if (value < mapData.Min) mapData.Min = value;

				mapData.Data[x,y] = value;
			}
		}	
	}
	
	// Build a Tile array from our data
	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];
				
				//normalize our value between 0 and 1
				value = (value - HeightData.Min) / (HeightData.Max - HeightData.Min);
				
				t.HeightValue = value;

				Tiles[x,y] = t;
			}
		}
	}

}
Now, if we run this code, we get the following output texture:

map

Doesn't look like much yet, however, it is a very good start. We have an array of data, containing values between 0 and 1, with some very interesting patterns.

Now, we need to start assigning some meaning to this data. For example, we can say that anything that is less than 0.4 is considered water.

We could change the following in our TextureGenerator, setting everything that is less than 0.4 to blue, and everything else to white:

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

Doing so, we then get the following output:

map

Now we are getting somewhere. We can start to see some shapes appear with this simple rule. Let's take this a step further.

Let's add some more adjustable variables to our Generator class. These will define what our height values will assign with.

	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;

Let's also add some custom colours to our 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);

Adding in all these rules, in a similar fashion, and we then get the following results:

map

Now we have a lovely Height Map, with a nice texture representing it.

You may download the source code on github for part 1 here.

Continue to Part 2 of this series.

25 Responses

Vadim Ivshin commented on January 31, 2016 at 12:50 pm

Great article, looking forward to reading the next part. I translated it into Russian and posted it to collective blog for Russian-speaking tech specialists.

koke commented on October 16, 2016 at 3:15 pm

so using accidentalNoise gives an error in unity, the type accidental… coulnt be found, any ideas?

Nrin commented on October 29, 2016 at 9:51 pm

Would it be possible to adapt this tutorial to work for infinite generation? I’ve attempted to but I don’t think there is a way to do it. I can’t normalize the data since there is no min or max value because because of the unbounded nature of infinite generation. If I try to normalize “chunks” of area they end up not lining up at the edges so I threw that idea out. I then switched from MULTI to FRACTIONALBROWNIANMOTION and it gave better results using the same terrain height values as in the tutorial but the map just ends up looking boring. MULTI gives much better terrain features.

Tyler commented on November 29, 2016 at 12:49 am

In your MapData class, you have the min and max values swapped.

    Steffen commented on December 13, 2016 at 4:13 pm

    No, it’s the correct way to initialize them.
    Look into Generator.GetData(…):
    if (value > mapData.Max) mapData.Max = value;
    if (value < mapData.Min) mapData.Min = value;
    Think about what would happen if you init min with float.MinValue and max with float.MaxValue. They wouldn't change at all.

Johan commented on November 12, 2017 at 12:41 am

Can you make this into 3D?

This is amazing, tutorial for 3D conversion please. And maybe pathfinding.

Jonathan commented on February 1, 2018 at 8:16 pm

Thanks a lot for that tutorial but i have a question and it is that. How do i install that Fractal library from GitHb into Visual Studio 2015?

Do i have to Install it into the Unity Project or into Visual Studio?

Merci.

    admin commented on February 1, 2018 at 8:28 pm

    I just drop the source code into a /AccidentalNoise folder, and reference it from there.

    You could compile it to a DLL and reference it if you prefer.

    There is no nuget installer, or anything fancy for it.

      Jonathan commented on February 1, 2018 at 9:49 pm

      Thank you i already solved that. The problem i am having now is that the ImplicitFractal does not contain a constructor for its 6 arguments that you input (From an error i am having)

      I am trying to understand your code. Thanks

      Jonathan commented on February 1, 2018 at 9:51 pm

      From the documents it shows

      CImplicitFractal(unsigned int type, unsigned int basistype, unsigned int interptype);

      How did you overloaded it?

Jonathan commented on February 4, 2018 at 7:55 pm

Hello JG, When in your code you instantiate the Tiles as game objects into Unity? I checked the Part1 code and followed up into the Final code but i cant see where you do the instantiation for the Tile and the texture.

Merci beaucoup!

    admin commented on February 4, 2018 at 7:59 pm

    I do not provide that code. I started working on an article that deals with rendering the data in a 2D world, but I have not yet published this work.
    There are various ways you can render this data. For now it would be up to you to decide how to do this.

Tristan commented on March 3, 2018 at 1:29 pm

Hey, thank you for this very good tutorial.

I can’t figure how do you render the map in Unity, did you add your Generator script as a component to a Sprite or an other GameObject ?

Some explications on what to bind on which GameObject are welcomed 🙂

    admin commented on March 3, 2018 at 10:33 pm

    You can download the project from the github link, and open the demo scene. This will demonstrate how it is all setup.
    But yes, the Generator is added in as a component, which creates the noise data. Then textures are created from this data, and rendered in the scene via a quad mesh, or sprite renderer.

Tristan commented on March 3, 2018 at 10:58 pm

Thank you, I got it now. 🙂

Great work btw !

Cassius commented on May 20, 2018 at 2:36 pm

Hi.
i got an error in unity when i installed the accidental noise library saying:

“The type or namespace name `ThreadLocal’ could not be found. Are you missing an assembly reference?”

and i’m not sure why.
I put the source code into unity project files and assets and so on. Am i doing something wrong here?

Thank’s in advance!

Ziga commented on June 8, 2018 at 3:26 pm

Hi,
Just curious, couldn’t have you just stored all the noise values and normalized them in the Tiles array? Why the MapData object?

    admin commented on June 8, 2018 at 3:34 pm

    Yes you can get rid of the MapData object. I had kept this as a way to keep a copy of the original raw data, but it is not needed.

sizlar.com commented on July 19, 2018 at 9:11 am

Quality content is the key to invite the people to go to see the web page, that’s what
this web site is providing.

kwatman commented on February 10, 2019 at 11:21 am

im stuck to what game objects do i attach this ?

Respond

Leave a Reply to admin

You must be logged in to post a comment.