Levels are set within a maze of city slums and rooftops, but they need specific structure to be enjoyable and interesting to play. Decided to assemble multiple smaller level chunks together to create larger levels for missions. By doing this team could handcraft interesting and reusable smaller chunks, layer in some procedural randomness and give each level the feel of being handcrafted. Procedural levels must:
- contain 1 main path
- contain 1-3 dead end paths for objectives and loot drops
- contain 1-3 shortcuts that act as alternative routes
- generate random props and cover objects within each level chunk
- spawn enemies based on a “tempo chart”
- spawn different types and ranks of enemies based on the mission difficulty
- work with Unity’s NavMesh system
- be deterministic and generate exactly the same across the network for COOP
The above requirements are specific to gameplay, world, and desired mission lengths. Every game will have its own requirements for a procedural level system. Missions could be completed in as quickly as ten minutes but could take up to double that time depending on how thoroughly you explore. That meant a bit of variety in the length of the path was also an important consideration.
Generating the Level Path
The first problem to solve for procedural level system was how to generate an interesting main path that includes dead ends and shortcuts. There is an answer in a Unite 2014 talk about Galak-Z level generation from Zach Aikman at 17-BIT (you can watch the full talk here).
Zach’s talk is really interesting and you should watch it, but to summarize: we used a modified Hilbert Curve algorithm to generate our main 2D path. This provides an interesting and unique winding path that is perfect as a starting point for our levels. While the developers for Galak-Z used this to create a 2D side view path for their game, we use it to create a 2D top-down view for ours. Imagine the following image is a top-down view of streets in game.
(image courtesy of the Galak-Z talk mentioned above)
After generating the main path, then need to evaluate all of the valid shortcut map cells and randomly choose to use some. A shortcut won’t let you cut across the whole map, but it will let you cut a small distance off the main path and potentially avoid some enemies. This adds more variety to the level’s main path and also provides another way to test the player’s skills.
Next, algorithm randomly add side paths with dead ends where no map cells exist. These provide alternate paths that break up the gameplay and offer ideal spots to put loot drops, hidden secrets, and special mission objectives such as Non-Player Characters (NPCs) to find and assassinate. Depending on the mission objectives, one to three dead end paths for each level being randomly generated. Each special mission objective is placed at the end of a dead end.
The idea is that you arrive in the level via a transport ship at the starting map cell where the main path begins. You then navigate your mercenaries through the level and complete your objectives before reaching the end of the main path where you are evacuated and the mission ends. In each level, completion of the mission objectives is required. Mission objectives exist off the main path which forces you to explore the level. Other dead ends are optional and offer secrets and extra loot. This provides a good mix so that our levels can be blasted through if players just want to complete the mission, or can take more time to explore if players want.
Once the full path with shortcuts and dead ends were generated , we convert it into a list of level chunks to load. Each level chunk is a scene in Unity, so algorithm named each scene with a specific pattern that indicates its configuration. From our generated path, convert each map cell into a scene name that follows the pattern. This pattern contains the chunk’s theme, its connections, and a variation. So following the pattern of <theme>_<main path connections>_<shortcut connection>_<dead end connection>_<variation> a level chunk might have the scene name of: “slums_03_-1_-1_A“.
Based on the structure of Hilbert Curves and our generated paths, each level chunk will have two main path connections, between zero or one shortcut connection, and zero or one dead end connection. A level chunk cannot have both a shortcut and dead end connection as they are never used. Each connection corresponds to an edge of the level chunk and each edge is labelled with a number between zero and three as described in the image below (to label a non-existent connection, like when there is no shortcut, we use “-1“).
For instance, a level chunk marked with a main path connection of “03” has a connection opening at the bottom (0) and right (3). The main path connection label is always ordered from smallest to largest (e.g. “03” instead of “30”). It is important to note that, when placing level chunks side by side, the connections do not necessarily have to line up 100% evenly. Adding “jagginess” to some connections adds variety and makes things look less smooth where chunks connect. However, connections must always be lined up in some way to ensure the path connects.
Variations offer the level designer a way to make different versions for the same level chunk. For instance, look at the two variations below labeled “A” and “B” for a “01” main path connection with no shortcut. When generating the level our system randomly chooses one of the available variations for each level chunk, which creates more visual variety.
As each level chunk were loaded, next step is moving it to its correct offset in the world based on where it belongs in the path. This means chunks cannot have meshes marked for static batching in Unity (because when you try to move them things will break). However, Unity’s dynamic batching still works with this system. Below are some example images of randomly generated level layouts (the red areas are shortcuts through buildings, the blue areas are variations of buildings). These are proof of concept layouts without finished art.
In Part 2 of the Procedural Level Generation in Unity . article we will discuss the lighting and NavMesh problems we overcame, and how to spawn NPCs based on a tempo.