Off The Clock: Discovering a Solution for Generating 2D Maps in Gaming
When I’m not building fintech solutions at Kunai, I’m often engaged in other types of creative problem solving in my free time. One great example of this is my years-long endeavor to create a simple computerized clone of the old Hasbro board game “Black Tower.”
Over time, my efforts to replicate the board game produced a full-fledged, 2D, Zelda-like Role Playing Game, or ZRPG. And what does any good 2D RPG game need? An unknown world for players to explore.
In gaming, this is usually accomplished with procedurally generated maps. A program is written to automatically produce random areas and terrain as players enter them. I wanted to do the same. I planned to write a program that mimicked real-world terrains and looked like what you'd see in the “Legend Of Zelda” or “Age of Empires” games. However, I had no idea how to accomplish this.
My first attempt involved using simple terrain tiles. This didn’t go well. If you randomly place the tiles you get something similar to white noise, which wouldn't work for my game and didn't look realistic at all. Here is a generated map of random tiles to give you an idea of what that attempt looked like:
Not very good-looking, right? It certainly doesn't mimic actual world terrains. I needed something less granular, so I tried a different approach.
Mapping by Numbers
With some help from Google searches and various online game development communities, I came up with a solution to my problem: a 2D array of random numbers.
Here’s how it works. First, you create a 2D array that is the same height and width as the tile map you wish to generate. Fill this array with random integers.
These integers can be anything, but if you're trying to generate a realistic-looking terrain map I recommend keeping the gap between the minimum and maximum values small. I like to use a range of 1-20, but of course this all depends on how many levels of height and types of terrain you want. Dramatic cliffs, for example, will require dramatic gaps in height.
It’s also important to consider whether you're having certain types of terrain correspond to a single number or range of numbers. You could use 1 for water, 2 for dirt, or 1-4 for water, and 5-10 for dirt, for example. The second gives you more flexibility, and I’ll get into more detail on it further down.
Once you’ve created this array, loop through all the cells and set the value of each cell to the average of the cells around it.
In the above loop, for example, we would set the value of the top left green cell to the average of the numbers adjacent to it rounded to the nearest whole number. In this instance, 19, 0, and 43 give us an average of 21.
Since the green cell is a corner cell, it only gets the average of three surrounding cells. Elsewhere in the array, non-edge cells will take on the average of eight adjacent cells. The red cell containing the number 35 with yellow around it would be set to 27, for example, which is the average of the total of all 8 squares adjacent to it.
Do this for each cell in the grid. It may not seem like much now, but we’re getting there!
More Looping Means More Realism
After looping through the cells of your array once, do it again. In fact, you should repeat the process at least twice. The more times you average the array of cells, the larger the areas of each number gets and the less different numbers there are.
This sounds confusing, but it’s important. Each number corresponds to a different kind of texture image. Without repeat looping, you’ll simply generate maps that look like the white noise map I posted above. Repeated looping solves this problem.
With just one additional loop of cell averaging, we get maps that look somewhat like this:
After five more loops of averaging, however, we get maps that look like this. Notice how some of the terrain types are completely gone:
Fill Gaps and Create More Detailed Maps with Ranges
If you have more numbers than tile textures, or even if you want to play with how your maps look, you can use numerical ranges for setting textures instead of single numbers.
Say, for example, that you assigned the following numbers to these terrain types:
- 1-4: water
- 5-10: dirt
- 11-15: grass
- 16+: stone
With this method, you can have a much larger gap between the minimum and maximum numbers and guarantee that all terrains will exist on the map. This will generate more diverse geography than the map above, which has each tile type corresponding to a single number. With such limited range, not all of the possible types of terrains made it to the map.
For even more unique maps, you could try setting a few horizontal, diagonal, or vertical lines of cells to very low or very high numbers to create mountains, rivers, or other geographic features. Another idea is to have different number maps used in tandem, and use them to automatically generate other items like trees or rocks. For example, say you create a nitrogen map and use both the height map and nitrogen map in your game. You could set it up so that if the value of a tile on the height map is between five and ten and corresponds with a nitrogen map tile that is greater than five, it automatically generates a bush or a tree on that tile.
You know a developer loves what they do when instead of taking their work home with them, they find ways to create work in the games they play. I hope that you've learned something in this tutorial that will be useful in 2D map making or any other creative development endeavors.