Autotiling

When building tile-based games, placing the right tile variant at every position can be tedious. Autotiling algorithms solve this by automatically selecting appropriate tiles based on their neighbors. Let's explore how these algorithms work and implement them interactively.

The Problem

Imagine you're building a tile-based game world. You have terrain like grass, water, or walls. When you place tiles, the edges need to connect smoothly with their neighbors. A grass tile surrounded by other grass tiles looks different from a grass tile at the edge next to water.

Manual vs Auto Tiling

Click on the grid to place or remove tiles. Watch how autotiling automatically selects the right tile variant:

Bitmasking: The Core Algorithm

The most common autotiling technique is bitmasking. We examine each tile's neighbors and encode their presence as bits in a number. This number then maps to the appropriate tile variant.

4-Bit Bitmasking (Simple)

The simplest form checks only the four cardinal directions: North, East, South, and West.

Neighbor check pattern:
N
W
X
E
S
→ Binary: NESW → Decimal: 0-15
function calculate4BitMask(x, y, grid) {
    let mask = 0;
    if (grid[y - 1]?.[x]) mask |= 1;  // North
    if (grid[y]?.[x + 1]) mask |= 2;  // East
    if (grid[y + 1]?.[x]) mask |= 4;  // South
    if (grid[y]?.[x - 1]) mask |= 8;  // West
    return mask;
}

This gives us 16 possible tile configurations (2^4), which is manageable but doesn't handle corner pieces well.

8-Bit Bitmasking (Advanced)

For better visual quality, we also check diagonal neighbors:

Full neighbor pattern:
NW
N
NE
W
X
E
SW
S
SE
function calculate8BitMask(x, y, grid) {
    let mask = 0;
    // Cardinal directions
    const n = grid[y - 1]?.[x] ? 1 : 0;
    const e = grid[y]?.[x + 1] ? 1 : 0;
    const s = grid[y + 1]?.[x] ? 1 : 0;
    const w = grid[y]?.[x - 1] ? 1 : 0;

    mask |= n << 0;  // North
    mask |= e << 2;  // East
    mask |= s << 4;  // South
    mask |= w << 6;  // West

    // Diagonal neighbors (only if adjacent cardinals exist)
    if (n && e && grid[y - 1]?.[x + 1]) mask |= 1 << 1;  // NE
    if (s && e && grid[y + 1]?.[x + 1]) mask |= 1 << 3;  // SE
    if (s && w && grid[y + 1]?.[x - 1]) mask |= 1 << 5;  // SW
    if (n && w && grid[y - 1]?.[x - 1]) mask |= 1 << 7;  // NW

    return mask;
}
Note: We only check diagonal neighbors when both adjacent cardinal directions have tiles. This prevents invalid corner configurations and reduces the number of tile variants needed.

The 47-Tile System

While 8-bit bitmasking can produce 256 different values, many of these are visually identical when rotated or mirrored. The popular "47-tile system" (used in RPG Maker) reduces this to just 47 unique tiles.

47-Tile Template

Each tile in the template serves a specific purpose:

Hover over a tile to see its purpose

The key insight is that we can break each logical tile into four quarter-tiles, then determine each quarter independently based on only three neighbors:

function getTileQuarters(mask) {
    // For each quarter, check relevant neighbors
    const quarters = {
        topLeft:     getQuarterType(mask, 'N', 'W', 'NW'),
        topRight:    getQuarterType(mask, 'N', 'E', 'NE'),
        bottomLeft:  getQuarterType(mask, 'S', 'W', 'SW'),
        bottomRight: getQuarterType(mask, 'S', 'E', 'SE')
    };
    return quarters;
}

function getQuarterType(mask, vert, horiz, corner) {
    const v = (mask >> directionBit[vert]) & 1;
    const h = (mask >> directionBit[horiz]) & 1;
    const c = (mask >> directionBit[corner]) & 1;

    if (!v && !h) return 'outer-corner';
    if (v && !h) return 'vertical-edge';
    if (!v && h) return 'horizontal-edge';
    if (v && h && !c) return 'inner-corner';
    if (v && h && c) return 'solid';
}

Interactive Bitmasking Explorer

See Bitmasking in Action

Click cells to toggle them and see how the bitmask value changes:

Neighbor Pattern

Bitmask Value

00000000
= 0

Resulting Tile

Comparison of Techniques

Technique Tile Count Visual Quality Complexity Use Case
4-bit (Cardinal) 16 tiles Basic Simple Minimalist games, prototypes
8-bit (Full) 256 tiles* Excellent Moderate Requires optimization
47-tile (RPG Maker) 47 tiles Very Good Moderate 2D RPGs, top-down games
Wang Tiles Varies Excellent Complex Procedural textures

* Most 8-bit configurations can be reduced through rotation and mirroring

Wang Tiles

Wang tiles take a different approach. Instead of checking neighbors, each tile edge has a "color" or type, and tiles can only connect if their edges match.

Wang Tile Matching

Click tiles to rotate them. Adjacent edges must have matching colors:

class WangTile {
    constructor(north, east, south, west) {
        this.edges = { north, east, south, west };
    }

    canConnect(other, direction) {
        const opposite = {
            north: 'south', south: 'north',
            east: 'west', west: 'east'
        };
        return this.edges[direction] === other.edges[opposite[direction]];
    }

    rotate() {
        this.edges = {
            north: this.edges.west,
            east: this.edges.north,
            south: this.edges.east,
            west: this.edges.south
        };
    }
}

Performance Considerations

When implementing autotiling in a game, consider these optimizations:

class AutotiledMap {
    constructor(width, height) {
        this.tiles = Array(height).fill().map(() => Array(width).fill(0));
        this.cache = new Map();
        this.dirty = new Set();
    }

    setTile(x, y, value) {
        this.tiles[y][x] = value;
        // Mark this tile and neighbors as dirty
        for (let dy = -1; dy <= 1; dy++) {
            for (let dx = -1; dx <= 1; dx++) {
                const key = `${x + dx},${y + dy}`;
                this.dirty.add(key);
                this.cache.delete(key);
            }
        }
    }

    getTileMask(x, y) {
        const key = `${x},${y}`;
        if (!this.cache.has(key)) {
            this.cache.set(key, this.calculateMask(x, y));
        }
        return this.cache.get(key);
    }
}

Practical Applications

Autotiling isn't just for terrain. Here are creative uses:

Different Autotiling Applications

Terrain

Pipes/Roads

Platformer Tiles

Implementation Tips

Getting Started:
1. Start with 4-bit bitmasking for prototypes
2. Create a simple tileset with clear edge distinctions
3. Test with various patterns to ensure all cases are handled
4. Optimize only when performance becomes an issue

Common Pitfalls

Further Reading