I’ve been writing procedural level generators for a few years now, from the very simple levels in Gunslugs 1, flat ground area’s with platforms scattered above the ground, to the more interesting random mazes in the original Heroes of Loot.
For me the biggest reason to add procedurally generated levels to a game is that it increases the replay value due to the nearly unlimited amounts of game areas it can generate. Besides that the amount of content it generates would be a time consuming process for a single developer to take care of.
For Heroes of Loot 2, which launched in Early Access this week, I wanted to improve on all my previous attempts of a level-generator by not just generating interesting dungeons, but also adding solvable quests and challenges.
Defining the design
I set out to create dungeons that have a main path with a start room and an exit room. The path should have various alternate routes functioning as distraction, alternate exit (warp-zones) or locations to place important items required for quests.
Finally the main path should have hallways blocked by gates which require you to either find a key, solve a puzzle, or defeat a monster in order to progress through the main path.
The dungeon is full of roaming enemies in all shapes and forms. It has a bunch of standard loot, rare items, treasure-chest, magic-chests, and destroyable scenery.
For the quests we sometimes need to place statues, altars, fill a room full of spikes, or place specific items.
So those are the functions that our level generator has to think about and make it all work together in such a way that each level is completable and interesting.
Creating room zero
The dungeon generation starts where a human level designer would also start: the first room. From this room the rest of the dungeon grows like branches from a tree, taking into account the previous places, the main path, and the requirements needed for the dungeon and the quests.
I decided to never place enemies in the first room, so that a player can always take a little breather when a new dungeon is being entered. There is some random scenery scattered around the room which, when smashed, can give some health and experience to the player.
Once the first room is placed, the first step we do is pick a random direction from North, East, South or West. This is the direction that our room will branch out to and create a narrow corridor with a random length. The length is always a minimum of 2 tiles, so that there is place to add a door or gate in the corridor.
The creation of the corridor is the first step in our recursive world generator. Once the direction and size of the corridor are decided upon, we also pick a random size and location for the next room based on the corridor’s end point.
With all these values decided upon we first check to see if the required space is still unused, so that we don’t overlap with existing rooms or corridors. IF the space of the corridor OR area's of the new room are used, we recalculate the corridor and room in a new randomly picked direction. We repeat this a couple of times until we either found a non-overlapping space to fit our corridor and room in, or we just give up and end the dungeon creation completely - it’s possible that we simply can’t fit any more rooms into the dungeon’s space.
I avoided overlapping rooms and corridors for this game, because we need a single path from start to finish so that it’s possible to add quests and make sure the player can’t just take another hallway to avoid locked gates/quests.
Besides the main path, we also try to add a random branching paths from each room that go a maximum set amount of rooms deep, this makes the dungeon more interesting to navigate and adds the rooms we need to add quests and hide keys.
With every room we add to this secondary branch, we also record a possible location to hide a key, this will be used in the quest generation code.
Gates, keys and quests
The above logic explains the way the shape of our dungeon is created, from a starting room to a corridor with the next room attached, where possible a second corridor with an alternate room is created and we do this until a set amount of rooms is created or we simply can’t place any rooms anymore.
During the process of creating a new corridor we also try and place a quest. This can be anything from simply hiding a key in a previously created room, to adding a mini-boss to slay before the gate opens.
So while placing the corridor, a random spot in that corridor is picked, and then a random quest type out of the available quests is picked. Some quests are fairly simple and only need a single spot in a previous room to hide a key and maybe put a statue on top of it.
Other quests will check the pre-made rooms on alternate paths to see if they fit the requirements. So for example, the mini-boss quests will need a room of a specific minimum size with enough empty space to move around. If such a room is not found, the quest is not generated and the corridor is not locked with a gate.
If the right circumstances are there, the quest is placed: adding a mini-boss, or a key, or a series of objects to a previously generated room on the alternate path, and then we place a gate in the corridor. Optionally we add a little Avatar person in front of the gate to explain the player what has to be done to open the gate, and our dungeon now has a quest.
The special rooms
The standard rooms and corridors are great to walk around in, but there is more we can do with rooms. When placing a room we first hand the room over to our "specialroom" function. This code looks at the newly generated room and based on the size, the location of the corridors, and often a random value, it will decide on using it for a specific purpose.
The most basic of those is using the room as a shop. So if the room is big enough to house an avatar, a couple of tables, and place some items on the tables, the room is then purposed as a shop and will not be available for quests. In the case of the shop we also limit the generation of this "specialroom" to a single shop per dungeon.
Other such "specialrooms" are rooms full of spikes, or a room with a single treasure chest in it, which when opened will spawn a bunch of enemies around you.
Populating and shaping the rooms
During the room-generation step we place the square of the room, but unless you want a very straight room-corridor-room dungeon, you will want to spice up the rooms a bit. So after placing the room, by removing solid tiles from the tilemap and replacing them with empty tiles, we randomly place tiles along the edges of the room, making sure to not block the corridors created to reach the room.
If the room is big enough it’s also possible to add some random solid-area’s within the boundaries of the room. This can turn a single bigger room into a miniature maze with walls and small hallway like areas.
Once these walls are added we populate the room with monsters and loot and maybe some candles to lighten the area for the player.
A large portion of the development time for Heroes of Loot 2 has been put in the level generator. I've been constantly improving it by adding new features and fixing level generation bugs. Which is probably the biggest downside of a level generator. The bigger the generator is, the more things could go wrong. The random nature means that it's nearly impossible for me to have tested all the various combinations of rooms, corridors, quests, and enemies.
I do feel confident about the current state of the game, which I why Heroes of Loot 2 is now available in Early Access release on Steam. It has an unlimited amount of playable dungeons being generated and the best way to know if it still creates mistakes is by sending it out into the wild!