Quick answer: After a batch of set_cell calls, call tile_layer.notify_runtime_tile_data_update() once. Physics shapes rebuild on the next frame. Avoid notifying per cell — the cost is per-call.
Player breaks a tile with set_cell(pos, -1). Visual disappears. Player still bumps into the now-invisible wall. The collision shape didn’t rebuild.
The Symptom
Runtime tile changes show visually but physics queries / character body collisions still respect the old layout. Disappearing tiles still block; appearing tiles aren’t solid.
The Fix
extends TileMapLayer
func destroy_tile(coords: Vector2i) -> void:
set_cell(coords, -1)
notify_runtime_tile_data_update()
func destroy_many(many_coords: Array) -> void:
for c in many_coords:
set_cell(c, -1)
notify_runtime_tile_data_update() # once at the end
For a single edit, the cost is fine. For a hundred edits, batch the notify after the loop.
Update Internals
Godot regenerates the layer’s physics body on idle following the notify. If you query physics in the same frame as a set_cell, your query may see the old state. Either:
- Schedule the query for the next frame (
await get_tree().process_frame). - Call
PhysicsServer2D.body_set_shape_count(body_rid, 0)+ rebuild manually.
The first is much simpler.
Verifying
Place a CharacterBody2D next to a tile. Destroy the tile via set_cell + notify. Move the character into the now-empty space; should pass through. Without notify: stops at the invisible wall.
“notify_runtime_tile_data_update after the batch. Physics catches up.”
Related Issues
For Area2D body_entered, see Area2D signals. For NavigationRegion2D bake, see NavRegion bake.
Notify once. Physics catches up.