Skip to main content
Version: Next

Walker

Walker is an optional movement helper for GameObjects. It can:

  • move an object along a list of waypoints (patrol paths, NPC routes)
  • loop those waypoints (cyclic) or stop at the end
  • optionally do simple grid-based pathfinding to avoid solid obstacles
  • draw debug visuals for waypoints and the computed path

Under the hood, Walker.tick() updates gameObject.speed every tick, and the scene physics step integrates that velocity as world units per second afterward.

Attaching a Walker

Create a walker and attach it to a game object:

import { GameObject, Vector, Walker } from "sliver-engine";

const npc = new GameObject("npc", new Vector(100, 100));

const walker = new Walker(
npc,
[new Vector(100, 100), new Vector(300, 100), new Vector(300, 300)],
120, // movement speed in pixels per second
true, // debug
true // cyclic (loop)
);

npc.setWalker(walker);
walker.start();

Notes:

  • Waypoints are interpreted in scene/world space (they’re compared to getScenePosition()).
  • speed is expressed in pixels per second, so values around 80 to 140 are a more typical starting point than 1 or 2.
  • Debug drawing respects the scene offset (so it stays aligned if the camera moves).

Starting, stopping, and resetting

  • start(): activates movement (and requests path recalculation)
  • toggle(): switches active/inactive (stops by zeroing object speed)
  • reset(): resets indices/path but does not teleport
  • hardReset(): resets and teleports the object to the first waypoint (if it exists)
walker.toggle();
walker.reset();
walker.hardReset();

Changing waypoints at runtime

walker.setWaypoints([a, b, c], true); // cyclic

setWaypoints also resets internal path state and stops the object momentarily (speed = 0) so the new route starts cleanly.

Completion callback

If ciclic is false, the walker stops after reaching the final waypoint and calls onComplete:

walker.setOnComplete(() => {
npc.sendMessage("npc:arrived", { name: npc.name });
});

Obstacle avoidance (pathfinding)

When enabled, the walker runs a lightweight grid A* search to route around obstacles.

walker.setPathfindingOptions({
avoidObstacles: true,
gridCellSize: 16,
recalculateEveryTicks: 30,
});

How obstacle avoidance works:

  • Only solid hitboxes are considered (hitbox.solid === true).
  • The walker builds a “proxy body” from the moving object’s solid hitboxes.
  • It treats other active objects’ solid hitboxes in the same scene as obstacles.
  • It searches on a 4-neighbor grid (up/down/left/right) and then simplifies the resulting path.

When paths recalculate

The walker can recalculate when:

  • you call requestPathRecalculation()
  • recalculateEveryTicks elapses
  • shouldRecalculatePath(ctx) returns true
walker.setPathfindingOptions({
avoidObstacles: true,
shouldRecalculatePath: ({ tick }) => tick % 10 === 0,
});

If there are no obstacles (or the object has no solid hitboxes), the walker falls back to moving directly toward the waypoint.

When no path is found

If obstacle avoidance is enabled and the walker cannot find a path, it throws by default. You can control that behavior:

walker.setPathfindingOptions({
avoidObstacles: true,
pathNotFoundBehavior: "snap", // "throw" | "stop" | "snap" | "continue"
snapTargetToEdgeDistance: 12,
});
  • throw: throw an error when no path is found.
  • stop: stop the walker and leave the object in place.
  • snap: if the waypoint is blocked, search for the nearest free target position within snapTargetToEdgeDistance and path to that instead.
  • continue: skip pathfinding and walk straight toward the waypoint.

For custom handling, use onPathNotFound:

walker.setPathfindingOptions({
avoidObstacles: true,
onPathNotFound: ({ goal }) => ({ behavior: "snap", goal }),
});

Performance knobs

For large scenes, tune:

  • maxExpandedNodes: cap the A* work per recalculation
  • maxSearchRadiusTiles: cap search distance (in tiles) around the start
walker.setPathfindingOptions({
avoidObstacles: true,
maxExpandedNodes: 5000,
maxSearchRadiusTiles: 64,
});

Debug drawing

If debug is true, the walker draws:

  • waypoints (red circles) and waypoint links (red lines)
  • the current computed path nodes (cyan circles/lines)

Walker debug rendering is called from the owning object’s render path, but it intentionally cancels object rotation so the path is drawn in world space.

If you want a larger obstacle-avoidance playground with draggable blockers, see Enemy patrol (Walker).

Pathfinding control

When obstacle avoidance is enabled, you can temporarily pause or cancel path updates without removing the walker:

walker.pausePathfinding();
walker.resumePathfinding();
walker.abortPathfinding();
  • pausePathfinding(): keep the current routed path, but stop recalculating new ones.
  • resumePathfinding(): re-enable pathfinding and request a fresh recalculation.
  • abortPathfinding(): clear the current path, zero movement, and leave pathfinding paused until you resume it.

Interaction with physics

Since Walker sets gameObject.speed, it composes naturally with Sliver physics:

  • collisions can push the object off its ideal path; the walker will drop “already reached” nodes and continue
  • if you also set speed elsewhere (your own tickFn), last write wins—prefer one source of movement at a time

For physics details (hitboxes, solid vs trigger, impulses), see Physics.

Example

For a full interactive walker demo with moving patrols, obstacle avoidance, and draggable blockers, see Enemy patrol (Walker).