Expert blueprint for TileMapLayer and TileSet systems for efficient 2D level design. Covers terrain autotiling, physics layers, custom data, navigation integration, and runtime manipulation. Use when building grid-based levels OR implementing destructible tiles. Keywords TileMapLayer, TileSet, terrain, autotiling, atlas, physics layer, custom data.
TileMapLayer grids, TileSet atlases, terrain autotiling, and custom data define efficient 2D level systems.
Expert TileMap serialization and chunking manager for large worlds.
set_cell() = 1000 individual function calls = slow. Use set_cells_terrain_connect() for bulk OR cache changes, apply once.set_cell(pos, atlas_coords) without source_id? Wrong overload = crash OR silent failure. Use set_cell(pos, source_id, atlas_coords).set_cell(mouse_position)local_to_map()local_to_map(global_pos)set_cells_terrain_connect() with terrain sets for autotiling.tile_cache[pos] = get_cell_tile_data(pos).TileMapLayer node# Each tile can have:
# - Physics Layer: CollisionShape2D for each tile
# - Terrain: Auto-tiling rules
# - Custom Data: Arbitrary properties
Add collision to tiles:
extends TileMapLayer
func _ready() -> void:
# Set tile at grid coordinates (x, y)
set_cell(Vector2i(0, 0), 0, Vector2i(0, 0)) # source_id, atlas_coords
# Get tile at coordinates
var atlas_coords := get_cell_atlas_coords(Vector2i(0, 0))
# Clear tile
erase_cell(Vector2i(0, 0))
extends TileMapLayer
func _input(event: InputEvent) -> void:
if event is InputEventMouseButton and event.pressed:
var global_pos := get_global_mouse_position()
var tile_pos := local_to_map(global_pos)
# Place grass tile (assuming source_id=0, atlas=(0,0))
set_cell(tile_pos, 0, Vector2i(0, 0))
func flood_fill(start_pos: Vector2i, tile_source: int, atlas_coords: Vector2i) -> void:
var cells_to_fill: Array[Vector2i] = [start_pos]
var original_tile := get_cell_atlas_coords(start_pos)
while cells_to_fill.size() > 0:
var current := cells_to_fill.pop_back()
if get_cell_atlas_coords(current) != original_tile:
continue
set_cell(current, tile_source, atlas_coords)
# Add neighbors
for dir in [Vector2i.UP, Vector2i.DOWN, Vector2i.LEFT, Vector2i.RIGHT]:
cells_to_fill.append(current + dir)
extends TileMapLayer
func paint_terrain(start: Vector2i, end: Vector2i, terrain_set: int, terrain: int) -> void:
for x in range(start.x, end.x + 1):
for y in range(start.y, end.y + 1):
set_cells_terrain_connect(
[Vector2i(x, y)],
terrain_set,
terrain,
false # ignore_empty_terrains
)
# Scene structure:
# Node2D (Level)
# ├─ TileMapLayer (Ground)
# ├─ TileMapLayer (Decoration)
# └─ TileMapLayer (Collision)
# Each layer can have different:
# - Rendering order (z_index)
# - Collision layers/masks
# - Modulation (color tint)
Check collision from code:
func _physics_process(delta: float) -> void:
# TileMapLayer acts as StaticBody2D
# CharacterBody2D.move_and_slide() automatically detects tilemap collision
pass
# In TileSet physics layer settings:
# - Enable "One Way Collision"
# - Set "One Way Collision Margin"
# Character can jump through from below
func get_tile_damage(tile_pos: Vector2i) -> int:
var tile_data := get_cell_tile_data(tile_pos)
if tile_data:
return tile_data.get_custom_data("damage_per_second")
return 0
# Static geometry: Single large TileMapLayer
# Dynamic tiles: Separate layer for runtime changes
# Split world into multiple TileMapLayer nodes
# Load/unload chunks based on player position
const CHUNK_SIZE := 32
func load_chunk(chunk_coords: Vector2i) -> void:
var chunk_name := "Chunk_%d_%d" % [chunk_coords.x, chunk_coords.y]
var chunk := TileMapLayer.new()
chunk.name = chunk_name
chunk.tile_set = base_tileset
add_child(chunk)
# Load tiles for this chunk...
Use with NavigationAgent2D:
# Navigation automatically created from TileMap
# NavigationAgent2D.get_next_path_position() works immediately
TileSet Layers:
- Ground (terrain=grass, dirt, stone)
- Walls (collision + rendering)
- Decoration (no collision, overlay)
MANDATORY: Read before implementing terrain systems or runtime placement.
Runtime terrain autotiling with set_cells_terrain_connect batching and validation.
Chunk-based TileMap management with batched updates - essential for large procedural worlds.
# ✅ Good - smooth terrain transitions
set_cells_terrain_connect(tile_positions, 0, 0)
# ❌ Bad - manual tile assignment for organic shapes
for pos in positions:
set_cell(pos, 0, Vector2i(0, 0))
# Background layers
$Background.z_index = -10
# Ground layer
$Ground.z_index = 0
# Foreground decoration
$Foreground.z_index = 10
func destroy_tile(world_pos: Vector2) -> void:
var tile_pos := local_to_map(world_pos)
var tile_data := get_cell_tile_data(tile_pos)
if tile_data and tile_data.get_custom_data("destructible"):
erase_cell(tile_pos)
# Spawn particle effect, drop items, etc.
@onready var highlight_layer: TileMapLayer = $HighlightLayer
func highlight_tile(tile_pos: Vector2i) -> void:
highlight_layer.clear()
highlight_layer.set_cell(tile_pos, 0, Vector2i(0, 0))