A guide to automatic tile selection for procedural maps & level editors
When building tile-based games, you usually want tiles to connect seamlessly. Grass should blend into dirt; walls should form proper corners and edges. Placing each tile variant by hand is tedious and error-prone. Autotiling automates this: for every cell in the grid, we look at its neighbors and pick the right tile variant automatically.
This article builds up autotiling from first principles, starting with the simplest approach (4 neighbors) and working up to the full 8-neighbor bitmask and the elegant dual-grid technique. Every section has an interactive demo — click on the grids to experiment.
4-bit autotiling — click to paint terrain. Tiles update in real time.
Every autotiling system follows the same basic pattern:
The number of neighbors you check determines the number of possible tile variants:
| Neighbors checked | Bits | Possible values | Unique tiles needed |
|---|---|---|---|
| 4 (cardinal) | 4 | 0 – 15 | 16 |
| 8 (cardinal + diagonal) | 8 | 0 – 255 | 47 (with reduction) |
Wait — 8 bits should give 256 values, so why only 47 tiles? We'll get to that in Section 3. Let's start simple.
We check only the four cardinal (N/E/S/W) neighbors. Each direction gets one bit:
| Direction | Bit | Value |
|---|---|---|
| North | 0 | 1 |
| East | 1 | 2 |
| South | 2 | 4 |
| West | 3 | 8 |
For a cell with matching neighbors to the North and West, the bitmask is 1 + 8 = 9. This gives us exactly 16 possible bitmask values (0 through 15), one for each combination.
Each bitmask value maps to a specific tile shape. Here are all 16:
All 16 four-bit tile variants with their bitmask values.
function getAutotileIndex4(grid, x, y) {
const here = grid[y][x];
let mask = 0;
if (y > 0 && grid[y-1][x] === here) mask |= 1; // North
if (x < width - 1 && grid[y][x+1] === here) mask |= 2; // East
if (y < height - 1 && grid[y+1][x] === here) mask |= 4; // South
if (x > 0 && grid[y][x-1] === here) mask |= 8; // West
return mask;
}
That's it. For each cell, compute the mask, then draw tileset[mask]. The demo at the top uses exactly this code.
Four-bit autotiling handles edges nicely, but notice how corners look awkward — there's no way to distinguish an inner corner from an outer corner. To fix this, we also check diagonal neighbors.
| Direction | Bit | Value |
|---|---|---|
| North | 0 | 1 |
| NorthEast | 1 | 2 |
| East | 2 | 4 |
| SouthEast | 3 | 8 |
| South | 4 | 16 |
| SouthWest | 5 | 32 |
| West | 6 | 64 |
| NorthWest | 7 | 128 |
Eight bits naively gives 256 values, but many are impossible or redundant. The key insight: a diagonal neighbor only matters if both adjacent cardinal neighbors are also present.
For example, the NorthEast diagonal only matters if both North and East are filled. If North is empty, there's no visible corner to the NorthEast regardless of what's there. So we mask out irrelevant diagonal bits:
function getAutotileIndex8(grid, x, y) {
const W = grid[0].length, H = grid.length;
const here = grid[y][x];
const match = (dx, dy) =>
x+dx >= 0 && x+dx < W && y+dy >= 0 && y+dy < H
&& grid[y+dy][x+dx] === here;
const n = match( 0,-1), e = match( 1, 0),
s = match( 0, 1), w = match(-1, 0);
const ne = match( 1,-1), se = match( 1, 1),
sw = match(-1, 1), nw = match(-1,-1);
let mask = 0;
if (n) mask |= 1;
if (n && e && ne) mask |= 2;
if (e) mask |= 4;
if (e && s && se) mask |= 8;
if (s) mask |= 16;
if (s && w && sw) mask |= 32;
if (w) mask |= 64;
if (w && n && nw) mask |= 128;
return mask;
}
With this reduction, only 47 unique tile shapes are possible. This is called the "blob" tileset or 47-tile set.
Click below to paint. Notice how inner corners (concave turns) are now properly handled — something the 4-bit version couldn't do.
8-bit (47-tile) autotiling — notice the smooth inner corners.
In practice, you don't draw 47 (or 256) separate sprites. There are two common strategies:
Pre-compute a mapping from each valid bitmask to a tile index (0–46). Many engines (Godot, Tiled, RPG Maker) use this approach. You create the 47 tiles in your art tool, assign them indices, and build a lookup array.
Split each tile into four quarter-tiles (mini-tiles). Each quarter only needs to know about three neighbors (two cardinal + one diagonal). This means each quarter has only 2³ = 8 possible states — so you need just 8 mini-tile variants per quarter. In total: 5 unique mini-tile shapes (due to symmetry), composited into 4 quadrants.
RPG Maker uses a variant of this mini-tile approach, which is why its tilesets look compact yet produce smooth results.
There's an elegant alternative to bitmasks that simplifies art production: the dual-grid (sometimes called "dual tilemap") technique.
The idea: maintain two grids offset by half a tile. The data grid stores your terrain values. The display grid is offset so that each display tile sits at the intersection of four data cells:
Data grid: Display grid (offset by half):
A . B . C ╔═══╦═══╗
. . . . . ║ ab║ bc║
D . E . F ╠═══╬═══╣
. . . . . ║ de║ ef║
G . H . I ╚═══╩═══╝
Each display tile looks at the four data-grid corners surrounding it. With two terrain types, that's 2⁴ = 16 possible combinations — the same as 4-bit autotiling, but now the tiles represent transitions between four corners rather than edges from a center.
Dual-grid autotiling — click to toggle data cells (small squares). Display tiles update automatically.
Wang tiles come from mathematics (Hao Wang, 1961) but map beautifully onto game tiling. The concept: each tile edge is assigned a color (or label). Tiles can only be placed next to each other if their shared edges have the same color.
In autotiling terms, the bitmask encodes which edges should "match" with neighbors. Wang tiles formalize this — and they also enable aperiodic tiling, where you can fill an infinite plane without repeating patterns. This is useful for procedural generation to avoid the visual monotony of repeating tiles.
A common game-dev application: create a small set of Wang tiles with two edge colors (2-edge-color Wang tile set = 16 tiles, same count as our 4-bit autotile set). The tile placement algorithm ensures matching edges, producing organic-looking variety.
If you've used Marching Squares for contour rendering, you may recognize the dual-grid idea. Marching Squares assigns values to grid corners and draws contour lines through cells. Dual-grid autotiling is essentially the same concept, but instead of drawing vector contour lines, you select pre-drawn tile sprites.
| Marching Squares | Dual-Grid Autotiling | |
|---|---|---|
| Input | Scalar field at corners | Terrain type at corners |
| Cases | 16 (4 corners × 2 states) | 16 (4 corners × 2 states) |
| Output | Line segments / polygons | Tile sprites |
With more than two terrain types, assign a priority to each. A cell "matches" its neighbor if the neighbor has the same or higher priority. Each terrain gets its own tileset overlay, drawn from lowest to highest priority.
Autotile indices are stable — a cell's bitmask only changes when a neighbor changes. So you can safely animate tiles (e.g., water ripples) by cycling frames within the same bitmask index.
Recompute bitmasks only when the grid changes, and only for the cells whose neighbors were modified. Cache the resulting tile indices. This makes autotiling essentially free at runtime.
Many engines expect specific tileset arrangements:
| Technique | Tiles needed | Corners? | Art complexity | Best for |
|---|---|---|---|---|
| 4-bit bitmask | 16 | No | Low | Prototyping, simple styles |
| 8-bit blob | 47 | Yes | Medium | Polished 2D games |
| Dual grid | 16 | Yes | Low | Smooth terrain transitions |
| Mini-tile (2×2) | 5 shapes | Yes | Low | RPG Maker style |
Autotiling turns a tedious art/design problem into a simple algorithmic one. Once you set up the tileset and the bitmask logic, your maps just work — every edge lines up, every corner is smooth, and your level designers (or your procedural generator) can focus on the big picture instead of fiddling with individual tiles.
Written 2026. Inspired by Red Blob Games. Interactive demos use HTML5 Canvas with no external dependencies.