#### Search Unity

Topics covered
Share

Many creators have used procedural generation to add some diversity to their game. Some notable mentions include the likes of Minecraft, or more recently, Enter the Gungeon and Descenders. This post explains some of the algorithms you can use with Tilemap, introduced as a 2D feature in Unity 2017.2, and RuleTile.

With procedurally created maps, you can make sure that no two plays of your game are the same. You can use various inputs, such as time or the current level of the player to ensure that the content changes dynamically even after the game has been built.

## What is this blog post about?

We’ll take a look at some of the most common methods of creating a procedural world, and a couple of custom variations that I have created.  Here’s an example of what you may be able to create after reading this article. Three algorithms are working together to create one map, using a Tilemap and a RuleTile:

When we’re generating a map with any of the algorithms, we will receive an int array which contains all of the new data. We can then take this data and continue to modify it or render it to a tilemap.

Good to know before you read further:

1. The way we distinguish between what’s a tile and what isn’t is by using binary. 1 being on and 0 being off.
2. We will store all of our maps into a 2D integer array, which is returned to the user at the end of each function (except for when we render).
3. I will use the array function GetUpperBound() to get the height and width of each map so that we have fewer variables going into each function, and cleaner code.
4. I often use Mathf.FloorToInt(), this is because the Tilemap coordinate system starts at the bottom left and using Mathf.FloorToInt() allows us to round the numbers to an integer.
5. All of the code provided in this blog post is in C#.

## Generate Array

GenerateArray creates a new int array of the size given to it. We can also say whether the array should be full or empty (1 or 0). Here’s the code:

```public static int[,] GenerateArray(int width, int height, bool empty)
{
int[,] map = new int[width, height];
for (int x = 0; x < map.GetUpperBound(0); x++)
{
for (int y = 0; y < map.GetUpperBound(1); y++)
{
if (empty)
{
map[x, y] = 0;
}
else
{
map[x, y] = 1;
}
}
}
return map;
}
```

## Render Map

This function is used to render our map to the tilemap. We cycle through the width and height of the map, only placing tiles if the array has a 1 at the location we are checking.

```public static void RenderMap(int[,] map, Tilemap tilemap, TileBase tile)
{
//Clear the map (ensures we dont overlap)
tilemap.ClearAllTiles();
//Loop through the width of the map
for (int x = 0; x < map.GetUpperBound(0) ; x++)
{
//Loop through the height of the map
for (int y = 0; y < map.GetUpperBound(1); y++)
{
// 1 = tile, 0 = no tile
if (map[x, y] == 1)
{
tilemap.SetTile(new Vector3Int(x, y, 0), tile);
}
}
}
}```

## Update Map

This function is used only to update the map, rather than rendering again. This way we can use less resources as we aren’t redrawing every single tile and its tile data.

```public static void UpdateMap(int[,] map, Tilemap tilemap) //Takes in our map and tilemap, setting null tiles where needed
{
for (int x = 0; x < map.GetUpperBound(0); x++)
{
for (int y = 0; y < map.GetUpperBound(1); y++)
{
//We are only going to update the map, rather than rendering again
//This is because it uses less resources to update tiles to null
//As opposed to re-drawing every single tile (and collision data)
if (map[x, y] == 0)
{
tilemap.SetTile(new Vector3Int(x, y, 0), null);
}
}
}
}```

## Perlin Noise

Perlin noise can be used in various ways. The first way we can use it is to create a top layer for our map. This is as simple as just getting a new point using our current x position and a seed.

### Simple

This generation takes the simplest form of implementing Perlin Noise into level generation. We can use the Unity function for Perlin Noise to help us, so there is no fancy programming going into it. We are also going to ensure that we have whole numbers for our tilemap by using the function Mathf.FloorToInt().

```public static int[,] PerlinNoise(int[,] map, float seed)
{
int newPoint;
//Used to reduced the position of the Perlin point
float reduction = 0.5f;
//Create the Perlin
for (int x = 0; x < map.GetUpperBound(0); x++)
{
newPoint = Mathf.FloorToInt((Mathf.PerlinNoise(x, seed) - reduction) * map.GetUpperBound(1));

//Make sure the noise starts near the halfway point of the height
newPoint += (map.GetUpperBound(1) / 2);
for (int y = newPoint; y >= 0; y--)
{
map[x, y] = 1;
}
}
return map;
}```

This is how it looks rendered onto a tilemap:

### Smoothed

We can also take this function and smooth it out. Set intervals to record the Perlin height, then smooth between the points. This function ends up being slightly more advanced, as we have to take into account Lists of integers for our intervals.

```public static int[,] PerlinNoiseSmooth(int[,] map, float seed, int interval)
{
//Smooth the noise and store it in the int array
if (interval > 1)
{
int newPoint, points;
//Used to reduced the position of the Perlin point
float reduction = 0.5f;

//Used in the smoothing process
Vector2Int currentPos, lastPos;
//The corresponding points of the smoothing. One list for x and one for y
List<int> noiseX = new List<int>();
List<int> noiseY = new List<int>();

//Generate the noise
for (int x = 0; x < map.GetUpperBound(0); x += interval)
{
newPoint = Mathf.FloorToInt((Mathf.PerlinNoise(x, (seed * reduction))) * map.GetUpperBound(1));
}

points = noiseY.Count;
```

For the first part of this function, we’re first checking to see if the interval is more than one. If it is, we then generate the noise. We do this at intervals to allow for smoothing. The next part is to work through smoothing the points.

```//Start at 1 so we have a previous position already
for (int i = 1; i < points; i++)
{
//Get the current position
currentPos = new Vector2Int(noiseX[i], noiseY[i]);
//Also get the last position
lastPos = new Vector2Int(noiseX[i - 1], noiseY[i - 1]);

//Find the difference between the two
Vector2 diff = currentPos - lastPos;

//Set up what the height change value will be
float heightChange = diff.y / interval;
//Determine the current height
float currHeight = lastPos.y;

//Work our way through from the last x to the current x
for (int x = lastPos.x; x < currentPos.x; x++)
{
for (int y = Mathf.FloorToInt(currHeight); y > 0; y--)
{
map[x, y] = 1;
}
currHeight += heightChange;
}
}
}
```

The smoothing happens through the following steps:

1. Get the current position and the last position
2. Get the difference between the two positions, the key information we want is the difference in the y-axis
3. Next, we determine how much we should change the hit by, this is done by dividing the y difference by the interval variable.
4. Now we can start setting the positions. We’ll work our way down to zero
5. When we do hit 0 on the y-axis, we will add the height change to the current height and repeat the process for the next x position
6. Once we have done every position between the last position and the current position, we will move on to the next point

If the interval is less than one, we simply use the previous function to do the work for us.

``` else
{
//Defaults to a normal Perlin gen
map = PerlinNoise(map, seed);
}

return map;
```

Let’s see how it looks rendered:

## Random Walk

### Random Walk Top

The way this algorithm works is by flipping a coin. We then get one of two results. If the result is heads, we move up one block, if the result is tails we instead move down one block. This creates some height to our level by always moving either up or down. The only downside to this algorithm is that it looks very blocky. Let’s take a look at how it works.

```public static int[,] RandomWalkTop(int[,] map, float seed)
{
//Seed our random
System.Random rand = new System.Random(seed.GetHashCode());

//Set our starting height
int lastHeight = Random.Range(0, map.GetUpperBound(1));

//Cycle through our width
for (int x = 0; x < map.GetUpperBound(0); x++)
{
//Flip a coin
int nextMove = rand.Next(2);

//If heads, and we aren't near the bottom, minus some height
if (nextMove == 0 && lastHeight > 2)
{
lastHeight--;
}
//If tails, and we aren't near the top, add some height
else if (nextMove == 1 && lastHeight < map.GetUpperBound(1) - 2)
{
lastHeight++;
}

//Circle through from the lastheight to the bottom
for (int y = lastHeight; y >= 0; y--)
{
map[x, y] = 1;
}
}
//Return the map
return map;
}
```

This generation gives us more of a smooth height compared to the Perlin noise generation.

### Random Walk Top Smoothed

This generation gives us more of a smooth height compared to the Perlin noise generation.

This Random Walk variation allows for a much smoother finish than the previous version. We can do this by adding two new variables to our function:

• The first variable is used to determine how long we have held our current height. This is an integer and is reset when we change the height.
• The second variable is an input for the function and is used as our minimum section width for the height. This will make more sense when you have seen the function

Now we know what we need to add. Let’s have a look at the function:

```public static int[,] RandomWalkTopSmoothed(int[,] map, float seed, int minSectionWidth)
{
//Seed our random
System.Random rand = new System.Random(seed.GetHashCode());

//Determine the start position
int lastHeight = Random.Range(0, map.GetUpperBound(1));

//Used to determine which direction to go
int nextMove = 0;
//Used to keep track of the current sections width
int sectionWidth = 0;

//Work through the array width
for (int x = 0; x <= map.GetUpperBound(0); x++)
{
//Determine the next move
nextMove = rand.Next(2);

//Only change the height if we have used the current height more than the minimum required section width
if (nextMove == 0 && lastHeight > 0 && sectionWidth > minSectionWidth)
{
lastHeight--;
sectionWidth = 0;
}
else if (nextMove == 1 && lastHeight < map.GetUpperBound(1) && sectionWidth > minSectionWidth)
{
lastHeight++;
sectionWidth = 0;
}
//Increment the section width
sectionWidth++;

//Work our way from the height down to 0
for (int y = lastHeight; y >= 0; y--)
{
map[x, y] = 1;
}
}

//Return the modified map
return map;
}```

As you can see from the gif below, the smoothing of the random walk algorithm allows for some nice flat pieces within the level.

## Conclusion

I hope this has inspired you to start using some form of procedural generation within your projects. If you want to learn more about procedural generating maps, check out the Procedural Generation Wiki or Roguebasin.com, which are both great resources.

You can look out for the next post in the series to see how we can use procedural generation to create cave systems.