Skip to content

Tile Data

Every cell of a tilemap is one 32 bit unsigned integer, the tile data. This page describes how it is packed and gives you the helpers to read and build it, for when you generate maps, save them in your own format or inspect what is in a cell.

Concept

A uint holds three fields:

Bits Mask constant Holds
0 to 15 Tileset.k_TileDataMask_TileId (0x0000FFFF) Tile id, the index of the tile in the tileset.
16 to 27 Tileset.k_TileDataMask_BrushId (0x0FFF0000) Brush id. 0 means the cell was painted without a brush.
28 to 31 Tileset.k_TileDataMask_Flags (0xF0000000) Four flags, below.

The flags:

Bit Constant Meaning
31 Tileset.k_TileFlag_FlipV (0x80000000) Mirrored top to bottom.
30 Tileset.k_TileFlag_FlipH (0x40000000) Mirrored left to right.
29 Tileset.k_TileFlag_Rot90 (0x20000000) Rotated 90 degrees clockwise.
28 Tileset.k_TileFlag_Updated (0x10000000) Internal. Set once a brush has refreshed the cell. Leave it alone.

An empty cell is the whole word set, Tileset.k_TileData_Empty (0xFFFFFFFF). Test for it by comparing the full value:

uint data = tilemap.GetTileData(x, y);
bool isEmpty = data == Tileset.k_TileData_Empty;

Tileset.k_TileId_Empty (0xFFFF) is the tile id part of an empty cell, and it is also what a brush cell carries before its brush has picked a tile.

The format is saved in every scene and asset that holds a tilemap, which is why it never changes between versions. Tile ids are positions in the tileset's tile list, and brush ids are the ids the tileset assigned to each brush asset.

Reading the fields

Tileset has static helpers:

uint data = tilemap.GetTileData(x, y);

int tileId  = Tileset.GetTileIdFromTileData(data);    // 0xFFFF for an empty cell
int brushId = Tileset.GetBrushIdFromTileData(data);   // -1 for an empty cell, 0 for no brush
uint flags  = Tileset.GetTileFlagsFromTileData(data); // still in the top 4 bits

bool flipH = (data & Tileset.k_TileFlag_FlipH) != 0;
bool rot90 = (data & Tileset.k_TileFlag_Rot90) != 0;

To ask whether a cell was painted with a brush, test brushId > 0. An empty cell gives -1 and a plain tile gives 0, so != 0 would count empty cells as brush cells. If you extract the brush id yourself with a mask and a shift instead of the helper, an empty cell gives 4095.

From the ids you get to the objects:

Tile tile = tilemap.Tileset.GetTile(tileId);          // null if the id is out of range
TilesetBrush brush = tilemap.Tileset.FindBrush(brushId); // null for 0 or -1

tilemap.GetTile(x, y) and tilemap.GetBrush(x, y) do both steps in one call.

Building tile data

Usually you do not have to, because STETilemap.SetTile takes the parts and packs them.

tilemap.SetTile(x, y, tileId);                                   // plain tile
tilemap.SetTile(x, y, tileId, Tileset.k_BrushId_Default, eTileFlags.Rot90);
tilemap.SetTile(x, y, Tileset.k_TileId_Empty, brushId);          // brush tile

When you need the value itself, for a pattern or your own save format, pack it the way SetTile does:

static uint MakeTileData(int tileId, int brushId, uint flags)
{
    return (flags & Tileset.k_TileDataMask_Flags)
        | (((uint)brushId << 16) & Tileset.k_TileDataMask_BrushId)
        | ((uint)tileId & Tileset.k_TileDataMask_TileId);
}

uint mirroredGrass = MakeTileData(12, Tileset.k_BrushId_Default, Tileset.k_TileFlag_FlipH);

A brush cell written from code should carry the brush id and an empty tile id, as the editor writes it. The brush chooses the tile at the next rebuild and then sets k_TileFlag_Updated, so what you read back afterwards differs from what you wrote in the tile id and in bit 28.

The TileData class

TileData unpacks a value into fields you can edit and packs it again. It costs an allocation per use, so keep it out of tight loops over a whole map.

TileData cell = new TileData(tilemap.GetTileData(12, 45));
if (!cell.IsEmpty)
{
    cell.flipHorizontal = !cell.flipHorizontal;
    tilemap.SetTileData(12, 45, cell.BuildData());
    tilemap.UpdateMesh();
}
Member What it is
tileId, brushId The two ids. brushId is 0 for an empty cell.
flipHorizontal, flipVertical, rot90 The three orientation flags.
IsEmpty True when brushId is 0 and tileId is Tileset.k_TileId_Empty. The flag fields mean nothing for an empty cell.
BuildData(), Value Pack the fields. An empty cell packs to k_TileData_Empty.
SetData(uint) Unpack a value into this instance.

new TileData() starts out empty.

Flips and rotation

The renderer applies the flips first and the 90 degree rotation second. The eight combinations give every orientation of a square tile:

Flags set The tile appears
none As in the atlas.
Rot90 Rotated 90 degrees clockwise.
FlipH + FlipV Rotated 180 degrees.
FlipH + FlipV + Rot90 Rotated 270 degrees clockwise, which is 90 counterclockwise.
FlipH Mirrored left to right.
FlipV Mirrored top to bottom.
FlipH + Rot90 Mirrored across the diagonal from bottom left to top right.
FlipV + Rot90 Mirrored across the diagonal from top left to bottom right.

Tile colliders and tile prefab instances follow the same flags, so a rotated wall tile collides as it looks.

To turn a tile that already has flags, do not XOR the new flag into it. A horizontal flip on a rotated tile has to flip the other bit. TilesetBrush.ApplyAndMergeTileFlags does that:

// Rotate whatever is in the cell by a further 90 degrees clockwise.
uint data = tilemap.GetTileData(x, y);
if (data != Tileset.k_TileData_Empty)
{
    tilemap.SetTileData(x, y, TilesetBrush.ApplyAndMergeTileFlags(data, Tileset.k_TileFlag_Rot90));
    tilemap.UpdateMesh();
}

On a brush cell the brush owns the flags and may replace them when it refreshes.

eTileFlags

SetTile takes the flags as eTileFlags, a [Flags] enum whose values are the flag bits shifted down by 28:

Value Same as
eTileFlags.Rot90 (2) k_TileFlag_Rot90
eTileFlags.FlipH (4) k_TileFlag_FlipH
eTileFlags.FlipV (8) k_TileFlag_FlipV
eTileFlags.Updated (1) k_TileFlag_Updated. Do not pass it.

Combine them with |, for example eTileFlags.FlipH | eTileFlags.FlipV for a 180 degree turn.

Before 1.7.7, FlipH and FlipV were swapped in this enum, so code written against older versions that compensated for it needs the two swapped back.

What the rebuild does with bad data

A tile id beyond the end of the tileset's tile list, written without a brush, logs a TileId ... not found! warning at the next rebuild and the cell is emptied. A brush id the tileset does not know logs BrushId ... not found! and the cell keeps its tile id without the brush. Both happen when a map outlives changes to its tileset, which is why adding tiles to a tileset needs care. The FAQ covers it.

Reference

Member What it does
Tileset.k_TileData_Empty 0xFFFFFFFF, an empty cell.
Tileset.k_TileId_Empty 0xFFFF, the empty tile id.
Tileset.k_BrushId_Default 0, no brush.
Tileset.GetTileIdFromTileData(uint) Bits 0 to 15.
Tileset.GetBrushIdFromTileData(uint) Bits 16 to 27, or -1 for an empty cell.
Tileset.GetTileFlagsFromTileData(uint) Bits 28 to 31, left in place.
TilesetBrush.ApplyAndMergeTileFlags(uint, uint) Adds a flip or rotation to existing tile data.