Lessons in Procedural Generation

Lessons in Procedural Generation

Oatmeal in Game Design

After a decade building procedural games at Maschinen-Mensch (Curious Expedition 1 & 2), I’ve learned this: making something random is 5% of the work. Wrestling the mess into submission is the other 95%. Below I’ll share a few of the tricks that made the 95% problem manageable.

Let's start with the "1000 bowls of oatmeal" problem. Kate Compton describes how you can generate 10,000 unique bowls of oatmeal, but it's still just oatmeal. A room that always has 4, 5, or 6 enemies is oatmeal.

It’s a trap we fall into quickly. The intuitive impulse is to use a simple, linear randomizer. For example, 'a room has 5 enemies, plus or minus 1.' You get an even distribution between 4, 5, or 6 enemies. It feels random, but it's not interesting. Every outcome feels technically different but emotionally the same.

Instead of a narrow range, try creating peaks and valleys:

  • 80% of the time: 5 enemies
  • 10% of the time: Just 1 enemy (a moment of relief)
  • 10% of the time: 10 enemies (a moment of panic!)

Random distribution that is equally weighted is boring. No one will remember the average encounter. Everyone will remember the outliers. Those are the moments that create stories.

I think Nuclear Throne must have used this technique when sometimes levels had a ridiculous amount of those scorpion enemies. I still remember it to this day. 😅

The Shuffle Bag

Ever played a game where you got the same useless item three times in a row? It feels unfair, and worse, it makes the game's world feel small and repetitive.

True randomness can feel "clumpy." In a random system, you can get the same outcome multiple times. For a player in their first 15 minutes, this can be a disaster. They don't think, "oh, that's statistics," they think: "this game only has three items!"

One tricks we used a lot in Curious Expedition is something called a "Shuffle Bag". Instead of rolling a die for every item, think of it like a deck of cards:

  • 1: Put all your possible items into a list (e.g., a shotgun, a knife, and a shovel). 2: Shuffle that list.
  • 3: Deal them out one by one, without putting them back in the deck.
  • 4: Only once the deck is empty do you reshuffle.

It guarantees a nice variety of content over a short period. You can do this within one level or even across multiple levels and store the shuffle state in your save game. It’s a simple trick that makes a huge difference.

Divide and Conquer

How do you procedurally generate a world that can be small, medium, or large, and guarantee it's always fun? This was a huge problem for us on Curious Expedition.

Just scattering a random number of locations was a failure. So our next thought was to create regions to stop locations from clumping up. It created a new problem: some regions ended up with all the resting spots, while others had none. The world was still super unbalanced.

The breakthrough was counter-intuitive. Instead of focusing on the bigger picture, we zoomed in and designed just one perfect self-contained region. It followed a simple guaranteed rule:

  • 1 Resting Spot
  • 1 Shrine (The Game Objective)
  • 1 Utility Location
  • 1 Loot Location (only spawned with 25% chance since they were bonus)

Once we had solved that, creating small or big worlds got trivial:

  • A small level is 2x2 regions
  • A medium level is 2x3 regions
  • A large level is 3x3 regions

No matter to what size we scaled up, the system didn't break. In Curious Expedition: Rivals we went as far as creating levels with 100x100 regions, without any extra tuning overhead.

Spelunky is another examples which builds its levels from perfectly playable, hand-crafted room chunks. We used a similar approach again in our game Mother Machine. Divide and conquer. To build a robust large-scale procedural system, focus on a small-scale self-contained building block.

Hooks, not Glue

Beyond being a roguelike and RPG, Curious Expedition is first a story generator. The game generates storylines that are surprising to me, even after working for years on it. We achieved this by tying the random events together through shared "hooks."

Instead of telling an event module about all viable follow-up events, which feels like gluing them together, we focused on having events know as little as possible about each other. An event only cares about shared resources. For example, when an event required an "ANGRY_COMPANION" as an input, it didn't care how that situation resulted, whether from a prior leadership decision or a random mishap.

The events database works like a cookbook. Each event is a page with a recipe, listing its required "ingredients" (the hooks) and the actions to perform.

This not only made wrangling hundreds of events more manageable by skipping complex dependencies but also resulted in surprising chain reactions we didn't foresee, yet felt logical and believable.

I’ve since applied the same idea to every procedural game I’ve worked on: first define the hook system, then create the events that use those hooks to be loosely chained.

Choking on Apples

I once played an old (ASCII) roguelike where your character could randomly choke on an apple and die. My mind raced with theories about the game's depth. Was it simulating food? Breathing? Anatomy?

Thankfully, the game was open-source, so I checked the code. It was a single line that basically said: if (random() < 0.01) { choke(); }.

This made me realize one of the most powerful tools we have as procedural designers: the player’s imagination. Players don't know how deep the rabbit hole goes, so they will often fill in the gaps with their own complex theories.

Recently I had to think of this again, when I received a LinkedIn DM with broken markup: "{Hello|Hi|What's up?} Riad". It reminded me of how we used text manipulation in Curious Expedition.

Initially, we used procedural text to swap synonyms: "A {handsome|good-looking|charismatic} person." This turned out to be completely pointless. The variation was technically there, but it had no emotional impact. It was text-based oatmeal, the same trap from the very first section.

So, we changed our approach to use antonyms or emotionally distinct words: "An {ugly|menacing|friendly|ambitious} person". The effect was very different. Players could invent complex theories about why the game chose "menacing" over "friendly" for a character on that specific run.

Sometimes there was a deep mechanical reason... but often, there wasn't.

We got away with it, just like the apple-choking game did. We created an illusion of complexity. I feel a lot about proc-gen game design is about playing with this illusion.

Show your Hand

I've thrown away good game design ideas because I couldn't find a way to communicate them. The mechanic worked, but if players couldn't see it, they couldn't learn it, they couldn't master it and they couldn't enjoy it. To me game design and the user interface are so intertwined that they're basically the same thing.

The same can be true for proc gen. If you don't find ways for the player to understand your proc gen rules, they can be perceived as boring randomness. Being able to master a system makes it intrinsically interesting. If you can't, it has to rely on extrinsic motivation like player progression.

Even though the proc gen in Curious Expedition might appear fairly random at first, there are several information layers that can be uncovered with increasing mastery:

  • Beginner: Regions had visible boundaries. Players could see the grid and learn that each region guaranteed certain location types.
  • Intermediate: When you see a point of interest questionmark in the distance, the specific terrain it is located on would tell you what type of location it was (e.g. villages are always close to water).
  • Expert: Seemingly random decorative landmarks like stone statues signal specific location types even before you spot their associated question mark

Some patterns were obvious. Others took dozens of hours to spot. But all of them were designed to be discovered. They are part of the game design, instead of living in a separate realm that is obfuscated to the player.

Earlier I wrote about how you can also exploit being unclear about your proc gen rules. I think being smart about combining both of these rules is the trick.