Steering Behaviors

Reynolds-style steering behaviors for creating realistic autonomous agent movement with seek, flee, wander, obstacle avoidance, and flocking.

Overview#

Steering behaviors create natural-looking movement by calculating steering forces that guide agents toward their goals while avoiding obstacles and other agents.

Available Behaviors#

Basic Movement

Seek, Flee, Arrive, Pursue, and Evade behaviors for fundamental agent motion.

Complex Behaviors

Wander, Obstacle Avoidance, Hide, Path Following, and Containment for advanced navigation.

Flocking (Group AI)

Separation, Alignment, Cohesion, and combined Flocking for realistic group movement.

Utilities

SteeringCombiner for weighted blending and priority-based selection with zero-GC design.

Basic Behaviors#

Seek#

Steer toward a target at maximum speed:

seek.ts
typescript
import { SteeringBehaviors, SteeringAgent, SteeringTarget } from '@web-engine-dev/core/engine/ai';
const agent: SteeringAgent = {
position: Transform.position[eid],
velocity: Velocity.linear[eid],
maxSpeed: 5.0,
maxForce: 10.0,
radius: 0.5,
mass: 1.0,
};
const target: SteeringTarget = {
position: targetPosition,
};
const result = SteeringBehaviors.seek(agent, target);
if (result.active) {
// Apply steering force
Velocity.linear[eid][0] += result.force.x * delta;
Velocity.linear[eid][1] += result.force.y * delta;
Velocity.linear[eid][2] += result.force.z * delta;
}

Flee#

Steer away from a threat:

flee.ts
typescript
const threat: SteeringTarget = {
position: enemyPosition,
};
// Flee with panic distance (only flee if within 10 units)
const result = SteeringBehaviors.flee(agent, threat, panicDistance: 10.0);
if (result.active) {
// Apply force to move away
applyForce(eid, result.force, delta);
}

Arrive#

Seek with deceleration near the target to avoid overshooting:

arrive.ts
typescript
const target: SteeringTarget = {
position: destinationPosition,
};
// Arrive with slowing radius of 2 units
const result = SteeringBehaviors.arrive(agent, target, slowingRadius: 2.0);
if (result.active) {
applyForce(eid, result.force, delta);
}
// Agent will slow down smoothly as it approaches the target

Pursue & Evade#

Intercept moving targets by predicting their future position:

pursue-evade.ts
typescript
// Pursue a moving target
const target: SteeringTarget = {
position: Transform.position[targetEid],
velocity: Velocity.linear[targetEid], // Required for prediction
};
const pursueResult = SteeringBehaviors.pursue(
agent,
target,
lookAheadTime: 0 // 0 = auto-calculate based on speeds
);
// Evade a moving threat
const evadeResult = SteeringBehaviors.evade(
agent,
threat,
lookAheadTime: 1.0, // Look ahead 1 second
panicDistance: 15.0 // Only evade within 15 units
);

Complex Behaviors#

Wander#

Random exploration with smooth, natural-looking movement:

wander.ts
typescript
import { WanderConfig } from '@web-engine-dev/core/engine/ai';
const wanderConfig: WanderConfig = {
circleDistance: 2.0, // Distance of wander circle from agent
circleRadius: 1.0, // Radius of the wander circle
angleChange: 0.3, // Maximum angle change per update
};
// Wander requires entity ID for per-agent state tracking
const result = SteeringBehaviors.wander(agent, eid, wanderConfig);
if (result.active) {
applyForce(eid, result.force, delta);
}
// Clean up wander state when entity is destroyed
SteeringBehaviors.clearWanderState(eid);

Obstacle Avoidance#

Navigate around obstacles using feeler rays:

obstacle-avoidance.ts
typescript
import { SteeringObstacle } from '@web-engine-dev/core/engine/ai';
// Define obstacles
const obstacles: SteeringObstacle[] = [
{ position: new THREE.Vector3(5, 0, 5), radius: 1.0 },
{ position: new THREE.Vector3(-3, 0, 8), radius: 1.5 },
];
// Avoid obstacles with 5 unit look-ahead
const result = SteeringBehaviors.obstacleAvoidance(
agent,
obstacles,
lookAhead: 5.0
);
if (result.active) {
// High priority - apply immediately
applyForce(eid, result.force, delta);
}

Path Following#

Follow a sequence of waypoints:

path-following.ts
typescript
import { SteeringPath } from '@web-engine-dev/core/engine/ai';
const path: SteeringPath = {
points: [
new THREE.Vector3(0, 0, 0),
new THREE.Vector3(5, 0, 5),
new THREE.Vector3(10, 0, 5),
new THREE.Vector3(10, 0, 10),
],
looped: false, // Whether to loop back to start
radius: 1.0, // Waypoint proximity threshold
};
let currentIndex = 0;
function updatePathFollowing() {
const { result, nextIndex } = SteeringBehaviors.pathFollowing(
agent,
path,
currentIndex
);
currentIndex = nextIndex;
if (result.active) {
applyForce(eid, result.force, delta);
} else {
// Path complete
onPathComplete();
}
}

Hide#

Find cover behind obstacles from a threat:

hide.ts
typescript
const threat: SteeringTarget = {
position: enemyPosition,
};
const obstacles: SteeringObstacle[] = [
{ position: new THREE.Vector3(5, 0, 0), radius: 2.0 },
{ position: new THREE.Vector3(-5, 0, 5), radius: 1.5 },
];
// Find hiding spot 2 units behind obstacles
const result = SteeringBehaviors.hide(
agent,
threat,
obstacles,
hideDistance: 2.0
);
if (result.active) {
// Moves to hiding spot behind nearest obstacle
applyForce(eid, result.force, delta);
}
// Falls back to flee if no hiding spots found

Containment#

Keep agents within defined boundaries:

containment.ts
typescript
const min = new THREE.Vector3(-50, 0, -50);
const max = new THREE.Vector3(50, 10, 50);
const result = SteeringBehaviors.containment(
agent,
min,
max,
margin: 5.0 // Start steering when within 5 units of edge
);
if (result.active) {
// Steer back toward center
applyForce(eid, result.force, delta);
}

Flocking Behaviors#

Separation#

Avoid crowding neighbors:

separation.ts
typescript
// Get nearby agents
const neighbors: SteeringAgent[] = getNearbyAgents(eid, radius: 5.0);
// Separate from neighbors within 2 units
const result = SteeringBehaviors.separation(
agent,
neighbors,
radius: 2.0
);

Alignment#

Match velocity direction with neighbors:

alignment.ts
typescript
// Align with neighbors within 5 units
const result = SteeringBehaviors.alignment(
agent,
neighbors,
radius: 5.0
);

Cohesion#

Move toward the center of the group:

cohesion.ts
typescript
// Move toward group center (neighbors within 5 units)
const result = SteeringBehaviors.cohesion(
agent,
neighbors,
radius: 5.0
);

Combined Flocking#

combined-flocking.ts
typescript
import { FlockingConfig } from '@web-engine-dev/core/engine/ai';
const config: FlockingConfig = {
separationWeight: 1.5, // Avoid crowding
alignmentWeight: 1.0, // Match heading
cohesionWeight: 1.0, // Move to center
separationRadius: 2.0, // Personal space
neighborRadius: 5.0, // Group awareness
};
const result = SteeringBehaviors.flocking(agent, neighbors, config);
if (result.active) {
// Apply combined flocking force
applyForce(eid, result.force, delta);
}

Combining Behaviors#

Weighted Blending#

Combine multiple behaviors with weights:

weighted-blending.ts
typescript
import { SteeringCombiner, SteeringPriority } from '@web-engine-dev/core/engine/ai';
const combiner = new SteeringCombiner();
// Add behaviors with weights
const seek = SteeringBehaviors.seek(agent, target);
combiner.add('seek', SteeringPriority.Normal, 1.0, seek.force, seek.active);
const separation = SteeringBehaviors.separation(agent, neighbors, 2.0);
combiner.add('separation', SteeringPriority.Normal, 0.5, separation.force, separation.active);
const wander = SteeringBehaviors.wander(agent, eid);
combiner.add('wander', SteeringPriority.Low, 0.3, wander.force, wander.active);
// Calculate weighted blend
const finalForce = combiner.calculateWeightedBlend(agent.maxForce);
// Apply force
applyForce(eid, finalForce, delta);
// Clear for next frame
combiner.clear();

Priority-Based Selection#

Higher priority behaviors override lower ones:

priority-selection.ts
typescript
const combiner = new SteeringCombiner();
// Critical priority: obstacle avoidance
const avoidance = SteeringBehaviors.obstacleAvoidance(agent, obstacles, 5.0);
combiner.add('avoidance', SteeringPriority.Critical, 1.0, avoidance.force, avoidance.active);
// High priority: flee from threats
const flee = SteeringBehaviors.flee(agent, threat, 10.0);
combiner.add('flee', SteeringPriority.High, 1.0, flee.force, flee.active);
// Normal priority: seek target
const seek = SteeringBehaviors.seek(agent, target);
combiner.add('seek', SteeringPriority.Normal, 1.0, seek.force, seek.active);
// Calculate prioritized force (higher priority takes precedence)
const finalForce = combiner.calculatePrioritized(agent.maxForce);
applyForce(eid, finalForce, delta);
combiner.clear();

Steering System#

Setup Steering Component#

setup-steering.ts
typescript
import { Steering, initSteering, setSteeringBehaviors, SteeringFlags } from '@web-engine-dev/core/engine/ai';
// Add steering component
addComponent(world, Steering, eid);
// Initialize with default values
initSteering(eid);
// Configure steering parameters
Steering.maxSpeed[eid] = 5.0;
Steering.maxForce[eid] = 10.0;
Steering.arriveRadius[eid] = 1.0;
Steering.slowingRadius[eid] = 2.0;
// Enable behaviors
setSteeringBehaviors(eid, SteeringFlags.Seek | SteeringFlags.Separation);

SteeringSystem Integration#

system-integration.ts
typescript
import { SteeringSystem } from '@web-engine-dev/core/engine/ai';
// In your game loop
const steeringSystem = SteeringSystem(world);
function gameLoop(delta: number, time: number) {
// SteeringSystem automatically applies steering forces
steeringSystem(delta, time);
}
// Set path for path following
import { setAgentPath } from '@web-engine-dev/core/engine/ai';
setAgentPath(eid, waypoints, looped: false);
// Clear path when done
import { clearAgentPath } from '@web-engine-dev/core/engine/ai';
clearAgentPath(eid);

Performance & Optimization#

Zero-GC Design#

Steering behaviors use pre-allocated vectors to avoid garbage collection:

  • All calculations reuse shared temporary vectors
  • Result objects are reused across calls
  • No allocations in hot paths (60 FPS safe)

Neighbor Queries#

Use spatial indexing for efficient neighbor lookups:

neighbor-queries.ts
typescript
import { getAISpatialIndex } from '@web-engine-dev/core/engine/ai';
const spatialIndex = getAISpatialIndex();
// Query nearby entities efficiently
const neighbors = spatialIndex.queryRadius(
position,
radius: 10.0,
maxResults: 32
);
// Convert to SteeringAgent array
const steeringAgents: SteeringAgent[] = neighbors.map(neighbor => ({
position: Transform.position[neighbor.eid],
velocity: Velocity.linear[neighbor.eid],
maxSpeed: 5.0,
maxForce: 10.0,
radius: 0.5,
mass: 1.0,
}));

Update Throttling#

throttling.ts
typescript
// Throttle steering updates for distant agents
const updateInterval = distanceToCamera < 20 ? 0.0 : 0.1;
Steering.updateInterval[eid] = updateInterval;
// SteeringSystem will automatically throttle updates

Example: Complete Steering Setup#

complete-steering.ts
typescript
import {
SteeringBehaviors,
SteeringCombiner,
SteeringPriority,
SteeringAgent,
getAISpatialIndex,
} from '@web-engine-dev/core/engine/ai';
function updateSteering(eid: number, delta: number) {
// Build agent
const agent: SteeringAgent = {
position: Transform.position[eid],
velocity: Velocity.linear[eid],
maxSpeed: 5.0,
maxForce: 10.0,
radius: 0.5,
mass: 1.0,
};
const combiner = new SteeringCombiner();
// Get obstacles
const obstacles = getObstaclesNearAgent(eid);
// Critical: obstacle avoidance
const avoidance = SteeringBehaviors.obstacleAvoidance(agent, obstacles, 5.0);
combiner.add('avoidance', SteeringPriority.Critical, 1.0, avoidance.force, avoidance.active);
// High: containment
const bounds = getBounds();
const containment = SteeringBehaviors.containment(agent, bounds.min, bounds.max, 5.0);
combiner.add('containment', SteeringPriority.High, 1.0, containment.force, containment.active);
// Normal: seek target or wander
const targetEid = getBlackboardValue(AIAgent.blackboardId[eid], 'targetEid');
if (targetEid) {
const target = {
position: Transform.position[targetEid],
velocity: Velocity.linear[targetEid],
};
const pursue = SteeringBehaviors.pursue(agent, target);
combiner.add('pursue', SteeringPriority.Normal, 1.0, pursue.force, pursue.active);
} else {
const wander = SteeringBehaviors.wander(agent, eid);
combiner.add('wander', SteeringPriority.Normal, 1.0, wander.force, wander.active);
}
// Calculate final force
const force = combiner.calculatePrioritized(agent.maxForce);
// Apply force
Velocity.linear[eid][0] += force.x * delta;
Velocity.linear[eid][1] += force.y * delta;
Velocity.linear[eid][2] += force.z * delta;
// Clamp velocity to max speed
const vx = Velocity.linear[eid][0];
const vy = Velocity.linear[eid][1];
const vz = Velocity.linear[eid][2];
const speed = Math.sqrt(vx * vx + vy * vy + vz * vz);
if (speed > agent.maxSpeed) {
const scale = agent.maxSpeed / speed;
Velocity.linear[eid][0] *= scale;
Velocity.linear[eid][1] *= scale;
Velocity.linear[eid][2] *= scale;
}
combiner.clear();
}
Documentation | Web Engine