Skill v1.0.1
currentAutomated scan100/1001 files
version: "1.0.1" name: godot-tilemap-mastery description: "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."
TileMap Mastery
TileMapLayer grids, TileSet atlases, terrain autotiling, and custom data define efficient 2D level systems.
Available Scripts
tilemap_data_manager.gd
Expert TileMap serialization and chunking manager for large worlds.
terrain_path_painter.gd
Advanced runtime terrain autotiling (Terrains v2) for roads, rivers, and organic paths.
destructible_tile_logic.gd
Pattern for managing tile health and breakage based on Custom Data Layers.
gameplay_data_query.gd
Efficiently reading Custom Data (friction, hazards) to drive character/physics logic.
procedural_chunk_batcher.gd
Optimized procedural generation using bulk tile placement logic for better performance.
sorting_Z_layering.gd
Handling Y-sorting and Z-index layering for 2.5D effects and multi-floor buildings.
physics_shape_interaction.gd
Expert TileMap physics: handling one-way collisions and collision layer management.
nav_mesh_teleport_fix.gd
Runtime navigation updates for dynamic world-shifting and destructible environments.
tile_pattern_stamper.gd
Using TileMapPattern for efficiently "stamping" complex, multi-tile structural pieces.
fast_metadata_cache.gd
Optimization: caching TileData metadata for high-frequency gameplay queries.
tilemap_layer_v43_upgrade.gd
Managing the transition to the Godot 4.3 standard of multiple TileMapLayer nodes.
NEVER Do in TileMaps
- NEVER use set_cell() in loops without batching — 1000 tiles ×
set_cell()= 1000 individual function calls = slow. Useset_cells_terrain_connect()for bulk OR cache changes, apply once. - NEVER forget source_id parameter —
set_cell(pos, atlas_coords)without source_id? Wrong overload = crash OR silent failure. Useset_cell(pos, source_id, atlas_coords). - NEVER mix tile coordinates with world coordinates —
set_cell(mouse_position)withoutlocal_to_map()? Wrong grid position. ALWAYS convert:local_to_map(global_pos). - NEVER skip terrain set configuration — Manual tile assignment for organic shapes? 100+ tiles for grass patch. Use
set_cells_terrain_connect()with terrain sets for autotiling. - NEVER use TileMap for dynamic entities — Enemies/pickups as tiles? No signals, physics, scripts. Use Node2D/CharacterBody2D, reserve TileMap for static/destructible geometry.
- NEVER query get_cell_tile_data() in _physics_process — Every frame tile data lookup? Performance tank. Cache tile data in dictionary:
tile_cache[pos] = get_cell_tile_data(pos).
Step 1: Create TileSet Resource
- Create a
TileMapLayernode - In Inspector: TileSet → New TileSet
- Click TileSet to open bottom TileSet editor
Step 2: Add Tile Atlas
- In TileSet editor: + → Atlas
- Select your tile sheet texture
- Configure grid size (e.g., 16x16 pixels per tile)
Step 3: Add Physics, Collision, Navigation
# Each tile can have:# - Physics Layer: CollisionShape2D for each tile# - Terrain: Auto-tiling rules# - Custom Data: Arbitrary properties
Add collision to tiles:
- Select tile in TileSet editor
- Switch to "Physics" tab
- Draw collision polygon
Using TileMapLayer
Basic Tilemap Setup
extends TileMapLayerfunc _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 coordinatesvar atlas_coords := get_cell_atlas_coords(Vector2i(0, 0))# Clear tileerase_cell(Vector2i(0, 0))
Runtime Tile Placement
extends TileMapLayerfunc _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))
Flood Fill Pattern
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:continueset_cell(current, tile_source, atlas_coords)# Add neighborsfor dir in [Vector2i.UP, Vector2i.DOWN, Vector2i.LEFT, Vector2i.RIGHT]:cells_to_fill.append(current + dir)
Terrain Auto-Tiling
Setup Terrain Set
- In TileSet editor: Terrains tab
- Add Terrain Set (e.g., "Ground")
- Add Terrain (e.g., "Grass", "Dirt")
- Assign tiles to terrain by painting them
Use Terrain in Code
extends TileMapLayerfunc 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)
Multiple Layers Pattern
# 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)
Physics Integration
Enable Physics Layer
- TileSet editor → Physics Layers
- Add physics layer
- Assign collision shapes to tiles
Check collision from code:
func _physics_process(delta: float) -> void:# TileMapLayer acts as StaticBody2D# CharacterBody2D.move_and_slide() automatically detects tilemap collisionpass
One-Way Collision Tiles
# In TileSet physics layer settings:# - Enable "One Way Collision"# - Set "One Way Collision Margin"# Character can jump through from below
Custom Tile Data
Define Custom Data Layer
- TileSet editor → Custom Data Layers
- Add property (e.g., "damage_per_second: int")
- Set value for specific tiles
Read Custom Data
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
Performance Optimization
Use TileMapLayer Groups
# Static geometry: Single large TileMapLayer# Dynamic tiles: Separate layer for runtime changes
Chunking for Large Worlds
# Split world into multiple TileMapLayer nodes# Load/unload chunks based on player positionconst CHUNK_SIZE := 32func 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_namechunk.tile_set = base_tilesetadd_child(chunk)# Load tiles for this chunk...
Navigation Integration
Setup Navigation Layer
- TileSet editor → Navigation Layers
- Add navigation layer
- Paint navigation polygons on tiles
Use with NavigationAgent2D:
# Navigation automatically created from TileMap# NavigationAgent2D.get_next_path_position() works immediately
Best Practices
1. Organize TileSet by Purpose
TileSet Layers:- Ground (terrain=grass, dirt, stone)- Walls (collision + rendering)- Decoration (no collision, overlay)
Available Scripts
MANDATORY: Read before implementing terrain systems or runtime placement.
terrain_autotile.gd
Runtime terrain autotiling with set_cells_terrain_connect batching and validation.
tilemap_chunking.gd
Chunk-based TileMap management with batched updates - essential for large procedural worlds.
2. Use Terrain for Organic Shapes
# ✅ Good - smooth terrain transitionsset_cells_terrain_connect(tile_positions, 0, 0)# ❌ Bad - manual tile assignment for organic shapesfor pos in positions:set_cell(pos, 0, Vector2i(0, 0))
3. Layer Z-Index Management
# Background layers$Background.z_index = -10# Ground layer$Ground.z_index = 0# Foreground decoration$Foreground.z_index = 10
Common Patterns
Destructible Tiles
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.
Tile Highlighting
@onready var highlight_layer: TileMapLayer = $HighlightLayerfunc highlight_tile(tile_pos: Vector2i) -> void:highlight_layer.clear()highlight_layer.set_cell(tile_pos, 0, Vector2i(0, 0))
Expert TileMap Architectures
1. Isometric TileMap (Z-Sorting / Y-Sorting)
To master isometric rendering in Godot 4.x, configure the TileSet with TILE_SHAPE_ISOMETRIC. To ensure correct depth-sorting between tiles and dynamic entities (like players), enable y_sort_enabled on all TileMapLayer nodes and their mutual Node2D parent. Use y_sort_origin on TileData to precisely tune the sorting pivot for tall objects.
class_name IsometricMapOrchestrator extends Node2D## Configures multiple TileMapLayers for isometric Y-sorting.@export var ground: TileMapLayer@export var objects: TileMapLayerfunc _ready() -> void:# Enable global Y-sorting for this container.y_sort_enabled = true# Configure individual layers.ground.y_sort_enabled = trueobjects.y_sort_enabled = true# Optional: Offset the objects layer to fake height.objects.y_sort_origin = 16
2. Procedural Generation (TileMapPattern Stamping)
For high-performance procedural generation, use TileMapPattern to store and "stamp" pre-fabricated tile structures. This is significantly faster than calling set_cell() in individual loops and preserves all tile identifiers (source_id, atlas_coords, alternative_tile) perfectly.
class_name TileStamper extends Node## Efficiently stamps pre-fabricated patterns into a TileMapLayer.@export var target_layer: TileMapLayervar _house_pattern: TileMapPatternfunc capture_pattern(coords: Array[Vector2i]) -> void:# Encapsulate a set of cells into a reusable pattern resource._house_pattern = target_layer.get_pattern(coords)func stamp_at(position: Vector2i) -> void:if _house_pattern:# Bulk-paste the pattern at the target coordinates.target_layer.set_pattern(position, _house_pattern)
3. Tilemap Diff (Layer Delta Merging)
To implement world-saving or destruction-syncing, calculate the "diff" between two TileMapLayer nodes. By iterating through get_used_cells(), you can identify discrepancies in source_id or atlas_coords and apply only the changes to a target layer, optimizing network or disk I/O.
class_name TileDiffManager extends Node## Calculates and applies the delta between two TileMapLayers.func apply_layer_diff(source: TileMapLayer, target: TileMapLayer) -> void:var source_cells := source.get_used_cells()for coord in source_cells:var s_id := source.get_cell_source_id(coord)var t_id := target.get_cell_source_id(coord)# If tiles differ, sync the target to the source.if s_id != t_id:var atlas := source.get_cell_atlas_coords(coord)var alt := source.get_cell_alternative_tile(coord)target.set_cell(coord, s_id, atlas, alt)
Reference
Related
- Master Skill: godot-master