Game Development

Blog 698: Tile Your Bathroom, Tile Your Game

If you’ve been following my game development activities for any length of time, you’ll probably have seen me attempt to create a level building workflow about… four times. I started with heightmaps and static meshes, but bare meshes aren’t particularly comfortable to work with and Unity’s heightmap is, err, too high-poly. I tried to roll my own voxel terrain editor, but that was too complex and janky and didn’t really tick my boxes after all.

I had a stab at procedural generation quite a long time ago now, which created levels based on  a 2D grid of tiles. I ported all that to Unity more recently, and it broadly worked but I ran into some snags, so I gave up on it. But, even more recently, I finally came up with an answer to those snags. This time — this time at last — I reckon I’ve got a sustainable answer.

Tile-Based Level Construction

I’ve always liked tile-based level systems. The first “game levels” I ever drew were island landscapes formed by colouring in the blocks on half-centimetre squared paper. When I graduated to playing games, I continued to see the blocks — Transport Tycoon, Age of Empires II, Warcraft IIEarth 2150, Warcraft IIISonic 3D; some of them disguise it better than others, but their worlds are all fundamentally built on chunky grids.

In grappling with the problem of how to build levels for my game, then, a chunky grid is the natural starting point. But the first time I tried to actually turn this nebulous concept into a reality, through the medium of that old procedural level generator work, I hit some issues. Some of them, to be fair, entirely self-inflicted.

  • If a character is the size of a single tile, the level generators tend to produce one-tile-wide corridors: levels get a bit tight for comfort, and the characters difficult to see for all the walls.
  • In order to reduce the amount of pattern repetition on surfaces, I decided that I’d use textures that spanned four tiles instead of one. Plugging this straight into the generators fell apart — I couldn’t get the walls and floors to line up seamlessly.
  • In order to generate visually sensible structures, I’d need to make a 3D model for every combination of a tile’s eight neighbours being walls or floors.

The solution to all of these problems begins with making a single tile to the generator in fact be four tiles to the world.

This immediately alleviates the first issue of tightness; levels have a cast-iron guarantee of spaciousness because no matter what the generator does, a single block to it is a comfortable living area to the characters in the world. While tight spaces might be nice in some circumstances, I can engage these by designing constrained tilesets on purpose rather than being forced into it by the fundamentals of the algorithms.

The second issue is also blasted to pieces with no extra effort. If each tile actually covers four tiles, then that covers one whole repeat of the textures involved — without having to manually recalculate UV coordinates all over the shop.

Loads o' space.
Loads o’ space.

The spaciousness also gives rise to the best part of all: streamlining the manufacturing of the prefabricated tile models that will interlock to form the actual game world.

As I said above, with a naive approach you have to manually produce a 3D model for every situation. If one tile is a wall and its neighbour is open then you need a wall edge, and depending on the combination of walls and floors amongst its seven other neighbours then you’ll require a nightmarish combinatorial explosion of corner models.

However, if a tile is four tiles, that gives you a good bit of room to play with sub-components. My solution is to, at the time of actually printing the world, sub-divide each tile into four quads. This way, each quad only has to worry about the combination of the three neighbours that are adjacent to it. If each corner quad considers its own neighbours, the four will slot together within the tile, and slot the tile will together with the rest of the world.

The even better thing here is that most quads can be created once for one corner and auto-rotated to fit the others. Fuckin’ jackpot!

A basic tileset like this can be manufactured in the space of a few hours, and then augemented with other combinations and variations in future. I've also split the tiles into wall and floor parts, so that I can hide the upper bits when your character would be standing behind them.
A basic tileset like this can be manufactured in the space of a few hours, and then augemented with other combinations and variations in future. I’ve also split the tiles into wall and floor parts, so that I can hide the upper bits when your character would be standing behind them.

So the generator works on a four-by-four grid, the printer works on a two-by-two grid, and the game world itself operates on the one-by-one grid. Decorations can be sprinkled on at the one-by-one level to smooth off the hard edges from the larger grid, and away we go!

The main thing now is to wire this up to an actual game. While my current aim is to use procedural generation, the tileset system could as easily be applied to hand-crafted layouts as those conjured out of thin air.

So for now, the plan is to generate “gauntlet” levels with a start point and an exit point, and have a simple scoring system to rate your progress. Points could be awarded for killing enemies, for finding gold, for combo kills and maybe even time taken. I want to make this simple, clean high-score permadeath gauntlet to start with, then refine it and refine it and refine it until I have a seriously solid foundation, until I have the core loop totally nailed down. Then, and only then, will the sweeping hand-crafted narrative-driven epic unfold.

And you tell me...

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.