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:

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.