Godot pathfinding / 2026-07-13 / 13 min read
Verified as of Godot 4.7 stable
Runtime AStarGrid2D Updates Without Full Rebuilds
Update doors, blockers, and terrain costs in AStarGrid2D without rebuilding the grid. Learn when update() is required and how to replay point data.

The direct answer
Do you need to call AStarGrid2D.update() after set_point_solid() in Godot? No. Use set_point_solid() for a door, tower, wall, or other cell-level blocker, and the next path query will read the new state. set_point_weight_scale() and both rectangular fill methods also take effect without update().
Call update() after changing the grid's structure: region, cell_size, offset, or cell_shape. That structural rebuild resets point solidity and weight scales, so keep those layers in your own game state and replay them before the next path query.
In Godot 4.7, an update() call on an already clean grid returns without rebuilding. That clean no-op doesn't erase point edits. The reset happens when a structural property has first made the grid dirty.
Match the symptom to the rule
These failures share one root mistake: structural state, query policy, point data, and cached agent routes have different update contracts.
| If this is happening | Use this rule |
|---|---|
| A door closes, but the route doesn't change | Change its occupied cells with set_point_solid(), then request a new path. Don't call update(). |
| Walls or costs disappear after update() | A dirty structural rebuild cleared native point data. Replay authored solidity, weights, and active blockers. |
| get_id_path() becomes empty after changing region | The grid is dirty. Call update(), replay point layers, then query. |
| Removing one blocker reopens another | Track blocker owners or per-cell reference counts instead of writing one boolean per object. |
| The grid changed, but agents keep following an old route | The point edit is complete; cached-path invalidation is a separate game-system job. |
Choose the smallest runtime update
Older examples may set size, but that property is deprecated in Godot 4.7. Set the complete region, including a negative origin when your TileMapLayer uses one.
| What changed | Native operation | Call update()? | What happens next |
|---|---|---|---|
| One blocker cell opens or closes | set_point_solid() | No | The next query uses the new solid flag. |
| A rectangular wall or room changes | fill_solid_region() | No | Every in-region cell in the clipped rectangle changes immediately. |
| A terrain cost changes | set_point_weight_scale() or fill_weight_scale_region() | No | The next weighted query reads the new scale. |
| Diagonal, jumping, or default heuristic policy changes | Set the matching property | No | The next query uses the new policy. |
| Region, cell size, offset, or cell shape changes | Set the property, then update() | Yes | The native grid is rebuilt; replay solidity and weights before querying. |
Initialize structure before point data
The safe setup order is structural properties, one update(), then authored solidity and weights. Every core snippet below is a fragment of the same runtime_astargrid2d_state.gd; the complete assembled file is in the verified download.
The diagonal and jumping properties don't mark the grid dirty in 4.7; they're query policy. The structural properties do. Point setters reject writes while the grid is dirty, so writing walls before the first update is the wrong order.
If the authored board comes from a TileMapLayer, keep coordinates and initial region construction in the TileMapLayer-to-AStarGrid2D guide. Stable walkability and terrain fields can come from TileMapLayer custom data. The runtime helper consumes normalized cells; it doesn't need to know how the designer painted them.
Edit door and blocker cells directly
A door changes point occupancy rather than the grid region or the position of every point. Prevalidate a multi-cell write so one invalid id can't leave half a door applied.
There is no update() after these writes. A new get_id_path() call will avoid the closed cells or use them again after the door opens.
fill_solid_region() is useful for an axis-aligned rectangle. Godot clips that rectangle to the grid region. The clipping is convenient for painting tools but can hide an invalid gameplay request. If a door footprint must fit completely inside the board, validate the rectangle first.
What update() does in Godot 4.7
When grid.is_dirty() is false, Godot 4.7 returns immediately from update(). It doesn't rebuild the region or clear point data. That early return was added for Godot 4.3, which is why older examples report different behavior.
When a structural setter makes the grid dirty, update() clears stored points and the solid mask, reconstructs positions across the complete region, and marks the grid clean. Solidity and weight scales return to their defaults.
An unconditional update() after set_point_solid() is therefore a redundant no-op on a clean 4.7 grid. It becomes destructive when the same code path has also changed region, offset, cell_size, or cell_shape. The call now does a real rebuild, and point layers disappear unless you replay them.
Keep point data outside the grid
The grid is an index, not the source of truth. Keep the data required to reconstruct it in ordinary game state.
Once a structural property marks the grid dirty, get_point_data_in_region() rejects the read. Resizing first and then asking the dirty grid what it used to contain is too late. Preserve the layers before the change, or own them outside AStarGrid2D from the beginning.
If the new region is smaller, decide whether out-of-bounds blocker owners are discarded or retained for a later expansion. Make that lifecycle explicit rather than leaving it to dictionary leftovers.
- Authored solid cells from the level or TileMapLayer.
- Authored weight scales.
- Active runtime blocker ids and their cells.
- Per-cell blocker counts when owners overlap.
Reconfigure once, then replay
Structural changes are valid at runtime, but they use a different path from a door toggle.
Don't query between the structural writes and update(). get_id_path() and get_point_path() reject a dirty grid and return an empty path. Point setters reject that state as well.
This is a full native rebuild because the meaning or position of every stored point may have changed. Keep ordinary doors, blockers, and cost changes on the point-data path so structural rebuilds remain rare.
Track overlapping blocker owners
A single boolean is insufficient when two runtime objects cover one cell. A closed gate and a temporary force field may share a tile; opening the gate must not remove the force field from pathfinding.
Use a count per cell and restore authored solidity when the last runtime owner leaves. The authored check prevents that last owner from reopening a wall that was already solid in the level.
Replace a moving blocker atomically
A moving object uses the same ownership data. Validate its complete next footprint before releasing any previous cell, then release only cells it left and claim only cells it entered.
The verifier includes overlap-release and invalid-batch fixtures. Scheduling hundreds of moving blockers belongs to a later high-frequency policy layer, not this correctness baseline.
Point edits and cached paths are separate jobs
set_point_solid() updates the native grid. It doesn't notify agents or rewrite path arrays they already hold.
If a newly blocked cell appears in a cached route, that route is illegal and should be invalidated. Opening a cell or lowering a cost may leave the old route legal but no longer cheapest. The game decides whether to keep it until its normal repath point or refresh it immediately.
The dynamic-blocker guide owns path-cell bookkeeping, queued repaths, and cell/chunk/region invalidation. Keep those policies outside the low-level writer so one door signal doesn't solve every affected route in the same frame.
Terrain costs use the point-data path too
Runtime mud, fire, or a temporary road can change weight_scale without a rebuild.
This establishes only the update boundary. The weighted terrain pathfinding guide owns safe weight authoring, returned-route cost reconstruction, sub-1.0 heuristic safety, and the documented fact that jumping ignores weight scaling.
Lowering a cost doesn't necessarily invalidate an existing route. It can make another route cheaper. That's a refresh-policy choice, not a reason to rebuild the grid.
Common failures
Calling point setters before the first update. The grid is dirty and rejects the write.
Using update() as a generic refresh. Point-local changes don't need it; a later structural edit turns the same call into a real rebuild.
Assuming a dirty update preserves obstacles. It resets solidity and weights, so replay them.
Changing region and querying immediately. A dirty grid rejects the path query and returns an empty array. Use the broader empty-path guide when the failing layer isn't yet known.
Trying to snapshot after the grid is dirty. get_point_data_in_region() rejects that state too.
Unblocking by boolean when owners overlap. One object can reopen another object's cell.
Releasing a runtime blocker without checking authored solidity. A level wall becomes walkable.
Editing the grid while agents keep cached routes forever. The native state is correct, but the agents still follow stale data.
Assuming a rectangular fill validates the whole request. The native fill clips to the grid region.
What the verification covers
The Godot 4.7 source package contains the assembled runtime state helper, a runnable door/blocker scene, and the headless verifier. The machine-readable receipt records 14/14 published checks, plus a green scene smoke.
The named checks cover point-local rerouting, immediate weight edits, clean-update preservation, dirty-update resets, authored-layer replay, overlapping owners, moving-blocker replacement, invalid-batch rejection, and the extracted-package rerun.
The ZIP is 9,285 bytes with SHA-256 b76e3d1298c8035cf9eee295b31528a5cbb84f73bab6333f9a80339a56f30af3. Measurement-gap: this is a deterministic correctness result, not a runtime, throughput, memory, asymptotic, every-project, or released-product benchmark.

Frequently asked questions
Do I need to call AStarGrid2D.update() after set_point_solid()?
No. In Godot 4.7, set_point_solid() changes point data immediately and the next path query reads it. Call update() only after structural changes such as region, cell_size, offset, or cell_shape.
Why did AStarGrid2D lose my solid cells after update()?
A dirty update() reconstructs the region and resets solidity and weight scales. Keep authored walls, costs, and active blockers in your own source of truth, then replay them. A clean update() is a no-op in Godot 4.3 through 4.7.
How do I open or close a door in AStarGrid2D at runtime?
Call set_point_solid(cell, true) to close its occupied cells and set_point_solid(cell, false) to open them. Don't call update(). Then refresh or invalidate any cached route affected by the change.
Why is get_id_path() empty after I changed the grid region?
Changing region makes the grid dirty. get_id_path() rejects a dirty grid, so call update() before querying and replay the solidity and weights cleared by the structural rebuild.
Does fill_solid_region() need update()?
No. fill_solid_region() takes effect without update() and clips its input rectangle to the grid region. Validate first if your game requires the entire rectangle to remain in bounds.
Does changing diagonal_mode require update()?
No in Godot 4.7. diagonal_mode changes query policy without making the grid dirty. cell_shape is different: it changes stored point placement and requires update().
Can I resize AStarGrid2D at runtime?
Yes. Set the new region, call update(), then replay authored solidity, weights, and active blockers before the next path query. Decide explicitly what happens to blocker owners outside a smaller region.