Hey everyone, sorry for the silence! We’ve been undergoing a series of under-the-hood upgrades that have been a long time coming. Since upgraded netcode, door mechanics, and player inventory aren’t the most exciting thing in the world to take screenshots of, let’s cover something that I’m sure many of you will find interesting: Our random level generation process. I’m not going to get too specific about the design process as that knowledge would require us to ask Baard questions. Writing up an accurate representation of that would delay this blogpost until after “The Winds of Winter” is released, so let’s look at the actual creation process instead!
Ok ok ok, let’s talk about the elephant in the room first. As the years have gone by there have been important systems within the game that have needed an overhaul, but due to the size of our team, we were unable to resolve at the time. This is what’s known as technical debt, and like most fledgling studios, we vastly underestimated how early decisions would affect the project as time passed. Only within the last year were we able to double the size of our programming team and begin refactoring core systems. More work has been done on the game in the past year than the three years before it, combined.
Once PAX East ended, we immediately dumped our old network library and replaced it with a new one. To put this task into perspective: this is like ripping out your spine and replacing it with a new one. It should just work, right? We didn’t want to be months into Alpha and have to shut it down because, “Oh hey, our netcode has a glaring vulnerability in it! Pardon us while we turn the game off for two months to fix this issue, thanks for not freaking out at us!” We all know that wouldn’t go over well.
After the dust settles and a majority of the networking bugs have been squashed, we’ll be upgrading to Unity 2018. We expect that process to go relatively smoothly, but are prepared for a few road bumps. There are other major tasks looming on the horizon as well, such as reworking player inventory, game-state manager, gun architecture, and scaling our back-end up to support matchmaking during Alpha and beyond. We’re taking this time to future-proof our systems to make testing as smooth as possible. We would much rather spend Alpha tuning gun balance or adjusting level generation based on player feedback instead of implementing a hack fix to quickly slap a bandaid on defunct systems.
We appreciate everyone’s patience as we continue to work towards release. We’re crushing bugs as quickly as we can because trust me when I say we’re trying to avoid shipping with stuff like this:
A Brief History of Randomness
Generating random levels has always been one of the core design ideas behind Due Process. Aligning objects and snapping them together to form interesting cover has defined every iteration of our level generator. During its infancy, levels were generated on the fly. The concept of level flow was almost non-existent as our only game mode was Team Deathmatch. There was no bomb to defuse, so maps could be as wild and crazy as they wanted to be.
This version of the game literally had infinite levels, but the quality of each level varied wildly. Sometimes you’d get an overly complex grid of 2x2 rooms with a door on every wall which made planning nearly impossible. Other times, the generator would decide that a long hallway with no cover seemed like a perfectly reasonable building to defend. Fire doors would spawn next to normal ones! Power switches could get placed on destructible walls! Nothing had to make sense, level generation was chaos. We knew that if we were going to make a competitive game, true random wasn’t going to cut it as getting screwed over by RNG felt bad.
Level Gen 1.0
Due Process underwent a complete rewrite in 2015, and with it, a new level generator was given life. No longer did we create maps on the fly but instead generated levels, verified them, then sent them out as map files. Having someone look over each level vastly improved the overall quality of the game. Levels were assembled by using pre-made rooms that snapped together on very specific nodes like 3D Tetris pieces. Each room was hand-made by an artist, then added into a pool for the generator to choose from. It worked, but levels wound up feeling very samey, to say the least. The variation between maps was limited to the total amount of rooms our artists had made.
Creating these “room tiles” was very labor intensive and wasn’t satisfying our goal of “randomly generated maps” quite how we had envisioned it. The “Donut Room” always wound up being a pain to assault since the cover never changed, enemy positions became too predictable. Other tiles became tedious to navigate if they snapped next to each other, forming more of a maze than a playable level. Random assembly of persistent room configurations wasn’t the answer. Our solution was to allow the tiles themselves to randomly place their own cover.
Level Gen 2.0
Today’s level generator is incredibly robust, so let's follow a Convenience Store map through the generation process. Tiles are created and assigned a room, such as a storefront, freezer, office, etc. Each tile has nodes spread across its surface that act as attachment points for individual pieces of cover. Walls are raised where these tiles meet, which also have their own unique nodes to help the generator understand the space it’s looking at. With the rooms divided, the cover is spawned additively in separate passes. Unless configured otherwise, the generator prefers to place larger, room-defining props first before moving on to smaller pieces of cover.
Above is an exaggeration of the level generation process. Typically each step happens simultaneously in a timespan of about 45 seconds. Once a level has been created by the generator, a designer will adjust it to make sure it’s playable. This includes creating doors, shifting cover around, and ultimately placing the bomb objective itself. While our editor allows us to make large-scale adjustments, such as deleting rooms or creating customized cover, we prefer to let the level generator do the bulk of the work.
Once a designer is satisfied with a map, it’s given a randomly generated name and sent off to the baking machine! The computer we use to bake levels houses an AMD Threadripper, which decreases baking time by a substantial amount. Each map is placed into its associated background scene where things like reflections, room volumes, occlusion culling, and lightmaps are finalized. Certain objects, such as storefront shelving, spawn additional items during this step. This process usually takes around 20 minutes per level and is typically done in large batches overnight. Once that’s all done, congratulations team, we have a new map! Say hello to Operation: Glass Trigger.
Into the Future
The ultimate goal for level generation is to be a one-button process. This is a moonshot of a goal, but we bring ourselves closer to it every day. Recently, we’ve been toying with the idea of props that spawn their own cover! This would create cover that is relevant to its location in a room instead of relying on a designer to manually move things around. Leveraging these advanced props could potentially move a large chunk of the design process into level gen itself.
This sort of “self-spawning” cover could also be applied to things like pipes in our Factory tileset, or teller counters in the Convenience Store. Instead of using pre-made configurations, a “root” would be spawned intelligently within a room, which would automatically branch out and spread towards walls until connected. These dynamic counters could even be configured to replace part of itself with, say, a teller slot.
While certainly cool, these expanding props are still highly experimental and exist outside of automated level gen. These prototypes are hand-placed and spawned one section at a time after a level has been created as opposed to running during the generation process. Merging these dynamic props back into level generation is on the back burner for now, but we may revisit the idea in the future.
Level gen is an evolving process that never really ends. Even after we’ve settled on a look and feel for a tileset, map and room configurations can be easily adjusted to produce wildly different results if things start to feel a bit stale. We’re confident that as we get closer to release, the level generator will be able to reliably create enough maps to keep the game feeling fresh and interesting.
That’s all for now! If you haven’t already, check out our Discord server! Head on over to #the-map and show us what kind of plan you’d make on the level we created in this blog post. If there are any other burning questions you might have, hit us up on either Discord or our Subreddit. I’ll just leave this here as a teaser for what’s coming up in the next blogpost. Until next time!