Skip to content

Agent Behaviors

The aggregate SpatialPlugin includes optional components for range-gated steering, target priority, and crowd separation. Add only the behaviors each agent needs.

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,
}),
);

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.

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,
});
FieldDefaultDescription
radius64World-space distance in which other agents cause repulsion
strength2800Maximum 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.

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.