Agent Behaviors
The aggregate SpatialPlugin includes optional components for range-gated steering, target priority,
and crowd separation. Add only the behaviors each agent needs.
Range-gated steering
Section titled “Range-gated steering”Add LineOfSight to make an agent follow its flow field only while a target in its current group is
within range:
import { Define, Position, Velocity } from '@shell/engine-2d';import { FlowFieldAgent, FlowFieldTargetPriority, GridCell, LineOfSight,} from '@shell/spatial';
const NavigationGroup = { Enemy: 1, Fortress: 2,};
const Guard = Define.prefab() .with(Position) .with(Velocity) .with(GridCell) .with(FlowFieldAgent, { group: NavigationGroup.Enemy, speed: 90 }) .with(LineOfSight, { range: 400 });LineOfSight.hasTarget is maintained by the plugin and can also be read by gameplay systems:
const sight = guard.getComponent(LineOfSight);if (sight?.hasTarget) { // A matching target is in range.}Despite the name, this is a 2D distance check. Navigation obstacles do not occlude targets. Agents
without LineOfSight follow their selected flow field unconditionally.
Checks are spread across lineOfSightIntervalMs to avoid scanning every agent in one update. Set the
option to 0 when every agent must be refreshed every Update:
world.addPlugin( SpatialPlugin.config({ cellSize: 32, width: 2048, height: 2048, lineOfSightIntervalMs: 0, }),);Primary and fallback targets
Section titled “Primary and fallback targets”FlowFieldTargetPriority lets a range-gated agent prefer one group and fall back to another. It
automatically requires FlowFieldAgent and LineOfSight:
const Defender = Define.prefab() .with(Position) .with(Velocity) .with(GridCell) .with(FlowFieldTargetPriority, { primaryGroup: NavigationGroup.Enemy, fallbackGroup: NavigationGroup.Fortress, });The plugin selects the primary group when a primary target is in range. Otherwise it selects the
fallback group when available. With neither available, it restores the primary group and sets
LineOfSight.hasTarget to false, which stops flow-field steering.
Both values must be individual group flags, not combined target masks.
Separate nearby agents
Section titled “Separate nearby agents”Add Separation to agents that should repel one another while moving:
import { Separation } from '@shell/spatial';
const CrowdAgent = Define.prefab() .with(Position) .with(Velocity) .with(GridCell) .with(FlowFieldAgent, { speed: 100 }) .with(Separation, { radius: 28, strength: 1400, });| Field | Default | Description |
|---|---|---|
radius | 64 | World-space distance in which other agents cause repulsion |
strength | 2800 | Maximum separation acceleration |
Only entities with Separation, GridCell, and Position participate as neighbors. This makes the
behavior opt-in: ordinary indexed entities, targets, and obstacles do not push agents away.
Separation changes Velocity after flow-field steering and before kinematic integration. It does
not perform collision detection, so use physics colliders as well when agents must not overlap.
Combine the behaviors
Section titled “Combine the behaviors”The behaviors compose on one prefab:
const Pursuer = Define.prefab() .with(Position) .with(Velocity) .with(GridCell) .with(FlowFieldAgent, { speed: 100 }) .with(LineOfSight, { range: 512 }) .with(FlowFieldTargetPriority, { primaryGroup: NavigationGroup.Enemy, fallbackGroup: NavigationGroup.Fortress, }) .with(Separation, { radius: 32, strength: 1600 });This agent follows a nearby enemy, falls back to the fortress, stops steering when neither is in range, and separates from other separation-enabled agents.