Hey guys, I’m TheGamer101. You might recognize me as the creator of several popular ROBLOX games, including Sword Fighting Tournament, Ultimate Assassin’s Creed, and King of the Hill (the latter of which is the subject of this article). I’m here to share my personal development process for a complex map generation script that automatically builds a new map with every round of King of the Hill. Creating this was a real learning experience, and hopefully you’ll be able to pick up a few ideas I present and use them in your own unique development strategies.
Why random map generation?
There are two main reasons why I decided on random map generation.
- This technique provides a unique gameplay experience every time you play the game. This keeps cheap players from “camping” at the best spots of the map, because they don’t know where those spots will be and adds variety.
- I aimed for a very lighthearted feel with King of the Hill and I reckoned that simplistic, blocky maps would fit in well with the aesthetic.
I started by brainstorming how I wanted the finished maps to look. I drew sketches and narrowed down the list of the key features I wanted the maps to include. I opted to add rivers and lakes as I hoped these features would stop players from charging straight to the hill and force them to navigate their way around the terrain. I also incorporated tunnels to bring another element of strategy to the game.
Brainstorming is a critical first step to take before implementing new features. Don’t just make something because it is the first thing you think of–think about what it will add to the game, and if there is anything else that would suit this purpose better. Having a clear plan before starting allowed me to leave room for the tunnels under the map, figure out the order in which to create each feature, and decide how to create each one. For example, I decided to create the water features before the tunnels — I realized that it would be easier for the tunnels to avoid the rivers and lakes than it would be for the water features to avoid the entrances to the tunnels. Try to make a “big picture” plan, and stick to it.
I started off by making a simple function that would make a blocky map. It’s important to start small to avoid being bogged down by a heavy workload. It’s motivating to be able to see some results — however small — quickly, without having to write a thousand lines of code and debug all at once.
I then expanded on this. The first thing I did was come up with a way to ensure variation in the total elevation from round to round. I added an argument to the function that notes whether the map should be normal, flat or highly varied. Of course, across any category of map, players must be able to move from any brick to at least one brick beside it, otherwise there would be low points where players would get trapped. To ensure this, the script had to be able to find every brick on each side of a brick. I divided the map into a 15-by-15 grid with parts named “BasePart1” – “BasePart225”. This allowed the script to find every part beside any part easily. For example, to find the brick on the right, the script adds one to the value at the end of the brick name.
I then started working on incorporating bodies of water. I started with rivers, as these would be the easiest to make, and each additional water feature could build on the river’s base code. At first, the rivers were simple: they flowed randomly and no restriction other than not being able to flow off the map. I understood the basic concept of how rivers flow, so working within those concepts helped me figure out the finer details. I made a list of all the problems and the possible solutions.
The purpose of the rivers was to divide the map and force players to navigate around them. The rivers in their initial form did not achieve this. Many rivers turned back toward their source, meaning the rivers in some cases covered less than a quarter of the map. To force rivers to flow through more of the map, I added a slight bias that encouraged rivers to flow away from their source.
The next feature I worked on was lakes. The greatest challenge of creating lakes was making them in viable and realistic shapes and sizes. After testing various different shapes, I concluded the shape should be irregular and wider in the center on both the x and z axis.
First, the function selects a part in the first seven rows of the map. The next five to seven parts in the same column are used as the center line of the lake. The rows closer to the middle of the center line are usually made wider than those near the sides, which made the shape of lakes somewhat natural. To save time I used the same function that generates the lake for the islands–only this time I would find the parts around the returned parts, and change those to water. Later on, I found I could also use the same function for selecting the area of an underground cavern.
Play testing is a really important part of map development, and game development in general. Some things can look like a really good idea on paper only to turn out to be unrealistic upon implementation. When making a game, don’t be afraid to cut features that don’t work, even if you’ve spent a lot of time on them. Testing helped me find bugs in the script and figure out what features I should add to improve gameplay. When testing for the first time after finishing the water features, two main problems arose:
- I found that there were some really obscure errors, and I could not figure out the exact cause. To solve this problem, I decided to make my maps generate from a seed, so I would be able to generate specific seeds to track down the root of the problem. I wrote my own function to generate random numbers. When generating a broken map, I added lots of print statements to the specific function that I thought may have a problem and used the output to track down the issue.
- Many testers said that the maps were too blocky. I also found that, although I intended for rivers to divide up the map, some testers found that they got in the way of the action. To solve these problems, I chose to generate roads across the map — they made my maps appear less blocky and provided “bridges” for the rivers and lakes. Roads were also convenient player spawn points.
The tunnels in the game were inspired by one of my favorite games, The Underground War by stickmasterluke. I really like the close combat in the tunnels of his game and I hoped to replicate that in my own. The tunnels had to be highly varied to create a fresh experience every time. While some tunnels run in a straight line to their destination, most tunnels include at least one turn. Some tunnels even include dead ends, which creates a nice maze effect. When testing, I felt that not enough use was being made of the tunnels, so I added underground caverns to attract players. I really enjoy fighting underground as the confined space means players can’t avoid battle.
Roads were the hardest aspect of this project. The roads had to be able to adapt to all the other features of the map, and adapt to the other roads they intersected with. When generating a part of the road, I had to keep two things in mind: what was in front of the road, and what was behind it. Because of these challenges, I had to plan out the road script before starting it, taking all the possibilities into account. I drew diagrams of all the possible situations the road script would have to accommodate and developed the following method:
- Find the part the road is to be generated on. Find out if it is grass, water or a tunnel entrance. Is there already a road part in this spot? This means two roads are intersecting.
- Find the last road piece. This piece of info can tell you how high your road is.
- Check the difference between the last height and the height of the current brick. If it is too great, make a wedge.
- Find the next part ahead of the current position, if there is one. Check if it is a water or a tunnel entrance. In this case a wedge may have to be made to rise above the water/tunnel entrance.
- At this stage enough is known to decide if the road part should be a normal part, an intersection, a part with stilts, or a part at an angle.
The intersections were the most challenging, especially the rare occasions where two intersections happened to run parallel to one another. When that happens, the second intersection has to be the same height as the first intersection, meaning the height of the road needs to be changed. I think the roads really add to the maps and I am proud of how they turned out.
After reading this article, you might be interested in utilizing map generation yourself (or maybe you are just baffled by what you should be taking away from this). If that’s the case, here are the key points I’ve tried to convey:
- Have a plan: it’s important to have an idea of what you plan to include before you start developing.
- Start simple: starting small will allow you to see some results early on and I find this motivates me to continue work on a project.
- Make things more complex: when the basics of a feature are done think of ways to improve it.
- Cut away waste: I often find myself reluctant to get rid of things that I have already made. However, to produce a finished product you are happy with, it is necessary to get rid of things that are unsatisfactory or revamp them.
- Test extensively: many of the best ideas don’t come from staring at the code, but by playing it. Play your game as much as you build it.
Thanks for reading! I hope you have enjoyed reading this article as much as I enjoyed writing it.
Editor’s note: for ROBLOX builders who are interested in learning more about procedural level generation, here are some great sources for inspiration, algorithms, and code:
- Mazes and Labyrinths: An Introduction to Random Levels
- The Procedural Content Generation Wiki particularly the sections on Dungeon Generation and Map Generation
- This Gamasutra article about the random map generation in the cult game Dwarf Fortress
- For hardcore computation geometry buffs, this article by Amit Patel contains a lot of novel ideas
- The original Spotlight article, Procedurally Generating a “Zombie City!”