| Header | Link |
|---|---|
| Purpose | Purpose |
| Use Cases | Use Cases |
| Practical Example | Practical Example |
| Reference | Reference |
TileMap2D builds a 2D level out of a grid of atlas tiles from a .ptileset instead of thousands of hand-placed sprites. Solid tiles can bake into static colliders and cast 2D light shadows, so one node gives you the level's visuals, collision, and shadow casters at once. It suits both hand-authored stages and grids generated at runtime.
- Hand-authored platformer or top-down stages: set
tilesetand the row-majortilesgrid in the scene, withcollision_enabled = trueso solid tiles become static colliders and shadow casters. - Procedurally generated dungeon floors, caves, or terrain: write
width,height, and thetilesarray on the node at runtime withwith_node_mut!(ctx.run, TileMap2D, id, ...); the runtime only re-bakes collision when tile content changes. - Sloped, spiked, or rounded tiles beyond box collision: give the tile an explicit
collision_shape(rect,circle,triangle, or convexpolygon) in the tileset; auto tiles merge into larger rect colliders. - Filter what the level collides with:
collision_layers/collision_maskon the tilemap (see BitMask).
Use TileMap2D when repeated atlas cells define both the level image and grid collision. Use individual sprites or mesh nodes when objects need independent transforms, scripts, or non-grid placement. Scene data owns a fixed map; a generator script owns a procedural map and writes the complete grid only when it changes.
Generate a walled room at load time by writing the tile grid directly on the node. Tile id 1 is a solid wall (collision) and id 0 is open floor.
use perro_api::prelude::*;
lifecycle!({
fn on_init(&self, ctx: &mut ScriptContext<'_, API>) {
let (w, h) = (16u32, 12u32);
let mut tiles = vec![0i32; (w * h) as usize];
for y in 0..h {
for x in 0..w {
let edge = x == 0 || y == 0 || x == w - 1 || y == h - 1;
tiles[(y * w + x) as usize] = if edge { 1 } else { 0 };
}
}
let _ = with_node_mut!(ctx.run, TileMap2D, ctx.id, |map| {
map.width = w;
map.height = h;
map.tiles = tiles;
map.collision_enabled = true;
});
}
});TileMap2D is the runtime 2D tile map node.
It draws atlas tiles from a .ptileset and can emit static 2D colliders.
[level]
[TileMap2D]
tileset = "res://tiles/world.ptileset"
width = 8
height = 4
empty_tile = -1
tiles = [
1, 1, 1, 1, 1, 1, 1, 1,
1, -1, -1, -1, -1, -1, -1, 1,
1, -1, -1, -1, -1, -1, -1, 1,
1, 1, 1, 1, 1, 1, 1, 1,
]
collision_enabled = true
collision_layers = [1]
collision_mask = []
[/TileMap2D]
[/level]
tileset:.ptilesetpath.width/height: tile grid dimensions.tiles: row-major tile ids.empty_tile: id skipped by draw and collision; default-1.visible: draw toggle.z_index: 2D draw order.collision_enabled: enables generated static colliders.collision_layers: generated collider tagged layers.collision_mask: generated collider ignored layers.
Collision tiles also cast 2D light shadows when collision_enabled = true.
Auto rectangles share the collision bake merge; explicit shapes keep their
circle, triangle, or convex polygon silhouette. See 2D Shadows.
Collision comes from the tileset. Each tile decides if it wants collider generation.
collision = true with no shape uses collision_shape = "auto".
auto builds a full tile bounds collider.
Adjacent auto tiles merge into larger rect colliders at runtime.
Explicit tile shapes stay as per-tile colliders.
Runtime supports rect, circle, triangle, and convex polygon explicit shapes.
Polygon example:
collision_shape = { polygon = { points = [(0, 0), (16, 0), (8, 16)] offset = (0, 0) } }
Runtime bake hashes tile grid plus tileset collision data. It rebuilds only when tile content or collision metadata changes.
Static pipeline parses .ptileset into packed PTSET bytes.
Those bytes include per-tile collision metadata.
Static runtime loads binary tilesets by path hash before disk.
Static release builds load binary .ptileset data from the static asset lookup.
Dynamic or edited tilemaps still use runtime collision bake.