Godot pathfinding / 2026-07-08 / 16 min read
Updated 2026-07-19 · Verified as of Godot 4.7.1 stable docs and expanded Dijkstra-map proof checked on 2026-07-19
Dijkstra maps for roguelike AI in Godot
Build Dijkstra maps in Godot for roguelike AI: pursuit fields, shared maps for many actors, source bias, flee maps, autoexplore, hazards, and INF-safe scoring.
What a Dijkstra map is
A Dijkstra map is a grid where every cell holds its cheapest distance to a goal, computed with Dijkstra's algorithm over non-negative step costs. Seed the goal cells at 0, flood the distances outward, and you have a field any number of actors can read at once - no per-actor pathfinding.
It turns up in roguelikes, grid AI, and procedural systems because it makes smart movement a lookup instead of a search. Godot 4 ships none of it natively, so this page builds it from scratch. The everyday shapes:
- Chase: an actor steps toward the lowest neighboring value.
- Avoid: it steps the other way - though the naive highest value rule hugs corners, which this page fixes later.
- Fire, smell, or sound spread: distance values can be transformed into intensity.
- Safe-spot AI: pick a high value on a danger map.
The direct answer
A Dijkstra map is a distance field for grid AI. Build one field from the player, a goal, gold, exits, hazards, or any set of sources; then each actor looks at neighboring cells and steps toward the lowest value.
That flips the usual pathfinding question. A* answers what path should this one actor take to this one target? A Dijkstra map answers what is the best direction from every reachable cell? For roguelike AI, that means one solve can guide pursuit, flee behavior, autoexplore, hazard escape, and source-biased desire maps.
A useful Godot rule is this: store the field as a dense PackedFloat32Array, mark unreachable cells as INF, seed one or more cells with starting scores, then rescan the finite cells through Dijkstra relaxation.
- Build the walkable grid from your TileMapLayer or board data.
- Seed the field: player = 0, exits = 0, gold = negative bias, hazards = safe cells, or any mix that fits the behavior.
- Relax the field through walkable neighbors with terrain costs.
- For each actor, inspect four neighboring cells and step to the lowest score.
- Recompute only when sources, terrain, blockers, or revealed cells change.
Why roguelikes use maps, not only paths
Roguelike AI is usually grid-native. Actors move one cell at a time, turns advance in discrete steps, and the same map facts are read by many systems: pursuit, fleeing, exploration, sound, hazards, tactical safety, and item desire.
If ten actors all chase the same player, ten A* queries are wasteful. A single Dijkstra map from the player gives every actor a downhill direction. When the player moves, rebuild the field. Until then, every actor can read the same array.
This is also why Dijkstra maps compose well with flow fields and influence maps. They are all grid-wide fields. The difference is what the cell values mean and how actors interpret them.
The Pac-Man-style ghost AI pattern
A Pac-Man-style maze chase is the textbook shared-field case: several ghosts pursue one player on the same grid. Instead of running a separate A* search for every ghost, build one player-centered Dijkstra map and let each chaser choose a valid neighboring cell with a lower score.
Different personalities do not require separate pathfinders. A direct hunter can read the shared player field, an ambusher can reseed the same solver ahead of the player's direction, and other policies can blend or switch fields. Occupancy and target reservations remain a separate movement rule applied when each ghost chooses its next cell.
To build the complete small loop around that field, use the Pac-Man-style maze-chase starter. You can see the larger pattern running in Dijkstra's Ghost, a playable Godot maze chase where four hunters use one Dijkstra-map system and the live overlay exposes the field behind each decision.
The field representation
The proof scene uses one flat PackedFloat32Array. A cell at (x, y) maps to y * width + x. That keeps the field compact, easy to serialize, and easy to scan in a headless verification run.
INF is the sentinel for blocked or unreachable. Do not use null for every unreachable cell in a hot grid loop; keep the array dense and reserve dictionaries for sparse facts such as blockers or terrain overrides.
Rescan seeded fields
The important implementation detail is the rescan. A basic solver can wipe the field, seed a goal at zero, and expand from there. But source bias and flee behavior need something stronger: a function that starts from every finite value already in the field.
That is what rescan_field() does. It accepts a pre-filled field, pushes every finite non-blocked cell into the frontier, and relaxes outward with terrain costs. Positive edge costs keep the Dijkstra loop valid even when some source cells start at negative values.
Build the standard pursuit map
A pursuit map is the simplest case. Seed the player cell at zero, rescan, then every actor walks downhill. Terrain costs make slower cells more expensive, so the field naturally bends around mud, doors, or hazard tiles if you model them as higher entry cost.
This version uses a sorted array frontier for readability. For large maps or frequent rebuilds, swap it for a real priority queue. The algorithmic contract stays the same; only the frontier implementation changes.
Actors read downhill
Once the field exists, movement is cheap. The actor checks each walkable neighbor and picks the cell with the lowest value. This is the shareable part: the expensive search happened once; each actor's turn is just a local read.
Keep occupancy separate from the field. The field describes terrain and goals. Runtime blockers, other actors, and reserved target cells are a second layer applied during step selection.
Why several actors choose the same cell
The field describes terrain and goals. It doesn't know that another actor picked the same destination a few microseconds earlier. If two actors read the same field and see the same lowest neighbor, both choices are individually correct.
Equal-cost choices also need a policy. The downloadable solver iterates right, left, down, then up, and replaces the current choice only when it finds a strictly lower value. Equal values therefore keep the first authored direction. If you want variation, shuffle with a seeded RNG or rotate the authored direction order by actor ID; don't let incidental container iteration decide replays.
For real-time grid ticks, clear an ephemeral reserved_targets table at the start of the tick and claim every accepted destination until the batch commits. This conservative policy blocks moves into cells another actor intends to vacate later in the same tick. If actors must swap, push chains, or move through each other's starting cells, use the turn-order and movement-planning resolver instead of making the field own a batch-resolution problem.
| Movement model | What to do after reading the field |
|---|---|
| Sequential turn-based | Commit one actor, update occupancy, then let the next actor read the field. |
| Queued or simultaneous turns | Resolve actors in a stable order and claim destinations through the reservation table. |
| Real-time grid ticks | Clear an ephemeral reserved_targets table at the start of the tick and reserve each accepted destination until the batch is committed. |
Turn the field into a complete route
Most actors need only one downhill step. A movement preview, debug overlay, or ‘show route to the stairs’ command often needs the whole path. You don't need to run A* again: follow strictly lower field values until no lower neighbor exists.
On a valid field with positive entry costs, every accepted step is lower than the last and the trace ends at one of the seeds. An empty return means the start was invalid, unreachable, or the safety guard found inconsistent data. A one-cell path means the start was already at a local seed.
This route is a view over the field, not a new source of truth. If terrain, blockers, or seeds change, rebuild the field before drawing or confirming it. The action-points and terrain-cost guide owns the separate rule that previewed cost and confirmed cost must use the same board revision.
Multiple sources and source bias
Multiple sources are where Dijkstra maps start to feel like AI instead of only pathfinding. Seed the player at zero and treasure at a negative value, then rescan. Cells near the treasure become more attractive without writing a separate target-switching system.
The negative number is not magic. It is an explicit preference. A source at -4 says this source is four cost units more attractive than a neutral source at 0. The rescan propagates that preference through the map.
Flee maps need the second pass
Naive flee behavior often gets stuck in local corners: multiply the approach map by -1, then step downhill away from the player. The actor runs toward the locally highest distance, which may be a dead end.
The useful roguelike trick is to multiply the approach field by a stronger negative coefficient, usually around -1.2, then rescan. That creates a new safety field. The rescan rewards cells that are far from the player but still connected through useful exits, so the actor can head toward a doorway instead of hugging the nearest corner.
Compose desires without INF poisoning
A richer actor can score a cell against several maps: approach the player, prefer treasure, avoid hazard, seek unexplored cells. The pitfall is INF. If one optional map is unreachable at a cell, you do not want that one value to poison an otherwise valid score.
The proof scene handles this by skipping INF maps during composition. If no finite map contributes at all, the score is INF and the cell is not attractive.
Behavior recipes
The same solver covers several common roguelike behaviors. What changes is the seed set and how the actor interprets the result.
| Behavior | Sources | Actor read | When to rebuild |
|---|---|---|---|
| Pursuit | Player cell at 0 | Step to the lowest neighboring value | When the player moves or blockers change |
| Many actors, one target | Shared target at 0 | Every actor reads the same field | When target or terrain changes |
| Greed | Player at 0, gold at negative bias | Step toward the lowest score | When gold, player, or bias changes |
| Flee | Approach map multiplied by a negative coefficient, then rescanned | Step to the lowest safety value | When player or blocker state changes |
| Autoexplore | Unseen/frontier cells at 0 | Step toward the nearest unrevealed frontier | When visibility changes |
| Hazard escape | Safe cells at 0, hazard cells as high terrain cost | Step toward lower danger distance | When hazard or safe-state changes |
Update policy matters more than the first demo
The Dijkstra map should not rebuild just because an actor takes a step. Rebuild when the facts that define the field change: the player source moves, treasure is collected, a door opens, a wall appears, a cell is revealed, or the selected behavior changes.
For small roguelike maps, a full field rebuild is often fine. For larger maps, split the work: keep terrain and blocker data explicit, use dirty regions where possible, and move repeated field builds into a scheduled budget. The related frame-spike scheduling guide covers the production side of that decision.
Measurement-gap: the attached artifact is a correctness proof, not a throughput benchmark. Its verification JSON confirms ten behavior contracts on Godot 4.7.1: the original field, bias, flee, scoring, and hazard fixtures plus deterministic ties, target reservations, strictly descending route traces, and unreachable starts.
Try the proof scene
The browser demo attached above is there because this concept is easier to trust when it moves. Drag the player and the field bends immediately; switch to greed or flee and the same grid starts answering a different AI question.
The downloadable Godot mini project includes the shared solver and headless verification runner. The current receipt is dijkstra-maps-for-roguelike-ai-verification.json, generated from a Godot 4.7.1-stable run with all ten behavioral checks passing plus a clean extracted-package rerun bound to the exact ZIP.
Frequently asked questions
What is a Dijkstra map in roguelike AI?
A Dijkstra map is a grid-wide distance or cost field. Seed one or more source cells, compute the cheapest cost to every reachable cell, then let actors inspect neighboring cells and step toward the lowest value.
Does Godot have a built-in Dijkstra map?
No native one. AStarGrid2D returns paths, not a reusable distance field, and NavigationServer is navmesh-oriented. The best-known addon, Dijkstra Map for Godot, targets Godot 3 and needs a port to Godot 4, so in Godot 4 you build the field yourself - which, as this article shows, is a short script.
When should I use a Dijkstra map instead of A* in Godot?
Use A* for one actor going to one target. Use a Dijkstra map when many actors share the same target, when every cell needs a score, or when AI should combine pursuit, flee, exploration, hazard, and desire fields.
How do I make actors chase the player with a Dijkstra map?
Seed the player's cell at zero, build the Dijkstra field, and have each actor move to the neighboring walkable cell with the lowest field value. Rebuild the field when the player or blocker state changes.
How do flee maps work?
Build an approach map from the player, multiply finite values by a negative coefficient such as -1.2, then rescan the seeded field. Actors then move downhill on the rescanned safety field instead of simply hugging the nearest high-distance corner.
Can Dijkstra maps use terrain costs?
Yes. Add the movement cost of entering each neighbor during relaxation. Higher-cost terrain raises the field value and naturally changes the directions actors prefer.
Is this a performance benchmark?
No. The attached Godot scene is a correctness proof for the article code. It verifies ten behavior contracts on Godot 4.7.1; throughput claims need a separate benchmark scene and repeated measurements.
Why do several Dijkstra-map actors move to the same tile?
They read the same valid lowest neighbor, but the field doesn't contain actor occupancy. Update occupancy after each sequential move, or use stable ordering and destination reservations for queued or per-tick movement.
Can a Dijkstra map return a complete path?
It doesn't store one path per actor, but you can reconstruct one by repeatedly choosing a strictly lower neighboring value until the trace reaches a seed. Guard unreachable INF cells and loops, and rebuild the field before tracing if its inputs changed.