Godot pathfinding / 2026-06-21 / 15 min read
Verified as of Godot 4.7 stable docs on 2026-06-21
AStarGrid2D in Godot 4: complete reference
Complete AStarGrid2D reference for Godot 4: region, cell size, update(), solid cells, weight scale, diagonals, heuristics, jumping, and partial paths.
AStarGrid2D is simple. The edge cases aren't.
AStarGrid2D is Godot's grid-based A* pathfinder for 2D games. You give it a rectangular cell region, call update(), mark blocked cells as solid, assign terrain costs with weight_scale, and ask for a path in grid coordinates or world positions. It's the right default for tile-based movement. It isn't a replacement for NavigationServer2D, crowd planning, clearance maps, or a production navigation workflow.
AStarGrid2D is a small class. You can read the whole API page in ten minutes, and most people do. Then they ship a grid where the path comes back empty, units cut through wall corners, or the route ignores the road and walks through the swamp. None of that is a Godot conspiracy. It's the gap between knowing the method names and knowing which small handful of settings decide the behavior.
So this is the page I'd keep open in a second tab. It walks through every property and method that changes behavior, with the gotcha attached to each one, and it ends with the question people actually came for: do I even want AStarGrid2D, or did I want NavigationServer2D?
If something here is wrong or out of date for your Godot version, I want to know. The API has moved across Godot 4.x, and stale navigation advice turns quickly into a debugging session.
Version notes for Godot 4.x
Godot's AStarGrid2D API has stayed recognizable, but a few details changed enough that older tutorials can mislead you.
If you're publishing code meant for current Godot, use region, not size. If you're reading an older tutorial that calls update() after marking obstacles, read the setup section twice.
| Godot version | What matters for AStarGrid2D |
|---|---|
| 4.0 | The docs use size = Vector2i(...) in the basic setup. |
| 4.1+ | region: Rect2i is the preferred grid shape property; size is deprecated. |
| 4.2+ | fill_solid_region() and fill_weight_scale_region() are available in the docs. |
| 4.3+ | cell_shape adds square, isometric-right, and isometric-down cell placement. get_id_path() and get_point_path() support allow_partial_path. |
| 4.7 stable | Current stable docs as of 2026-06-21. Use these for final publish verification. |
The setup that runs - and the order that matters
Here is the smallest setup that works. The order matters:
var astar := AStarGrid2D.new()
astar.region = Rect2i(0, 0, 64, 64) # 64x64 cells, origin at (0, 0)
astar.cell_size = Vector2(16, 16) # world units per cell
astar.update() # builds the grid
var cells: Array[Vector2i] = astar.get_id_path(Vector2i(0, 0), Vector2i(10, 8))
var points: PackedVector2Array = astar.get_point_path(Vector2i(0, 0), Vector2i(10, 8)) Apply solids and weights after update()
update() is the line everyone forgets. AStarGrid2D doesn't rebuild itself after shape changes until you ask it to. Set the region, set the cell size, call update(), then query. Forget it and you'll usually get an empty array with no useful gameplay explanation.
But the more expensive mistake is the opposite one: calling update() after you mark obstacles. update() rebuilds the grid and clears point data, including solidity and weight scales. The safe setup pattern is:
astar.region = Rect2i(0, 0, 64, 64)
astar.cell_size = Vector2(16, 16)
astar.update()
# Apply point data after update().
for cell in wall_cells:
astar.set_point_solid(cell, true)
for cell in mud_cells:
astar.set_point_weight_scale(cell, 4.0) The rule worth memorizing
Grid shape changes need update(); point data is applied after update(). region, cell_size, offset, and cell_shape are shape or placement changes. Solids and weights are point data. If you change the shape later, replay the solids and weights from your own source of truth.
AStarGrid2D API cheat sheet
Use this as the quick reference before the longer explanations below.
| API | What it does | Needs update()? | Gotcha |
|---|---|---|---|
region | Defines the rectangular cell area | Yes | Preferred over deprecated size |
cell_size | Maps cell coordinates to world positions | Yes | Affects get_point_path(), not which cells are chosen |
offset | Shifts returned world positions | Yes | Useful for visual alignment |
cell_shape | Square, isometric-right, or isometric-down point placement | Yes | Present in 4.3+ docs |
set_point_solid() | Blocks or unblocks one cell | No | Call after update(); guard bounds |
fill_solid_region() | Blocks or unblocks a rectangle of cells | No | Present in 4.2+ docs |
set_point_weight_scale() | Sets terrain traversal cost multiplier | No | 0.0 is allowed; negative values are rejected |
fill_weight_scale_region() | Sets weight over a rectangle | No | Present in 4.2+ docs |
diagonal_mode | Controls diagonal neighbors and corner cutting | No | Set before querying; pair with the right heuristic |
jumping_enabled | Enables Jump Point Search | No | Ignores weight scaling when enabled |
get_id_path() | Returns cell coordinates (Array[Vector2i]) | No | Best for tile logic |
get_point_path() | Returns world positions (PackedVector2Array) | No | Docs warn this method is not thread-safe |
allow_partial_path | Returns closest reachable path when possible | No | Solid targets can make the search slow |
is_dirty() | Reports shape changes needing update() | No | Treat a dirty grid as setup failure |
is_in_boundsv() | Checks if a cell is inside region | No | Use before gameplay-driven point writes |
get_point_data_in_region() | Reads point data in a rectangle | No | Useful for debugging overlays |
clear() | Clears grid state | N/A | Resets the grid shape and data |
What AStarGrid2D actually is
AStarGrid2D is the grid-shaped specialization of Godot's A* search. Plain AStar2D makes you add every point and every connection by hand. That's useful when your graph is irregular: waypoints, a road network, a hand-built node mesh. AStarGrid2D skips all of that. You hand it a rectangular region and a cell size, and the grid is the graph: every cell is a point, neighbors are connected for you, and you spend your time marking which cells are solid and which are expensive.
That's the trade. You give up arbitrary graph shapes and get a dense uniform grid with almost no setup. For tile-based games - strategy, roguelikes, tactics, tower defense, most top-down movement - that's usually the shape you want.
What it isn't: it isn't NavigationServer2D. The navigation server works with baked navigation mesh (navmesh) regions and polygons, agent avoidance, and continuous space. AStarGrid2D works in whole cells. Different tool, different problem. See the Godot pathfinding glossary for the short version of the terms.
Region, cell size, offset, and cell shape
Four properties place the grid in your world:
region: Rect2i- the rectangle of cells, in cell coordinates.Rect2i(0, 0, 64, 64)is a 64x64 grid starting at origin. This is the current property to use.size: Vector2i- old shape property from early Godot 4 docs. It still exists, but it's deprecated. Preferregion, especially when your grid doesn't begin at(0, 0).cell_size: Vector2- how big one cell is in world units. This affects the positions returned byget_point_path().offset: Vector2- a world-space shift added to returned point positions. Use it when your visual grid and logical grid need a consistent offset.cell_shape- square, isometric-right, or isometric-down point placement. It affects how positions are placed in the grid; when changed, callupdate()before querying again.
Inspect alignment instead of guessing
get_point_position(id) gives you the world position of a single cell if you need to inspect alignment by hand. If your path starts one tile off, don't guess. Print the cell, print the region, and print the point position.
Solid cells and disabled cells: your obstacles
This is how walls happen:
astar.set_point_solid(Vector2i(5, 5), true) # one obstacle cell
var blocked := astar.is_point_solid(Vector2i(5, 5))
astar.fill_solid_region(Rect2i(8, 0, 1, 30), true) # a 1x30 wall Solidity doesn't need another update()
A solid cell is disabled for pathfinding. Nothing routes through it.
Three things matter here. First, solidity changes don't need update(). They apply on the next query. Second, update() clears solidity, so apply solids after the grid has been updated. Third, single-cell reads and writes outside the region fail, so guard gameplay-driven IDs with is_in_boundsv(id).
fill_solid_region() is the rectangle version. Use it when you're stamping a wall, clearing a room, or applying tilemap collision data in blocks. It's present in the Godot 4.2 docs and current stable docs.
For the part where changing blockers too often starts costing frames, see dynamic blockers without a full rebuild.
Weight scale: making terrain cost something
Solid is binary. A cell is in or out. Weight is the dial in between.
set_point_weight_scale() multiplies the cost of traveling from a neighboring point into this cell:
astar.set_point_weight_scale(Vector2i(5, 6), 4.0) # swamp: 4x as costly to enter The weight floor is 0.0, not 1.0
Default is 1.0. Higher values are more expensive. Values between 0.0 and 1.0 make a cell cheaper than normal. The source guard rejects values below 0.0, so 0.0 itself is allowed. Treat that as a free-to-enter cell: occasionally useful, usually a sign that the cost model needs a second look. In most game code, keep it boring: 1.0 for normal terrain, > 1.0 for mud, water, danger, or rough ground.
Like solidity, weight changes apply after update() and don't need another update call.
One caveat: weights and heuristics talk to each other. For A* to remain optimal, the estimate must not overestimate the real remaining cost. If your weighted routes look strange, check the heuristic pairing before blaming the weight values.
Diagonal modes: corner cutting and how to stop it
diagonal_mode decides whether units can move diagonally and whether they can slip diagonally past obstacles. It has four values:
DIAGONAL_MODE_ALWAYS- diagonals are allowed even when adjacent solid cells would make the move look like corner cutting.DIAGONAL_MODE_NEVER- orthogonal movement only. Four directions.DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE- a diagonal is allowed if at least one of the two side-adjacent cells is walkable.DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES- a diagonal is allowed only when both side-adjacent cells are walkable. This is the usual choice when walls should feel solid.
Diagonal mode doesn't dirty the grid
Changing diagonal_mode doesn't require update() in current source; set it before querying and pair it with a matching heuristic. If you want no diagonals, use DIAGONAL_MODE_NEVER and usually HEURISTIC_MANHATTAN.
Heuristics: compute vs estimate
AStarGrid2D exposes two heuristic settings:
default_compute_heuristic- used for the cost between connected cells if_compute_cost()is not overridden.default_estimate_heuristic- used for the estimate from a point to the goal if_estimate_cost()is not overridden.
Heuristic pairings to start with
Both heuristic settings default to HEURISTIC_EUCLIDEAN. The options are EUCLIDEAN, MANHATTAN, OCTILE, and CHEBYSHEV.
| Movement | Estimate heuristic to start with |
|---|---|
| 4-direction grid | HEURISTIC_MANHATTAN |
| 8-direction grid with diagonal cost | HEURISTIC_OCTILE |
| 8-direction grid where diagonal and straight are treated similarly | HEURISTIC_CHEBYSHEV |
| Unsure / conservative default | HEURISTIC_EUCLIDEAN |
Don't overestimate the real remaining cost
On a square grid with diagonal movement, Manhattan overestimates because it counts diagonal progress as if it needed two orthogonal moves. That can make the search faster-looking and less correct. If your path is almost right but not quite, heuristic plus diagonal mode is the first pair to inspect.
Jumping (JPS): fast path, sharp caveat
jumping_enabled = true switches on Jump Point Search. On a large, open, uniform-cost grid it can reduce how many intermediate points the search expands. It can also change the shape of returned paths because the algorithm jumps over long runs of open cells.
The current docs include the important warning: when jumping is enabled, weight scaling isn't considered in pathfinding. So this isn't a free speed button for weighted terrain.
Use it for open, uniform-cost grids. Turn it off when terrain weights are part of the design.
get_id_path vs get_point_path, and allow_partial_path
Two methods ask for a route:
get_id_path(from, to)returnsArray[Vector2i]/Vector2i[]of cell coordinates. Use this for tile logic, movement ranges, turn-based steps, and debugging.get_point_path(from, to)returns aPackedVector2Arrayof world positions. Use this to drive a sprite, draw a line, or feed a simple follow loop.
Partial paths aren't a validation substitute
In current Godot 4.x docs, both path methods can take allow_partial_path. With it true, an unreachable goal can return a path to the closest reachable cell instead of an empty array.
Two catches: if the start cell is solid, current docs say the result is empty even when from_id == to_id; and if allow_partial_path is true while the target cell is solid, the search may take unusually long.
So don't use partial path as a substitute for validation. Validate bounds and solidity first. Then query.
func safe_id_path(astar: AStarGrid2D, start: Vector2i, goal: Vector2i) -> Array[Vector2i]:
if astar.is_dirty():
push_warning("AStarGrid2D parameters changed; call update() and replay point data.")
return []
if not astar.is_in_boundsv(start) or not astar.is_in_boundsv(goal):
return []
if astar.is_point_solid(start):
return []
if astar.is_point_solid(goal):
# Pick your own policy here: reject, choose a neighbor, or allow a partial path knowingly.
return []
return astar.get_id_path(start, goal) TileMapLayer alignment: the quiet source of wrong paths
AStarGrid2D uses grid IDs. Gameplay code often starts from world positions. Tile maps add another space in between.
For TileMapLayer, the safe conversion is usually:
# World position -> map cell.
var cell: Vector2i = tile_layer.local_to_map(tile_layer.to_local(global_position))
# Map cell -> world position.
var local_center: Vector2 = tile_layer.map_to_local(cell)
var world_center: Vector2 = tile_layer.to_global(local_center) Print converted cells before inspecting the solver
If your TileMapLayer is at (0, 0), skipping to_local() may appear to work. Then someone moves the layer under a parent and every path starts lying. Print the converted cells before you inspect the solver.
AStarGrid2D vs AStar2D vs NavigationServer2D
Pick the tool by world shape, not by habit.
| You have... | Use | Why |
|---|---|---|
| Tile grid, whole-cell movement | AStarGrid2D | The grid is the graph. Solid and weighted cells are built in. |
| Irregular graph: waypoints, roads, hand-placed nodes | AStar2D | You define points and connections yourself. |
| Continuous movement, baked geometry, avoidance | NavigationServer2D / navigation mesh (navmesh) | Polygon regions and server-backed navigation, not cell rules. |
Grid or navigation mesh?
If you're weighing grid against a navigation mesh (navmesh) specifically, read grid or navmesh in Godot. Short version: grids win when movement is tile-shaped and you want cell-level control; navmesh wins when movement is continuous and the geometry isn't naturally a tile board.
Where AStarGrid2D stops being enough
Everything above is the tool working as designed. The next question is usually: how many agents can it move? The honest answer is that the solver is rarely the whole problem. The query pattern is.
If 500 agents ask for full paths in the same frame, the frame goes bad. In our measured Godot benchmark, the naive per-agent query pattern produced a 670 ms median frame and a 5.7-second worst sampled frame. A scheduled shared-field approach moved far more agents while keeping sampled frames under budget. That's not an argument that AStarGrid2D is bad. It's an argument that repeated per-agent path solves are the wrong shape for crowds with shared goals.
See moving 10,000 agents in Godot for the measured version.
The other edges - multi-size agents, dynamic blockers at scale, diagnostics for why a route failed, and repeated query scheduling - are where a grid project turns into a navigation system. That boundary is covered in where AStarGrid2D stops being enough.
Use the built-in while it keeps the project legible. When the scaffolding around it outgrows it, admit what you're building.
Frequently asked questions
What is AStarGrid2D in Godot?
AStarGrid2D is Godot's grid-based A* pathfinder. You give it a rectangular region and a cell size, call update(), then mark cells as solid or weighted. It's built for tile-based 2D movement.
AStarGrid2D vs AStar2D - what is the difference?
AStar2D is a general graph where you add points and connections yourself. AStarGrid2D assumes a rectangular grid and wires the neighboring cells for you. Use AStar2D for irregular waypoint graphs; use AStarGrid2D for tile maps.
AStarGrid2D vs NavigationServer2D - which should I use?
Use AStarGrid2D for whole-cell, tile-based movement where you want per-cell control. Use NavigationServer2D for continuous movement over baked navigation mesh regions, especially when your world isn't naturally a grid.
Does AStarGrid2D.update() clear solid cells and weight scales?
Yes. update() rebuilds the grid and clears point data, including solidity and weight scale. Call update() after shape changes, then apply solids and weights again from your own data.
Why does my AStarGrid2D path come back empty?
The usual causes are a dirty grid that needs update(), start or goal outside region, a solid start cell, a solid or unreachable goal, wrong world-to-cell conversion, or disconnected walkable areas.
What does get_id_path return?
get_id_path() returns cell coordinates: Array[Vector2i] / Vector2i[]. Use it when gameplay logic cares about tiles.
What does get_point_path return?
get_point_path() returns world positions as a PackedVector2Array, with cell_size, offset, and cell_shape applied. Use it for drawing or simple movement following.
How do I make some terrain cost more?
Use set_point_weight_scale(cell, value). 1.0 is normal, values above 1.0 are more expensive, 0.0 is allowed as a free-to-enter cell, and negative values are rejected. Weight changes don't need update(), but they're cleared by a later update().
Does jumping_enabled ignore weight_scale?
Yes. Current docs state that enabling jumping disables consideration of weight scaling. Use jumping for open uniform-cost grids; turn it off for weighted terrain.
Does AStarGrid2D support isometric or hex grids?
Current Godot has cell_shape for square, CELL_SHAPE_ISOMETRIC_RIGHT, and CELL_SHAPE_ISOMETRIC_DOWN placement. Hex grids aren't a native AStarGrid2D mode; you need your own coordinate mapping/neighbor logic or a different graph approach.
How many agents can AStarGrid2D handle?
The class isn't usually the limit by itself. Repeated per-agent path queries in the same frame are the common frame killer. See the measured crowd benchmark in moving 10,000 agents in Godot.