Combining procedural map generation with handcrafted details
If you’re creating a game that has a large area of land for the player to interact with, you will probably create a very simplistic map to use while you create the features of the game. It will have the bare minimums required to test your features and, since it will be thrown away later, will probably look like a hot mess. On a small team with limited resources, we decided that building the final maps would happen when the features were complete, otherwise we ran the risk of rebuilding maps from scratch or doing heavy modifications to deal with new or updated features.
At first, Idle Realm had a map that was 128×128 tiles in size and was populated with nothing but water, with the exception of the center of the map, which had 6 of each type of land type next to each other. The map was hard-coded into the source code and was fairly ugly, but got the job done.
When the features were nearly finished, it was time to start sending the build out to more beta testers and for that, I needed a real map saved in a file. All maps in Idle Realm are typically large islands, always surrounded by a border of at least 2-3 tiles of water. To make this happen, an in-game map editor was created. I personally like working with in-game editors because I can modify the content in real-time and see the results. Also, painting a map while in the game is fun!
The editor’s first purpose was to simply let me pick a terrain type and then paint the tiles on the map. There was no configurable brush size, so it was one tile at a time. Also, the map was still hard-coded to be 128×128 in size. It turns out that building such a large map was a pain with this method, so I dialed down the test map by creating a smaller 64×64 sized island in the middle of a giant water map. Surprisingly, having the map be this small felt good in the game and if I had done the 128×128 size version, it would have felt too large. In the end, I settled on a map that is about 80×80.
More testing and more updates to the map created the need to save and load the map into separate files, pick a size for the terrain brush and toggle the ability to overwrite water tiles (useful when you have a larger brush and want to add sand to the edges of the water without killing your water tiles in the process). This was really all I needed to make maps, but making a full size map with all the details still took close to an hour.
Time for some procedural generation!
The first way to add randomization to the editor was via the terrain brush size. Terrain brushes, when painting an area with a radius of 20, looked pretty bad. It converted everything to that one tile!
To deal with this, I added a fill percentage to the brush. The editor would grab all tiles within the radius of the brush and then roll the dice. A fill rate for 50 would convert 50% of the tiles into the new tile, and the other tiles would be converted to grass. Painting a mountain range with a fill rate for 60 made for a pretty nice grouping of mountains in a line, sand near the water with a fill rate of 80 worked out well too. Littering a large area of grass with 10% bushes or trees worked great. With this tool, the map creation process dropped down to 40 minutes.
When analyzing the amount of time it took to build a map, I discovered about half of the time was spent building the silhouette of the island. To help in that area, I added a new button to the editor called Generate that would create a new island full of grass, surrounded by water. The generation process involved using perlin noise for a height map combined with a radial gradient at the center of the map to push the terrain further into the water near the edges of the map. This resulted in some fairly quick, fairly nice island shapes!
Even with the generated island, I would need to touch up some of the island edges to make them more interesting. Also, it was all grass and I needed dirt, trees, mountains, rivers and more to be added by hand. Still, the map creation process dropped down to about 20 minutes after this change.
Of course, I wanted even more to be generated randomly, so I added more layers of noise to the generation process to get heat and moisture maps, combining them with the height maps to generate a biome map. The great thing about the biome map was that I could define the odds of all the terrain types appearing in the biome and then select every tile of a biome type and roll more dice, generating all the terrain needed for the biomes. Deserts would be mostly sand with some dirt and trees appearing, high mountains would be mostly dirt and mountain terrain, etc.
Now, I would simply generate an island, look at the overall look and biome layout and if I didn’t like it, I would generate another. Once I found one I liked, I needed to make some customized changes to make certain areas feel better as well as add rivers, because those still needed to be hand crafted. Still, the whole process would take around 7 minutes. Going from an hour down to 7 minutes to make a map is huge! I can pump out quite a few maps anytime I wanted without it being a huge chore.
But rivers… I wanted to tackle those too, so I turned to ridged perlin noise. When generating this type of noise, it kind of looks like rivers on a map:
So, I used it for that exact purpose. Now, I drop the ridged noise on the map to carve out where the rivers should be and the end result looks pretty good. Map creation time is now down to about 4 minutes!
Next, in Part 2, I talk about randomly generating entity locations on the map!