How dungeons are generated in Enter The Gungeon

Recently, I played a lot in Enter The Gungeon . This is an awesome, terribly challenging bullet hell game that reminds me a lot of Binding of Isaac . But the more I played it, the more I realized the subtle genius of the dungeon design.









There are many procedural generators that create logical level schemes that ensure the correct pace of the game and reward players, and there are other generators that create levels with loops and compact schemes. But it’s rare to find both types in one game. The only game I know of in which there was an attempt to implement such a thing is Unexplored .



So I naturally launched the decompiler so that Gungeon would reveal all its secrets to me. In this article I will share with you what I managed to find.






At first glance, the Gungeon levels seem pretty simple. Here is a typical map example.









Typical Level 1 Card



The player starts at the entrance. runs through the chests and shopkeeper to collect loot and then finally defeats the boss . If you have stumbled into a long dead end, you can use teleports to quickly return to rooms. Obviously, separate rooms were created manually, and inhabited by various enemies that need to be shot. When advancing to the next levels of the game, the player sees the same pattern again and again, only the stages become larger.



So far, it seems that this is nothing interesting. On the Internet you can find many generators connecting aisles to random rooms.



The generator feature becomes noticeable when you start playing. The levels seem ... a little more planned than might be expected from chance. The boss room is always a reasonable distance from the start. Rooms with enemies are always reasonably interspersed with calm rooms, benches and intersections. And most importantly - many chests are located behind the hinges with one-way cross.









The red line is a one-way corridor. If you want to get into a room with a chest, then you need to go a long way. The vast majority of chests are located either at the end of a one-sided loop, or deep enough inside the level, which forces the player to battle through many rooms, just to reach the chest. There is no reward without risk.



The secret to this passage is that general schemes are created manually. Here is the diagram used to generate the level shown above.









Normal rooms are randomly selected rooms with enemies, intersection rooms, or large rooms with multiple exits. The rewards (Reward) and the boss (boss) do not need to be explained. It does not show the “connecting” rooms, that is, rooms without enemies, often with natural hazards. The remaining rooms are either predefined or selected from a special room table.



In Gungeon, there are quite a few such schemes called "flows" (flow). At the Hollow levels, they are the least (only 4), and in Gungeon Proper - the most (8). These are not simple schemes, their design is created on the basis of a certain feature, which can be noticed with repeated passage. It can be a giant loop, or an important fork in many ways, or the need to get to the bench to pass the level. They are so noticeable that speedrunners noticed differences and made graphs by which you can find the boss as quickly as possible. I have prepared a complete list of schemes that can be downloaded from here .



You may have noticed that the flow pattern and the map do not match completely. Under the bench there is an extra room. inconsistent with the scheme, and strange corridor rooms . Let's study the whole process, it contains a lot of smart ideas.



The process begins by randomly selecting a stream file like the one shown above. This is the “graph” data structure , that is, it stores the interconnections of rooms, but not their location. Each room contains metadata about the type of room and the connections it should have. Connections have a direction - each flow diagram starts from the root node, and then forms a tree of child nodes. Then additional connections break the tree structure to create loops. I think that this is mainly due to the peculiarities of the development of the game, but it simplifies the procedures for analyzing the map, because all the loops have clearly defined beginning and end.



Stream conversion



A stream file can be converted in a limited number of ways. First, some specific rooms will be replaced by series of rooms of random lengths. This function is used only in later, larger levels. In addition, some parts of the stream file have alternative paths, and one of them is randomly selected. This function is used only twice.



Then a few additional nodes are “injected”. This function is quite flexible and is used for many different purposes.



Each “injection” contains data that determines what type of object should be inserted, where it should be inserted, the likelihood of creation and any conditions that must be met (for example, the presence of a master round , high curse or that the player has not yet saved the character ) For example, secret rooms are usually created at dead ends, but have a 1/5 probability of being attached to any room. They have a 90% chance of occurrence and do not require any additional conditions.



Almost every special room in the game is determined by the injection of the site, including merchants (not including the main shop), prisons, rooms with fireplaces and elevators.









One of the prison cells that can be injected into a level



At the same stage, the generator selects a specific room for each node. This mainly depends on the current stage and the type of room needed. There is a huge list of rooms - almost 300 for the first stage - but the generator tries not to select the same room twice.



Connector nodes work differently. Their rooms are selected later, while the scheme is created. Often these are long and narrow rooms, so it is very important to choose a room with the correct orientation.



Compound objects



After the creation of the stream is completed, it is divided into “composite objects”. Each composite object is either a separate loop from rooms, or a set of connected rooms without loops (i.e. a tree ). This is accomplished by finding the smallest loop on the map and cutting it out as a composite object. The operation is repeated until there are no loops on the map. The rest of the map becomes a set of divided trees and connections between individual composite objects.









The same stream after injection and compounding



Compound Object Schema



Then each composite object is created separately, on a separate map. They will be joined together later.



To plan a compound object, the first room is placed in an arbitrary place. Then, one after another, rooms are added to the circuit by selecting a pair of exits, one of which relates to the new room, and the other to the existing circuit. Exits are predefined locations in the metadata of each room. Then the new room is placed so that its exit is directly connected to the exit from the previous room. Then the process repeats.



More specifically, the composite tree objects are placed by traversing the tree in depth . The algorithm selects only those pairs of outputs that lead to the appearance of a new room without intersecting with the previous ones. In general, the algorithm prefers to choose outputs that are far from existing ones. If it is impossible to place a room, it will go back and regenerate the choice of rooms, repeating this process up to three times.



Meanwhile, composite loop objects are placed by adding elements from the loop in turn on both sides of the line. To begin with, pairs of exits are randomly selected (preference is given to opposite walls, east-west or north-south). When the loop is half created, it begins to give preference to the pairs of outputs that bring together two open edges of the loop. After creating all the rooms, the algorithm needs to add another connection between the last two rooms. He chooses another pair of exits. If possible, he designs between these exits a small rectangular room. Otherwise, he searches for a path between the exits and creates a “room”, which is just a narrow corridor. The length of the corridor should be from 4 to 30 units (in mines up to 50).



Final assembly



At this stage, small separate parts of the dungeon are connected to each other, but the composite objects themselves must be connected to create a complete map.









The same map as composite objects before final assembly



As you can see, the remaining compounds in this case do not give a very large selection. But there may be more complex cases than before. The algorithm bypasses the map, starting from the room with the most connections. As before, a pair of outputs is selected for each created connection. If two rooms are in separate parts of the map, then these two parts of the map are aligned to create a short path. Otherwise, a route search is used to create the route.



And this is where the creation of the level diagram ends. It remains only to choose enemies and decorations for rooms, and this is a completely different topic.



Finally



It seems that the main goal of the developers was to create a procedural generator that provides a satisfactory gameplay. Obviously, for it to be implemented correctly, they had to perform many iterations - I found a large amount of generation code, which, it seems, is not used, because developers have changed their procedural formula in search of perfection.



A curious trick is that they generate the most complex / important parts of the map first. The generator focuses on creating narrow loops and short corridors in the very central parts of the level, and then tries to connect everything else with them.



As in the case of my study of the generation of Diablo 1 levels , it struck me how efficient it is to generate part of the dungeon in an abstract form - in this case, it is a graph without information about the location. Everything becomes more specific only later. The function of “injection” would be simply impossible if we immediately went to work with a tile map. Thanks to the abstraction from the details of the placement of rooms, it allows you to control the style of passing the game and the scale of the level.



In addition, I was impressed by the extensibility of the system as a whole. Unity encourages a data driven approach. Adding a new room, scheme, or even special behavior can be realized by simply adding new objects to the corresponding tables. This must have been a great help, because Dodge Roll has already released some free DLCs and, without a doubt, supports the creation of mods .











Bonuses



Studying this generator was my first chance to research creating a professional game in Unity. Dodge Roll developers did their best and wrote good code. It is well read, and in some places quite funny - it seems that their love of puns has spread to the code. I liked these ones:





I also noticed that initially the studio had plans for stages in the themes of space, the jungle and the Wild West. Alas, they were not destined to appear. Dodge Roll decided that her work on Gungeon was completed . I will wait for their next game and hope that they put as much love and attention into it.



All Articles