New! Unlimited audio, video & web asset downloads! Unlimited audio, video & web assets! From \$16.50/m
Advertisement

# Using Tile Masks to Set a Tile's Type Based on Its Surrounding Tiles

Difficulty:IntermediateLength:MediumLanguages:

Commonly used in tile-based games, tile masks let you change a tile depending on its neighbours, allowing you to blend terrains, replace tiles, and more. In this tutorial, I'll show you a scalable, re-usable method for detecting whether a tile's immediate neighbours match one of any number of patterns that you set.

Note: Although this tutorial is written using C# and Unity, you should be able to use the same techniques and concepts in almost any game development environment.

## What Is a Tile Mask?

Consider the following: you're making a Terraria-like, and you want dirt blocks to turn into mud blocks if they are next to water. Let's assume that your world has a lot more water than it does dirt, so the cheapest way you can do this is by checking each dirt block to see if it's next to water, and not vice versa. This is a pretty good time to use a tile mask.

Tile masks are as old as the mountains, and are based on a simple idea. In a 2D grid of objects, a tile can have eight other tiles directly adjacent to it. We'll call this the local range of that central tile (Figure 1).

Figure 1. The local range of a tile.

To decide what to do with your dirt tile, you'll compare its local range to a set of rules. For example, you might look directly above the dirt block and see if there's water there (Figure 2). The set of rules you use to evaluate your local range is your tile mask.

Figure 2. Tile mask for identifying a dirt block as a mud block. The grey tiles can be any other tile.

Of course, you'll also want to check for water in the other directions as well, so most tile masks have more than one spatial arrangement (Figure 3).

Figure 3. The four typical orientations of a tile mask: 0°, 90°, 180°, 270°.

And sometimes you'll find that even really simple tile mask arrangements can be mirrored and non-superposable (Figure 4).

Figure 4. An example of chirality in a tile mask. The top row cannot be rotated to match the bottom row.

## Storage Structures of a Tile Mask

### Storing Its Elements

Each cell of a tile mask has an element inside it. The element at that cell is compared to the corresponding element in the local range to see whether they match.

I use Lists as elements. Lists allows me to perform comparisons of arbitrary complexity, and C#'s Linq provides very handy ways of merging lists into larger lists, reducing the number of changes that I can potentially forget to do.

For example, a list of wall tiles can be combined with a list of floor tiles and a list of opening tiles (doors and windows) to produce a list of structural tiles, or lists of the furniture tiles from individual rooms can be combined to make a list of all furniture. Other games might have lists of tiles that are burnable, or affected by gravity.

### Storing the Tile Mask Itself

The intuitive way to store a tile mask is as a 2D array, but accessing 2D arrays can be slow, and you are going to be doing it a lot. We know that a local range and a tile mask will always consist of nine elements, so we can avoid that time penalty by using a plain 1D array, reading the local range into it from top to bottom, left to right (Figure 4).

Figure 5. Storing a 2D tile mask in a 1D structure.

The spatial relationships between elements are preserved if you ever need them (I haven't needed them so far), and arrays can store pretty much anything. Tilemasks in this format can also be easily rotated by offset read/writes.

### Storing Rotational Information

In some cases you may want to rotate the central tile according to its surroundings, such as when placing a wall next to other walls. I like using jagged arrays to hold the four rotations of a tile mask in the same place, using the outer array's index to indicate the current rotation (more on that in the code).

## Code Requirements

With the basics explained, we can outline what the code needs to do:

1. Define and store tile masks.
2. Rotate tile masks automatically, instead of us having to manually maintain four rotated copies of the same definition.
3. Grab a tile's local range.
4. Evaluate the local range against a tile mask.

The following code is written in C# for Unity, but the concepts should be fairly portable. The example is one from my own work in procedurally converting roguelike text-only maps to 3D (placing a straight section of wall, in this case).

## Define, Rotate and Store the Tile Masks

I automate all of this with one method call to a DefineTilemask method. Here's an example of usage, with method declaration below.

I define the tilemask in its unrotated form. The list called ignored stores characters that don't describe anything in my program: spaces, which are skipped; and underscores, which I use to show an invalid array index. A tile at (0,0) (upper left corner) of a 2D array won't have anything to its North or West, for example, so its local range gets underscores there instead. anyis an empty list which is always evaluated as a positive match.

It's worth explaining the implementation of this code. In DefineTilemask, I provide nine lists as arguments. These lists are placed into a temporary 1D array, and then rotated in +90° steps by writing to a new array in a different order. The rotated tilemasks are then stored in a jagged array, whose structure I use to convey rotational information. If the tilemask at outer index 0 matches, then the tile is placed with no rotation. A match at outer index 1 gives the tile a +90° rotation, and so on.

## Grab a Tile's Local Range

This one is straightforward. It reads the local range of the current tile into a 1D character array, replacing any invalid indices with underscores.

## Evaluate a Local Range With a Tile Mask

And here's where the magic happens! TrySpawningTile is given the local range, a tile mask, the wall piece to spawn if the tile mask matches, and the row and column of the tile being evaluated.

Importantly, the method that performs the actual comparison between local range and tile mask (TileMatchesTemplate) dumps a tile mask rotation as soon as it finds a mismatch. Not shown is basic logic defining what tile masks to use for which tile types (you wouldn't use a wall tile mask on a piece of furniture, for example).

## Appraisal and Conclusion

### Advantages of This Implementation

• Very scalable; just add more tile masks' definitions.
• The checks a tile mask performs can be as complex as you like.
• The code is fairly transparent, and its speed can be improved by performing a few extra checks on the local range before you start going through tile masks.

### Disadvantages of This Implementation

• Can potentially be very expensive. In the absolute worst case you will have (36 × n) + 1 array accesses and calls to List.Contains() before finding a match, where n is the number of tile mask definitions you're searching through. It's essential to do what you can to narrow the list of tile masks down before you begin searching.

### Conclusion

Tile masks have uses not only in world generation or aesthetics, but also in elements that affect gameplay. It's not hard to imagine a puzzle game where tile masks might be used to determine the state of the board or the potential moves of pieces, or editing tools might use a similar system to snap blocks to each other. This article demonstrated a basic implementation of the idea, and I hope you found it useful.

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.