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:
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. |