Skip to content

Navigation

Navigation moves an agent to a world point along a route that avoids blocked cells, using A* over a NavGrid.

A NavGrid is a rectangular grid of walkable/blocked cells. Install it on the Nav resource so agents can path against it:

import { defineSystem, Res, Nav, NavGrid } from 'esengine';
export const setupNav = defineSystem([Res(Nav)], (nav) => {
nav.setGrid(new NavGrid({
width: 60, // columns
height: 44, // rows
cellSize: 20, // world pixels per cell
origin: { x: -600, y: -440 }, // world position of cell (0,0)'s center
}));
}, { name: 'SetupNav' });
NavGrid option Type Description
width number Cell columns.
height number Cell rows.
cellSize number World pixels per cell (square).
origin Vec2 World position of cell (0,0)’s center. Defaults to (0,0).
walkable Uint8Array Optional row-major width*height mask, 1 = walkable, 0 = blocked. Omitted → all walkable.

To derive a grid from a painted tilemap instead of hand-authoring the mask, use navGridFromTilemapLayer (which cells a layer’s solid tiles block) or navGridFromTiles.

Attach a NavAgent, then point it at a destination. The built-in nav plugin plans the path and steps the agent along it each frame — you just set the goal.

import { NavAgent, setNavDestination, stopNavAgent } from 'esengine';
cmds.spawn()
.insert(Transform, { position: { x: 0, y: 0, z: 0 } })
.insert(NavAgent, { speed: 140, arriveRadius: 8 });
// From any system/action with world access — safe to call every frame:
setNavDestination(world, entity, { x: 320, y: -120 });
// …and to halt in place:
stopNavAgent(world, entity);
NavAgent field Default Description
speed 120 Movement speed in world pixels per second.
radius 0 How wide the body is, in pixels. Planning routes it around anything it would not fit through; 0 routes it as a point.
arriveRadius 6 Stop distance from the final goal.
repathInterval 0.5 Seconds between replans while moving; 0 = replan only when the target changes.
hasTarget false Whether a destination is set (managed for you).
targetX, targetY 0 Current destination in world pixels.
arrived false Set true the frame the agent reaches its goal.

setNavDestination is safe to call every frame to chase a moving target — the agent only replans when the target actually moves or repathInterval elapses. Read agent.arrived (or Perception) to know when to switch behavior.

Set radius to the body’s actual half-width and planning keeps it out of gaps it cannot fit through. Left at 0 an agent is routed as a point, which is what you want for something that has no collider — and what produces the classic sight of an enemy walking confidently into a doorway and stopping there, because the cell it was routed to is walkable and the half of it hanging over the next one is not.

A goal the body cannot stand on (a pickup in a corner) is still reached: the plan ends as close as the body fits, and arriveRadius covers the rest.