Hi, I’m Will, one of the lead programmers of Twin Flames. I’ve previously worked on other big Fat Panda projects such as Flat Kingdom or Lobo With Shotguns. Today I’m gonna tell you a little bit about the procedural world of Twin Flames and some rules we followed in order to make it work.
First of all, what is a procedural world or procedural level? This basically means that when you enter a level, it will always be different because it was just created. In contrast, most games have pre-designed levels that are always the same. However, when a level is created, it does not happen from scratch (that would end up being quite chaotic), it must follow a set of rules (defined by us, depending on what we want the level to be) in order to work and have a semblance of a finished experience.
Here in the team we’re big fans of some rogue-likes such as The Binding of Isaac, Rogue Legacy, Risk of Rain or Spelunky. We love their replay value and that feeling of struggling through always completely new levels in order to advance a little further. We wanted to add our own twist to it and give it a try, thus Twin Flames was born.
However, we have never developed a game such as this before, so the learning curve was steep. We first needed to define a few basic rules for the procedural generation to know how much it would create and how much we would predefine. To avoid extreme complexity and headaches, we went for a pseudo-procedural level design, such as The Binding of Isaac. This means that the majority of “rooms” are already designed, but the way they connect and interact with each other is randomized.
The first step was to do a little research and read from people who have done this before, so I found a few interesting articles that I’d like to share with you:
-The Binding of Isaac Gameplay Explained. Here, my boy Edmund talks about how he came up with the gameplay of The Binding of Isaac. This article was very useful, because it talks about the randomness of the levels, items and every other element in the game.
-Procedural House Generation: A Method for Dynamically Generating Floor Plans. This piece describes how to procedurally generate a house. It’s interesting because it talks about the order in which things are created, considering that each structure has its own priority. It also describes the relationship of each structure with each other via nodes.
-Basic 2D Dungeon Generation. More than an article, this is a tutorial. We used this as one of the bases for the Twin Flames algorithm. In this tutorial you generate a level in which each room connects nicely with the next rooms.
So, with these tools at hand, we defined the rules to compose each level. They all have a series of interconnected rooms towards different roads, each with a defined size and type.
Room size is a very important propriety because we wanted the connection to be coherent. It would be very weird to have a sequence of very long rooms take the same space as a sequence of small ones. We first defined a base size as our starting point. This way, once we had the standard and smallest possible room (1x1) we created more room sizes that would fit our system: 1x1, 2x1, 1x2, 2x2, 3x1, etc.
Once the sizes have been established, the next step was to connect the rooms in order to generate the paths. We decided to create a main path (this would be the road from the first room in the level to the exit) and various alternate paths (these are for filling empty routes and can contain special rooms). That way, the levels are not linear, but kinda maze-like (we decide how many branches it can have, so it doesn’t end up too crazy).
Now, with this two types of paths, we defined a few room types for each one:
-Beginning Room: This is the room where your level begins (obviously). It has special decoration and one exit through the justify. It’s a 2x1 sized room.
-Middle Room: This room will always be roughly half way through the path. It has special decoration and two exits (one on the left and one on the justify). It’s a 3x1 room.
-Boss Room: This is where the boss fight happens in each level. The size and decoration varies depending on the boss being fought.
-Exit Room: This is the last room in the level. It’s main purpose is to connect to the next level. It’s a 1x1 room.
-Normal Room: These form the majority of the main path. They have different sizes, exits and decorations. They serve as the bulk of the level, mainly because most enemies and obstacles are in these rooms.
- Treasure Room: This room has to be always in the first half of the level (that way, you can try out your new weapon-power up in the remaining sections of the level)- It always contains a treasure, has special decoration and is a 1x1 room.
- Alternate Room: This is useful to close off exits and dead ends. Essential in any worthy maze.
Once the rules have been set, you can now begin to imagine how the levels will be generated. In our case, we already had a system to load and save rooms, so we divided the process in two parts: First the level must perform a structural generation, and then it performs a physical generation.
This means that, it first generates a set of paths and variables that the level will follow (based on the previously established rules), and then it actually puts the rooms where they belong taking them from a pool of previously pre designed rooms. Imagine the first as the skeleton of the level and the latter as the skin to dress it up. This order is important because you always need to know what you want to do first (in this case, which path) and then you put the pieces that you need to accomplish that (the rooms).
In this step, the most important factor are the predefined rules. For Twin Flames, we needed a main path with a beginning room and that leads to an exit room, but it also needed to contain a middle special room and a boss room. To simulate that the player is in constant movement and maintain a sense of progression (you’re supposed to be climbing a huge tower), the main path can take any direction except the left. We also needed alternate paths to contain treasures (to reward exploration) and just spice up the game in general (no one likes linear mazes, after all).
Step 1 - The logic begins with the creation of a room. Depending on the type, it generates a number of possible exits. These start empty and lead nowhere. To avoid strange or useless exists, we also define first where the exits in each room are placed.
Step 2 - Once the “structural” room is created, it then creates a room next to each exit for the previous room. This new room (or rooms) follow the exact same rules as in step 1. It then creates more rooms for the exits of the new rooms, and the cycle goes on.
You can guess the next step: a rule to end the cycle and stop building rooms (or you’ll end up with an endless huuuuuge maze!). We can establish how many main rooms we want in the path, to estimate the length and play time of each level. Once this is all created, we have our “structural level”. This is not yet a playable level, but rather a set of rules and guides on how said level will be built. Kinda like a blueprint of the level that will be generated.
This is where the rooms are actually created based on the previously defined structure. It needs to check the defined path of the structural level and, for each structural room, assign a physical room from the pool of rooms that is compatible with the shape, size and number of exits needed.
This way, with the same structural level, we could end up with various different levels with variations in their internal composition (obstacles and such) but all with a similar structure.
In order to know which rooms are compatible and which aren’t, we created a room editor where we can assign values to each room such as size, type, exits, camera limits, etc. All of these variables are saved and used to classify the room in order to have it ready when it needs to be used.
With me so far? This ends the first part of a series of articles about this. The next posts will be much more practical and I’ll share a few tips on how to achieve this. Did you enjoy this? If you have built a similar system in the past, what are your recommendations? Would you have done something different? If you have anything to say, just leave a comment! See you next time and thanks for reading!
From our blog: fatpanda.tv
If you like the game, please stay tuned on our social channels: