Drawing tile maps in 2D games


Some two weeks ago, I've participated in the Ludum Dare compo and created a 2D platforming game. Almost any 2D game that uses tile-based maps will face the issue of how to render them properly. This may seem trivial – just use texture X on tile X and texture Y on tile Y – but unless you're aiming for a very minimalistic style, you'll probably want edges and corners to look a bit differently than a "solid" tile.

An image showing the difference between a simple rendering style and the more advanced one that's the focus of this article.

While this requires some extra work on the graphics, it's surprisingly simple code-wise and leads to nice-looking results. Follow me as I describe my approach.

Dividing tiles into quarts

In the first step, we divide each tile into four quarters, which I shall henceforth call "quarts". A tile has four quarts: the top-left, top-right, bottom-right and bottom-left one. Each quart stores information about the tile's neighbours.

An illustration of quarts within a tile and their relationships with neighbouring tiles.

What this means is that, effectively, each quart is a three-field bitmask:

  1. Whether there's a continuous tile on the X-axis.

  2. Whether there's a continuous tile on the Y-axis.

  3. Whether there's a continuous tile "in the corner" - e.g. for the top-left quart, this means the [x-1][y-1] tile.


An interesting aspect to consider when calculating quarts is what I call their "stickiness". Quarts are non-sticky if we only consider tiles of the same type as continuous (left image), and sticky if we consider all non-empty tiles to be continuous (right image).

A comparison of non-sticky quarts versus sticky quarts.

Using sticky quarts typically leads to a smoother-looking map, where the elements of the terrain fade into each other (kind of) seamlessly. On the other hand, non-sticky quarts can be used to obtain sharp edges between different tile types. If needed, one might extend this approach into giving each tile type its own "stickiness" property, or dividing tile types into groups and only considering tiles of the same group to be continuous.

Creating the tile graphics

Before we can render quarts, we need to prepare the tile graphics. In my projects, I use the following format:

An example set of tile graphics.

First, there's the solid tile; it's then followed by the horizontal and the vertical tile, finishing with two corner tiles – the outside-corner and inside-corner.

You may be thinking "wait a moment – there's eight possible quart values, yet only five tile graphics?". The reason is rather simple and becomes obvious when visualised:

An outside-corner quart stays an outside-corner quart, no matter the "corner" bit.
An horizontal/vertical quart stays an horizontal/vertical quart, no matter the "corner" bit.
The corner bit makes the difference between a solid and an inside-corner quart.

As such, if you're particularly lazy (or have tight constraints), you can merge inside-corners and solid tiles into one by just ignoring the "corner" bit in your code and only considering the XY neighbours – this will leave you with four graphics per tile instead of five.

Rendering the map

To render the map, we must first calculate quarts. This is rather simple – all we have to do is, for all tiles, assume the initial state as zero (no neighbours) and then check whether X/Y/corner neighbours are present – and if so, flip the appropriate bit.

An example showing how quarts are calculated and rendered.

In the example above, the state of the center tile's quarts is as follows:

Once we calculate the quarts, all that's left to do is to draw them. One important thing to note is that since we're not rendering whole tiles anymore, but rather quarts, the number of draw calls will effectively quadruple. This can lead to a huge loss in performance; as such, it's usually a good idea to pre-render the map into an offscreen texture and then paste this texture onto the screen, instead of re-drawing the map from quarts on each frame.

Share with friends

e-mail Google+ Hacker News LinkedIn Reddit Tumblr VKontakte Wykop Xing


Do you have some interesting thoughts to share? You can comment by sending an e-mail to blog-comments@svgames.pl.