Procedural Dungeon Generation in Unreal Engine 4
With last week’s #screenshotsaturday we got a lot of exposure, comments and feedback. We showed one of the procedurally generated levels in HyperParasite:
We also got a lot of questions on UE4 sub-reddit and FB groups about how it works, so I decided to write a quick breakdown of how Vania (our in-house procedural dungeon generation technology) works.
The UE4 tool was created ad hoc for our other project, Tenebrae (still unreleased), and it directly derives from Daedalus, a Unity plug-in we used to sell on the Asset Store (no more available):
Grid and Rooms
The generator works on a grid, so all the space is evenly subdivided into a regular grid made of squares (1 square = 1 tile).
Because of this, all Rooms must be square or rectangular in shape, they must be subdivided in tiles as well, and the tile size must be consistent over all rooms/maps (of course, different maps can use a different tile size).
Keeping in mind these rules, we then create a fair amount of Rooms and variations:
Each Room is saved as a separate map (UE4’s .umap files). It can contain anything you want and can have any contorted path inside, as long as it respects the aforementioned constraints:
Also, Rooms must have a few Passages (the purple boxes in the following picture):
It is through Passages that Rooms get connected one to another, so it’s importart to have quite a few of them scattered on the perimeter of a room. The more the Passages, the more contorted and interesting the generated dungeon will be.
Vania can create both flat frontal (i.e., platformer) and top-down maps, here are a few examples of single rooms for both types of games:
Once all Rooms are ready, we process them through a tool we wrote that extracts metadata from them.
Data like Room dimensions (width x height), amount and location of passages, Room type and custom parameters, gets written in a JSON file for later use during the generation phase:
We also use DataTables for convenience, so we’re able to enable/disable one or more Rooms for the generation.
Also, we can set here some very specific parameters to alter the dungeon generation:
Since this tool was purposely created for our Metroidvania game, these DataTables contain quite a lot of features to be able to handle that kind of maps/gameplay, so we got lock-key combinations, “blocking” rooms (i.e., rooms you can only enter if you got a specific Metroidvania-style power), etc. But there are also some other parameters that allow for more general fine-tuning, like Frequency Weight and Frequency Falloff. This opens to a great degree of customization for the final dungeon’s look.
You can even specify things like how many Rooms must be there between two different Binding Rooms or if a particular Room must lay on the Main Path (i.e., the mandatory path that goes from the Start to the End Room that the Player must traverse in order to complete the dungeon) or on the Side Path (additional, constraint-free Rooms).
Another feature we really like is the possibility to generate different “zones” for a dungeon; for example, if you’re creating a castle-like game level, you could force the generator to spawn tower-like rooms with windows in the upper zone of the dungeon, while cellar rooms will be placed near the bottom of the map. That’s a really cool feature!
In the previous pictures, purple rooms are for towers and blue ones are for cellar rooms.
The original Daedalus plugin we created for Unity featured quite a few different generation algorithms (Hunt & Kill, Backtrack, BSP). Again, since Vania was created on purpose for our game project, we decided not to carry all of them over from Daedalus; instead, we just picked the one that best suited our map generation and gameplay needs, but nothing would stop us from implementing new algorithms if need be.
When the generation phase kicks in, a logical map is created through the algorithm, and finally all the graphic assets (i.e., Rooms) are spawned and positioned on the grid, exactly where they need to be:
During the generation, all unused Passages get closed and turned into perimeter walls, while (optionally) a Door object is placed between open Passages that connect one Room to another.
We also spawn one UBoxComponent for each node/Room in the map, so we can later leverage Unreal’s level streaming technology; this is especially useful for huge/complex game levels with a lot of Actors. In fact, you can decide whether to show all rooms at once or just the one the Player is in, thus keeping good performances. You can even specify a padding value for the streaming, so adjacent Rooms are streamed in as the Player approaches:
One more generation pass is used to further randomize Rooms content, but it depends on the type of game the generator is used for and its rules/gameplay mechanics. For example, we also randomize AI spawners, loot, secrets and other gameplay features: