NavAgent
NavAgent
Section titled “NavAgent”Moving an NPC along a path involves three concerns: finding the route, advancing position each tick, and knowing when the goal is reached. golem.NavAgent handles all three. Embed it in your NPC struct—it owns the path, reads and writes position automatically, and drives movement through the standard Ticker interface.
Wiring up
Section titled “Wiring up”Embed NavAgent, call Bind in OnSpawn, and delegate to Tick in the entity’s Tick method:
type NPC struct { TargetX, TargetY float64 Agent golem.NavAgent}
func NewNPC() *NPC { return &NPC{ Agent: golem.NavAgent{ Speed: 120, // world units per second StoppingDistance: 8, // half a 16-px tile }, }}
func (n *NPC) OnSpawn() { n.Agent.Bind(n) }func (n *NPC) Tick(dt float64) { n.Agent.Tick(dt) }Bind attaches the entity so the agent can read and write its position without extra arguments. Any generated Synced* type satisfies the requirement automatically—no extra methods needed on your struct.
Tick is called by the registry before your OnTick callback each tick, so movement happens without a manual loop.
Setting a destination
Section titled “Setting a destination”Call SetDestination whenever the NPC’s goal changes—on spawn, in response to a player command, or when a patrol target is reached:
// In a command handler or OnTick:npc.Agent.SetDestination(s, npc.TargetX, npc.TargetY)SetDestination reads the NPC’s current position from the bound entity, calls FindPath internally, and stores the resulting route. It returns false when no path exists or no backend is configured. When the goal is reached, HasPath returns false and Tick becomes a no-op until a new destination is set.
Replanning
Section titled “Replanning”Call SetDestination again at any time to reroute—for example when the target moves, a door opens, or the player issues a new command:
// Replan whenever the target has moved significantly.if dist(npc.TargetX, npc.TargetY, newTarget.X, newTarget.Y) > 32 { npc.TargetX, npc.TargetY = newTarget.X, newTarget.Y npc.Agent.SetDestination(s, npc.TargetX, npc.TargetY)}Call ResetPath to stop the NPC immediately without giving it a new route.
Velocity and facing direction
Section titled “Velocity and facing direction”Velocity returns the NPC’s movement direction scaled by Speed in world units per second, as computed during the most recent tick. Use it to drive animation or to send facing direction to clients:
vx, vy := npc.Agent.Velocity() // (0, 0) when not movingRemaining distance
Section titled “Remaining distance”RemainingDistance returns the total path length from a given position to the goal. Use it to trigger arrival events or start a landing animation before the NPC fully halts:
if npc.Agent.RemainingDistance(npc.Position()) < 32 { npc.StartLandingAnimation()}NextWaypoint returns the immediate next waypoint the agent is heading toward—useful for look-ahead steering or debug visualisation.
Manual control
Section titled “Manual control”If you prefer to drive movement from OnTick instead of implementing Ticker—for example to interleave movement with other per-entity logic in a specific order—bind the entity but call Step yourself rather than delegating to Tick:
// Entity binds in OnSpawn but does NOT implement Ticker.func (n *NPC) OnSpawn() { n.Agent.Bind(n) }
// In OnTick, drive movement explicitly:srv.OnTick(func(dt float64, s *golem.Server) { for _, e := range s.All() { npc, ok := e.(*NPC) if !ok { continue } if !npc.Agent.HasPath() { npc.Agent.SetDestination(s, npc.TargetX, npc.TargetY) } x, y := npc.Position() nx, ny := npc.Agent.Step(dt, x, y) npc.SetPosition(nx, ny) }})Bind is still required for SetDestination. If you want to skip NavAgent entirely and manage the path slice yourself, call s.FindPath directly.
See also: Backends & wiring, kelindar backend.