Vav Labs
Back to blog

Godot pathfinding / 2026-06-21 / 15 min read

Updated 2026-07-29 · Verified as of Godot 4.7.1 stable checked against the 4.7 docs on 2026-07-29

AStarGrid2D in Godot 4: complete reference

Set up AStarGrid2D correctly in Godot 4.7.1 with practical code and an interactive demo: update(), solids, weights, diagonals, 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. If the confusion is a NavigationObstacle2D steering around something while the route query stays unchanged, use NavigationObstacle avoidance vs pathfinding in Godot instead.

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 stays on API behavior: setup order, update(), solids, weights, diagonal modes, heuristics, jumping, coordinate conversion, and partial paths. If you're asking when to replace AStarGrid2D with a larger navigation layer, use the limitations and alternatives guide instead.

If you have not chosen the representation yet, the 2D grid, graph, or navmesh guide puts AStarGrid2D beside AStar2D and NavigationServer2D before this page goes into API depth.

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 versionWhat matters for AStarGrid2D
4.0The 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.6 beta 3+A solid from_id returns an empty path. Godot 4.5 and the first two 4.6 betas could still route from a solid start.
4.7 / 4.7.1Godot 4.7 is the current stable docs line; 4.7.1 is the current maintenance release as of 2026-07-29.

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:

For doors, runtime costs, clean-versus-dirty updates, and point-layer replay, use the dedicated runtime AStarGrid2D update guide.

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.

APIWhat it doesNeeds update()?Gotcha
regionDefines the rectangular cell areaYesPreferred over deprecated size
cell_sizeMaps cell coordinates to world positionsYesAffects get_point_path(), not which cells are chosen
offsetShifts returned world positionsYesUseful for visual alignment
cell_shapeSquare, isometric-right, or isometric-down point placementYesPresent in 4.3+ docs
set_point_solid()Blocks or unblocks one cellNoCall after update(); guard bounds
fill_solid_region()Blocks or unblocks a rectangle of cellsNoPresent in 4.2+ docs
set_point_weight_scale()Sets terrain traversal cost multiplierNo0.0 is allowed; negative values are rejected
fill_weight_scale_region()Sets weight over a rectangleNoPresent in 4.2+ docs
diagonal_modeControls diagonal neighbors and corner cuttingNoSet before querying; pair with the right heuristic
jumping_enabledEnables Jump Point SearchNoIgnores weight scaling when enabled
get_id_path()Returns cell coordinates (Array[Vector2i])NoBest for tile logic
get_point_path()Returns world positions (PackedVector2Array)NoDocs warn this method is not thread-safe
allow_partial_pathReturns closest reachable path when possibleNoSolid targets can make the search slow
is_dirty()Reports shape changes needing update()NoTreat a dirty grid as setup failure
is_in_boundsv()Checks if a cell is inside regionNoUse before gameplay-driven point writes
get_point_data_in_region()Reads point data in a rectangleNoUseful for debugging overlays
clear()Clears grid stateN/AResets 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. Prefer region, 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 by get_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, call update() 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).

Deciding which cells should be solid is a content rule, not an AStarGrid2D API feature. If those cells come from painted tiles, use TileMapLayer custom data for walkability as the source of truth.

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. If that collision data comes from painted cells, start with build an AStarGrid2D from a TileMapLayer. 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:

For safe 1.0+ terrain authoring, complete route-cost verification, Jump Point Search, sub-1.0 heuristic safety, diagonals, and unit-profile grids, continue with weighted terrain pathfinding in Godot.

Point weights can choose the route, but action points and terrain costs belong to the game transaction that quotes, confirms, and refunds it.

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.

MovementEstimate heuristic to start with
4-direction gridHEURISTIC_MANHATTAN
8-direction grid with diagonal costHEURISTIC_OCTILE
8-direction grid where diagonal and straight are treated similarlyHEURISTIC_CHEBYSHEV
Unsure / conservative defaultHEURISTIC_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:

Both return a path for one point id per step. If that point represents the top-left anchor of a 2x2 or rectangular unit, the movement layer must apply the same footprint convention while executing every returned cell; the multi-tile movement guide covers anchor placement, visual centers, transition checks, and rotation.

  • get_id_path(from, to) returns Array[Vector2i] / Vector2i[] of cell coordinates. Use this for tile logic, movement ranges, turn-based steps, and debugging.
  • get_point_path(from, to) returns a PackedVector2Array of world positions. Use this to drive a sprite, draw a line, or feed a simple follow loop.

Run one query at a time per grid instance

The Godot 4.7 class reference explicitly marks get_point_path() as not thread-safe and recommends a Mutex when multiple threads can reach the same instance.

Do not assume get_id_path() is safe for concurrent calls just because its method entry lacks the same note. In the 4.7 source, both path methods call the same stateful _solve() pass, which updates shared search bookkeeping on the grid. The cautious production rule is one active path query per AStarGrid2D instance.

If path work must run in parallel, serialize access to a shared instance or prepare a separate grid with the same region, solids, weights, and movement policy for each worker. Do not change grid structure or point data while another thread is querying that instance.

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; this became the current behavior in Godot 4.6 beta 3, so older tutorials may show a path from a solid start. If allow_partial_path is true while the target cell is solid, the search may also take unusually long.

The navigation debug panel includes a small AStarGrid2D helper that verifies this last-cell behavior before mixing it with NavigationServer2D diagnostics.

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)

AStarGrid2D vs AStar2D vs NavigationServer2D

Pick the tool by world shape, not by habit.

You have...UseWhy
Tile grid, whole-cell movementAStarGrid2DThe grid is the graph. Solid and weighted cells are built in.
Irregular graph: waypoints, roads, hand-placed nodesAStar2DYou define points and connections yourself.
Continuous movement, baked geometry, avoidanceNavigationServer2D / 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.

Reference boundary: API behavior, not replacement strategy

Everything above is the tool working as designed. This page is the API reference you keep nearby while implementing AStarGrid2D: what to call, in what order, and which settings change the path shape.

Performance and replacement questions live in separate proof pages. If several enemies share one player target in a maze, use the maze-chase starter to see when one shared Dijkstra field replaces per-pursuer paths. If many agents request full paths in the same frame, the issue is usually query shape; see moving 10,000 agents in Godot. If one grid must answer queries for several square agent sizes, use Annotated A* with clearance-aware neighbor expansion. Rectangular footprints and circle radii need the separate shape-aware grid-clearance contract. For dynamic blockers, scheduling, or diagnostics, use AStarGrid2D limitations and alternatives in Godot.

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?

A dirty update() rebuilds the grid and clears point data, including solidity and weight scale. Call it after shape changes, then apply solids and weights again from your own data. The runtime AStarGrid2D update guide covers the clean-update no-op and the complete replay workflow.

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 API does not define a universal agent limit. Repeated per-agent path queries in the same frame are the common frame killer; use the measured crowd benchmark for that performance question.