Tile Maps in Unity

I’ve been experimenting with Unity a lot lately. I’ve usually worked with 3D prototypes but this weekend I wanted to investigate the work that was involved in getting something tile-based up and running.

I was going to do an introductory blog post first, but I’ve made some interesting developments with this so I thought I would document it all here now.


Tile-Based Anything in Unity

The main annoyance I’ve had with trying to do anything tile-based with Unity is that a lot of the documentation and tutorials I’ve read seem to steer you in the direction of using GameObjects for each individual tile. While this approach is simple and easy to follow, I don’t feel that it scales well, and I generally dislike approaches that clog up the Hierarchy this much (granted steps can be taken to organise it properly)

Fortunately, there are some techniques that can be learned to get around the use of GameObjects per tile, this fantastic tutorial series by quill18 introduces you to the concept of creating meshes programmatically, and even touches on extending the editor, a technique that I think is essential to getting the most out of Unity.

I followed enough of the series to get a basic understanding of the process, and set off to produce this:

What we have is a script component in which you specify your level’s dimensions in tiles, along with the scale of each tile in Unity units. The script will take this, generate a mesh and a texture to produce what you see on the left. quill18’s tutorial also shows you how you can add a custom element to the component’s inspector in the form of the ‘Rebuild’ button, which in my script allows you to generate a new level, the changes are also visible in edit mode.


The Process

The end goal with this was to produce something seamless with a very minimal tileset. Something like these which contain just enough information to construct what you see in the above screenshot:

These have of course been scaled up, each tile is 16×16, making each tileset 48×48.

The map is an array of 1’s and 0’s, where 0 is air and 1 is a solid block. Initially the mesh was only textured to show solid tiles, with no inclusion of the edge tiles. Below is a screenshot comparing this original look (foreground tileset) with the new, auto-tiled look (background) which gives all adjacent tiles a connected appearance:

Getting the auto-tiling to work was an interesting challenge on its own, and one that still needs some work before it will work with any tileset I give it.


Auto-Tiling Our Level

The first step in my approach was to assign numbers to each of the corner and edge tiles surrounding the center ‘wall’ tile, like so:

Then, for each tile in our level’s array, we compare its surrounding 8 tiles and add the corresponding number from the above image.

int getAdjacentTiles(int x, int y) {
    int value = 0;
    int center = 0; // have we reached the center tile yet?
    for (int i = 0; i < 8; i++) {
        int currentX = x + (i%3) - 1;
        int currentY = y + (i/3) - 1;
        if (currentX == x && currentY == y) {
            center = 1; // for offsetting i after the center tile.
            continue; // skip the center tile
        }
        if (tileOutOfBounds(currentX, currentY) 
        ||  levelTiles[currentY,currentX] == 1) {
            value |= 1 << (i - center);
        }
    }
    return value;
}

After getting this value for a tile, you can determine which edges/corners need to be added by checking if the expression:

value & (1 << i)

is non-zero.

In Unity, stitching adjacent textures together on to a single tile was easier said than done, although I could be something crucial in Texture2D’s functionality. Traditionally, you can use Texture2D’s getPixels() and setPixels() to effectively combine portions of textures together. This is fine so long as you don’t intend for your pixels to overlap, but in my case this was not so, and as a result the transparent pixels would overlap the colour.

I got around this with a naive rewrite of the setPixels function, which attempts to ignore ‘transparent’ pixels so that only the non-transparent parts of an image are overlayed:

private void setPixels(ref Texture2D texture, int x, int y, int w, int h, Color[] p)
{
    for (int i = 0; i < w* h; i++)
    {
        if (p[i].a != 0) // if the pixel to be written is solid
        {
            texture.SetPixel(x + (i % w), y + (i / w), p[i]);
        }
        else
        {
            // this was a hack to ensure that the image wouldn't
            // lose its transparency, like I said it needs work.
            Color c = texture.GetPixel(x + (i % w), y + (i / w));
            if (c == Color.white || c == Color.clear)
            {
                texture.SetPixel(x + (i % w), y + (i / w), Color.clear);
            }
        }
    }
}

This is something I will come back to, since I’m sure there’s going to be a better way to get around this then what I went with. That said, I’m very pleased with the end result. I have a script generating a mesh, a random level and auto-tiling the mesh according to that level’s data.


What’s Left?

The next step with this is that I want to extend the editor further to allow the user to draw the level on to the mesh themselves, at that point I may release what I’ve got so far for others to mess around with.

~ Oli S-L