R.A.B.B.i.T. Procedural World Construction

We've been very quiet lately on the blog post front. I've been spending a lot of time with the game's user interface and Dave's been working on the background art. We've also been nailing down exactly what's left to implement in the game before we release it and we plan on sharing our current progress with you sometime early next week.

Meanwhile, we started writing this particular blog post about two weeks ago and never got around to finishing it. It has actually been extremely difficult to write this one, as a lot of the information below can get CRAZY technical and we didn't want to bore you with lots and lots of complex 'If-A-then-do-B-else-do-C-unless-D-then-do-E' type logic processes (nor just summarise everything as "It builds levels, innit"). But, finally, we managed to format this in a way that's both in depth and -- we hope -- easy enough to follow.

Onwards!

THE BASICS

Firstly we needed to come up with a core set of metrics for the world. Having implemented a (very rough) swinging gameplay mechanic early in development and built some level design tests in UE4, we decided a sensible scale would be 500 x 500 square blocks, laid out on a grid.

Using this system we could easily tile blocks together to form an interesting game space, and we would also be able to flip pieces (both horizontally and vertically) to re-use them multiple times:

We also did some rough prototyping to decide how tall our game world would be and settled on 12 grid units high. This gave the world some verticality, but didn't go overboard.

SECTION TYPES

The world is built out of vertical sections. We have three different section types:

  1. Tunnels: These make up the core of the game world. They are basically giant pillars with one or two holes carved out of them for the player to navigate through. Tunnels always have a floor and a ceiling and the player can grapple from both (unless the floor of the tunnel is the level's ground itself).
  2. 'Swing Around' areas: These are five units wide, open areas with two grapple points in the centre that the player is able to swing around in both directions.
  3. Divide: These are small tunnels with no grapple points on them that appear only when we need to connect two 'swing around' areas together. The player must throw the rabbit through these gaps.

The idea with using sections was to build the world in vertical chunks that we could then tile together easily without worrying too much what was contained within them. To make this work, we couldn't simply randomise the sections that we choose to put next to each other as we could easily end up with a level that the player would find impossible to navigate through.

To solve this, we first divided the tunnel sections up into six unique templates:

  1. Top:  Can build a tunnel between blocks 8 and 12.
  2. Middle: Can build a tunnel between blocks 4 and 9.
  3. Bottom: Can build a tunnel between blocks 1 and 5.
  4. Bottom/Top: Builds a tunnel between blocks 1 and 5 and another between 8 and 12.
  5. Middle/Top: Builds a tunnel between blocks 3 and 7 and another between 9 and 12.
  6. Bottom/Middle: Builds a tunnel between blocks 1 and 4 and another between 6 and 10.

The image below shows the potential area that each tunnel could take up, if it used the maximum available space allowed:

With all of these pre-defined templates available, we were able to create a very simple set of construction rules for the sections:

  • A section can only connect to another section if it is guaranteed that the player will be able to reach another grapple point (e.g. we can't connect a bottom tunnel to a top tunnel, as the player wouldn't be able to reach it. The same goes for connecting a top/middle tunnel to a bottom tunnel - the player may end up coming through the top tunnel, but will then struggle to reach the bottom one).
  • Two 'swing around' sections must always be connected with a divide section.

With these rules in place, we were able to create a simple map between all of the different sections, which we could use to choose the next section randomly:

Once we had these rules in place, we were able to test constructing hundreds of randomly generated level layouts and, as you can see from the following animation, it is always possible to map a safe route between the sections:

CONSTRUCTING TUNNELS SECTIONS

While the 'swing around' sections and the divides are always built using exactly the same template, the tunnels actually have a insane amount of variation to them and make up the bulk of the game's gameplay.

In order to build tunnel sections, first we had to lay out some important ground rules to make sure we don't make our levels unfair:

  1. Tunnels can be anywhere between two units and eight units wide.
  2. Tunnels which are two units wide MUST be at LEAST two units high, and ideally they will be exactly two units high.
  3. Tunnels which are three units wide MUST be at LEAST three units high, and ideally they will be exactly three units high.
  4. We factor level difficulty into the maximum width of the tunnels. For example, easy difficulty levels have wider tunnels a lot more often than extreme difficulty tunnels.

It should be noted that this means we need four vertical blocks (Bottom | Empty | Empty | Top) to build a two high tunnel, and five vertical blocks (Bottom | Empty | Empty | Empty | Top) to build a three high tunnel. We never build a one-high tunnel (we found these to be completely un-fun) nor do we ever build a four-high tunnel (they were too easy and very boring).

To construct a tunnel we follow these steps:

  1. Based on the type of tunnel section we are about to construct (e.g. top, middle/top, bottom etc) we run a function which calculates the starting index, the height and the width of the first tunnel.
  2. If applicable, we run the function again for the second tunnel, using the width of the previous tunnel to determine the maximum possible height. In these cases, whichever width is chosen for the second tunnel becomes the width of both tunnels.
  3. We now construct the first tunnel by building the entire bottom row, then the entire top row and finally flagging the empty rows in between as being empty.
  4. We repeat step #3 for the second tunnel (where applicable).
  5. Finally, we work upwards from the bottom of the section, filling out any row that hasn't been dealt with yet with tunnel walls (this is why we have to flag the empty rows in step #3 and #4, to make sure they aren't filled in).

To construct each tunnel row we:

  1. Determine the number of pieces to build based on the width of the tunnel.
  2. Start with the first piece and build a left facing corner.
  3. Build across the row, until we reach the second to last piece, building generic block pieces as we go.
  4. Build a right facing corner for the last piece.

Note: When constructing corners, based on the difficulty of the level, sometimes we replace the corner piece with a standard tile and then build the corner piece one unit to the left (for the left corner) or to the right (for the right corner) instead. This makes the corners extend out from the section slightly and both creates a lot more variety in the design of the tunnels (and also greatly increases the game's difficulty when multiple of these extra extensions are used in close proximity):

Finally, when constructing individual pieces in the rows (whether it's standard tiles or corners) we:

  1. Choose a random mesh from a pool and build the core first.
  2. If the piece is the top or bottom of a tunnel, we construct a grapple mesh (the yellow/black stripped pieces) and build that too.

PUTTING IT ALL TOGETHER

We construct a level we start with a medium tunnel. From there we place a gap of three tiles then randomly choose to build another section. We place another three tile gap and continue onwards until the entire level is complete. At the end, we have a level that looks something like this:

NewSwinging1.gif


Posted on November 20, 2015 .