Data-Driven Procedural Level Generation

Posted by (twitter: @pkenneydev)
January 2nd, 2016 5:05 am

For most of my LD games I go with procedural level generation, rather than hand-crafted levels.

Why Proc-Gen?

I’m always shooting for the “Fun” category, and I’m aiming for a cocktail like this:

  • A core physics-based action that’s fun to just do on its own.
  • Obstacles with some variation, that can be procedurally mixed up in varied combinations
  • A quick retry loop so the “you lose” moment gets the “one more try” response rather than the player quitting
  • A score mechanism that lets the player be challenged but doesn’t overly punish someone just having a good time

I don’t always succeed in getting the balance right – for example my LD29 game about navigating a ship between icebergs was far too difficult to be accessible.  LD31 was much too confusing.  LD32 was a total failure because the core action simply wasn’t sufficiently controllable.  So there are many pitfalls,  but when it works I feel like this is a good approach, at least for me.

The procedural generation is a key piece because it keeps the game fresh, even for myself, and facilitates that “one more try” feeling upon failure.  LD games are played in a quick sitting to evaluate, so I hate to ask the player to repeat anything.  I don’t even send them back to the start of the difficulty curve when I’m doing generated levels… I want them to push one button and immediately be back in the flow but seeing something new.

How I Approach Proc-Gen

I have developed a bit of a system…  here is a screenshot of me mapping out the difficulty curve and then adding elements to my level generator in Unity:


On the left in the spreadsheet I have mapped out what I want to achieve in a sort of visual way.  In Psychotennis the player is on the left, bouncing  a ball to break bricks, like this:


The different bricks have different behaviors, some taking more shots to break and others hitting the ball back to you with various amounts of speed-boosting.  So it’s important to make sure they’re placed in some meaningful way.

Designing the Generation

You can read the spreadsheet (which is the design doc) like this:  Each row is a “match” aka a level, and each column is a possible placement of bricks.  In the early matches 1-5, the player should face bricks relatively far away, consisting of fluff and a bit of “light structure” which is a term I use to describe bricks that take more than one shot to remove, and thus create a durable shape to each match.

By the midgame we have threats in the back portion, covered with some structure, and a bit of easy-to-clear fluff nearer the player.  By the endgame there’s a complex sandwich with a few threats close to the player, which are especially hard to deal with.

You can also see that although there are three big “zones” of 1-5, 6-12, and >12, I do some additional minor transitions, such as levels 6 and 7 lacking the “fluff” nearest to the player.

So creating that spreadsheet is how I design the difficulty curve and overall shape I want from the procedural generation.

Implementing the Generation

Looking back at the screenshot, in the Unity editor on the right you can see how I enter this design into the game’s level generator that I wrote.  This is a critical bit – although there is a lot of custom logic in the generator, it is even more driven by the data that I enter, which allows me to more rapidly input/edit the design.

I create a series of entries, each of which defines a range of “layers” (the horizontal position) at which this entry can be spawned, as well as a level range at which it occurs.  Then there are a set of four prefabs, one brick each, representing what will be placed if this entry is instantiated.  It’s four bricks rather than one because I somewhat arbitrarily decided that I’d do the generation in “quads” of 2×2 bricks.  So I kind of generate by the molecule instead of the atom, if you will.

The weight is usually one, but can be adjusted to make certain entries more rare or common.  I put all eligible entries into a “Weighted Random Grab-Bag” and then pull from that bag to select the item to be instantiated.  A rather high “skip placement” chance is then applied, first at the quad level and then again at each brick level, which creates the empty spaces and makes the “shape” of each level different.  Early on it was a giant wall of bricks like atari breakout, but that was a lot more repetitive and thus less fun.

“Concrete” – the unbreakable bricks seen in the clip above – required a lot of care since they can create impossible-to-win levels and even get the ball completely stuck.  The game creates a “budget” for concrete that will not be exceeded, and has a lot of restrictions on where and how closely concrete can be placed.

A Flaw in the Approach

In general there is a problem with this pattern:  The player plays for a while, gets past the easy stuff and to their skill level.  If they lose, I make them restart but from the current level… so I generate a new level but don’t send them backward in the difficulty curve.  This works well for a while, keeping the player challenged.  But if they play for a very long time, they will get some lucky easy levels and push further into the difficulty curve.  Then they will be playing in an incredibly hard level and be stuck there, losing and losing.  Hopefully the game is still fun then, but it’s not a very nice way to leave them.  In post-compo work on pychotennis, this is the primary thing I’m trying to address.  The game just gets too hard and then strands you there.

There are other flaws in that a player can be served a bad level that is either too hard or too easy.  But with short levels and fresh generation each time, I don’t worry too much about this.  As long as the game is not generating totally game-breaking levels then I think it’s okay if the player has the occasional silly experience.

My Games Using This Approach

To see this in action, you can check out my current entry, Psychotennis.

If you’re even more interested than that, you can check out my games list page for past entries.  The ones that use this approach most clearly are Hammer Control and Splash Europa.


Leave a Reply

You must be logged in to post a comment.

[cache: storing page]