Vav Labs
Back to blog

Godot pathfinding / 2026-06-06 / 9 min read

Updated 2026-06-12

Grid or navmesh in Godot? Choosing pathfinding that survives production

The short answer first, then the full decision: movement model, runtime changes, scale, agent size, path stability. Current for Godot 4.6, FAQ included.

A split diagram: on the left a tile grid with integer cells and a blocked cell, on the right an organic navigation mesh of polygons with a free-form path, and a decision axis between them.

The choice most projects make by accident

Most Godot projects pick grid or navmesh the same way: a tutorial used one, so the project uses it too, and the decision is defended for a year. That is a fine way to ship a prototype and an expensive way to discover, six months in, that the navigation layer is fighting the game instead of serving it.

There is no universal winner here. Grid pathfinding (AStarGrid2D) and navmesh pathfinding (the NavigationServer2D with a baked navigation mesh) are good at different things, and the right choice is set by your game's constraints, not by taste. The useful part is that those constraints are knowable in advance.

The short version: grid is the safer default when game logic is discrete, dynamic, explainable, and tile-bound. Navmesh is stronger when motion is free-form, mostly static, and spatially continuous. The rest of this article is how to tell which one describes your game — current as of Godot 4.6.

The short answer

If you want one rule before the detail: tile-locked, frequently-changing, integer-logic worlds lean grid; free-form, mostly-static, smooth-movement worlds lean navmesh. Almost everything below is a refinement of that sentence.

And if you're here because a path came back empty, that's its own diagnosis — why is my path failing in Godot walks the empty-path checklist for both models.

What AStarGrid2D actually is

AStarGrid2D is Godot's grid specialization of A*. You give it a rectangular region and a cell size, and it builds the graph for you — no manually creating points and wiring up neighbors like the general AStar2D. It thinks in integer cells (Vector2i), which is exactly what a TileMap-based game already thinks in.

Two properties define it and one method drives it. Set region and cell_size, call update() once, then query with get_id_path(). Marking a cell blocked is set_point_solid(); raising a cell's cost is set_point_weight_scale(). Neither of those needs another update() — only changing region, cell_size, or offset does, and update() clears solidity and weight data, so do not call it as a refresh.

var grid := AStarGrid2D.new()
grid.region = Rect2i(0, 0, 64, 64)
grid.cell_size = Vector2(16, 16)
grid.update()  # only needed after region / cell_size / offset changes

grid.set_point_solid(Vector2i(10, 8), true)        # no update() needed
grid.set_point_weight_scale(Vector2i(12, 8), 2.0)  # no update() needed

var path := grid.get_id_path(Vector2i(0, 0), Vector2i(40, 30))

What NavigationServer and navmesh actually are

The navmesh approach is a different model. You describe the walkable area as a navigation mesh — a set of polygons — baked into a NavigationRegion2D, which uploads it to the NavigationServer. Agents then move to any point inside that area, not only to cell centers.

You do not need a NavigationAgent2D to use it. The server answers path queries directly, with the funnel algorithm available for tighter corner paths:

NavigationAgent2D is an optional helper layered on top: it follows the path, handles local avoidance, and exposes get_next_path_position() for your movement code. You still move the node yourself — the navigation system never moves it for you.

# Direct path query — no NavigationAgent2D required.
var map: RID = get_world_2d().get_navigation_map()
var path: PackedVector2Array = NavigationServer2D.map_get_path(map, start, target, true)
# optimize = true runs the funnel algorithm for tighter corner paths.

AStarGrid2D or NavigationServer? The decision

Godot's own documentation draws the line cleanly. AStar is "best suited for cell-based 2D gameplay that does not require actors to reach any possible position within an area but only predefined, distinct positions." NavigationServer is "best suited for 2D realtime gameplay that does require actors to reach any possible position within a navigation mesh defined area." If your actors live on tiles, that is grid. If they walk freely, that is navmesh.

Where grid wins: tile worlds with integer logic — tower defense, tactics, roguelikes, city-builders. The cell model makes runtime blockers cheap (see dynamic blockers without a full rebuild), multi-size agents tractable with clearance maps, crowds solvable with flow fields (see shared-goal flow fields), and tactics expressible as influence layers over the same grid.

Where navmesh wins: free-form 2D or 3D movement, organic terrain, smooth paths, built-in local avoidance, large mostly-static areas, and off-mesh connections like jumps or teleports via NavigationLink2D.

Keeping pathfinding correct when the world changes

If walkability changes often — doors, placed towers, destroyed walls — grid is usually cheaper, but be precise about what "cheaper" means. The walkability edit itself is O(1)-style: set_point_solid() flips one cell and needs no update(). Keeping pathfinding correct is not O(1) — that still costs a path query or replan, and depends on your invalidation strategy: which cached paths and which agents actually need recomputing.

The navmesh equivalent of a runtime change is rebaking a region or using obstacle-assisted baking. The expensive part is usually parsing and synchronizing the source geometry, not the bake arithmetic alone, and it scales with the region rather than with the size of the change. It is the same spike story as repathing a whole grid at once: on either side, the fix is to bound the work to what changed — see why your Godot pathfinding causes frame spikes and dynamic blockers without a full rebuild.

Incremental replanning is well studied. Algorithms like D* Lite and Lifelong Planning A* reuse the previous search when only part of the graph changed instead of planning from scratch. The grid model makes that kind of local invalidation straightforward; the navmesh model makes you think in terms of regions and obstacles.

Why dynamic tile games lean grid, while large static spaces lean navmesh

Here is the counter-intuitive part, straight from the docs: for large static areas, navmesh scales better, not worse. "A large area can often be defined with a single polygon when it would require many, many grid cells." So the lean toward grid is not about raw size — it is about dynamism and discreteness. Tile-bound, frequently-changing, explainable worlds fit grid; continuous, mostly-static motion fits navmesh.

In practice, very large navmesh worlds reward careful partitioning. The Godot community is actively discussing chunked, streamed navmesh with multiple agent types, precisely because the naive "one giant navmesh" setup struggles with bake time and memory. Treat that as direction-of-travel, not a verdict: large static navmesh works today, it just rewards planning.

Path quality is a real tradeoff too. On complex meshes the shortest graph path is not always the most natural route; funnel and string-pulling smooth corridor paths, and Dijkstra-style queries for higher-quality paths are under discussion. This is a long-standing result in pathfinding, not a Godot quirk.

Finally, maturity. As of Godot 4.6, NavigationAgent2D and NavigationObstacle2D are marked Experimental — "this class may be changed or removed in future versions." AStarGrid2D has been stable. None of this makes navmesh a bad choice; it makes grid the lower-risk default for tile and dynamic games, and it is a good reason to pin your Godot version and re-test navigation on upgrades.

So: navmesh is not worse, and grid is not automatically more scalable. The boundary is knowable — pick the model that matches how your world moves and how often it changes.

The hybrid reality

The two are not enemies, and plenty of shipped games use both. A common split is navmesh for motion and a grid for tactics and rules: units move smoothly on a navmesh while the game validates a tower placement on a grid so a player can never seal the only route, or biases movement with an influence layer.

Treat them as layers, not a religion. The question is rarely "which one is correct" and usually "which layer owns which decision."

When the built-ins are enough

Either model is fine until the scaffolding around it outgrows the engine layer — caches, debug overlays, clearance logic, custom invalidation. That is the point where you are no longer using a pathfinding API; you are building a navigation system. If that is where your grid project is heading, where AStarGrid2D stops being enough is the next read.

Until then: choose by constraints, keep it boring, and let the model match the game.

Frequently asked questions

When should I choose AStarGrid2D and when NavigationServer in Godot?

Choose AStarGrid2D when movement is tile-locked, the world changes often at runtime, and you need to explain why a path failed. Choose NavigationServer with a baked navmesh when movement is free-form, the world is mostly static, and agents need smooth paths through irregular geometry. Mixing them is normal: many shipped games use a navmesh for motion and a grid for rules.

Should I use AStarGrid2D or NavigationServer for a tower defense or roguelike?

Grid (AStarGrid2D). Discrete tile movement, cheap runtime blockers, and explainable paths are exactly its sweet spot.

Can I use NavigationServer2D without a NavigationAgent2D?

Yes. Query NavigationServer2D.map_get_path(map, from, to, true) directly, using get_world_2d().get_navigation_map() for the map RID. NavigationAgent2D is only an optional path-following and avoidance helper.

Does AStarGrid2D need update() after set_point_solid()?

No. set_point_solid() and set_point_weight_scale() take effect immediately. update() is only needed after changing region, cell_size, or offset, and it clears solidity and weight data — so do not call it as a refresh.

Why does my navmesh path come back empty on the first frame?

The NavigationServer map has not synchronized its regions yet. Wait a physics frame; with an agent you can skip until NavigationServer2D.map_get_iteration_id(agent.get_navigation_map()) is no longer 0.

How do I handle dynamic obstacles without rebaking the whole navmesh?

Use NavigationObstacle2D for avoidance or obstacle-assisted baking where it fits. For frequent game-logic walkability changes, a grid with local invalidation is often simpler. Pick the layer that matches what actually changed — an obstacle node does not re-solve topology on its own.

Are NavigationAgent2D and NavigationObstacle2D stable in Godot 4.6?

Both are marked Experimental as of Godot 4.6 ("may be changed or removed in future versions"). They work, but pin your Godot version and re-test navigation on upgrades. AStarGrid2D has been stable.