Wishlist on Steam
No.12

Steering Behaviors

Discover how Craig Reynolds' steering behaviors — Seek, Flee, Arrive, and Wander — give game characters natural autonomous movement using elegant vector math you can implement in an afternoon.

Published
Reading time
7 min
Demos
One to play with
Author
Kieren Hovasapian
Interactive 3D · Drag to orbit · scroll to zoomOpen full screen

Imagine a crowd of pedestrians parting around a street performer, or enemy soldiers spreading out to flank your position. These movement patterns don't come from hand-crafted animations or rigid scripted paths. They emerge from a handful of mathematical rules called steering behaviors.

First formalized by Craig Reynolds in his 1999 GDC paper "Steering Behaviors for Autonomous Characters," this system remains one of the most practical tools in game AI. It operates on a simple premise: instead of dictating exactly where an agent should be, tell it what it wants to do and let physics handle the rest.

The Three-Layer Model

Reynolds organized autonomous movement into three hierarchical layers that cleanly separate concerns:

  • Action Selection: Decides what the agent wants — seek a target, flee a threat, idle randomly.
  • Steering: Translates that goal into a force vector pointing the right direction with the right urgency.
  • Locomotion: Applies that force to velocity and integrates position each frame.

This separation works because the locomotion layer is universal. Whether an agent is fleeing a dragon or ambling toward a tavern, the same physics update runs: add force to velocity, clamp to max speed, add velocity to position. Only the computed steering force changes, making behaviors fully composable.

The Core Steering Equation

Every steering behavior shares one fundamental equation. Compute the desired velocity — the velocity needed to accomplish the goal perfectly right now. The steering force is the difference from current velocity:

Clamp this to a maximum force , then integrate:

When the agent heads in the wrong direction, is large and correction is strong. When already close to the desired heading, the difference is tiny — a gentle nudge. The result is inertial motion that makes characters feel like they have weight and intention.

JavaScript
function applySteering(agent, steeringForce, dt) {
  const f = steeringForce.clone().clampLength(0, agent.maxForce);
  agent.velocity.addScaledVector(f, dt);       // F = ma, mass = 1
  agent.velocity.clampLength(0, agent.maxSpeed);
  agent.position.addScaledVector(agent.velocity, dt);
}

Seek: The Foundation Behavior

The simplest behavior: the agent desires to move directly toward a target at maximum speed. The desired velocity is a unit vector toward the target, scaled by :

A pure Seek agent overshoots and orbits the target perpetually, which works for homing projectiles but not for characters who need to stop. That problem is solved by Arrive.

JavaScript
function seek(agent, targetPos) {
  const desired = targetPos.clone()
    .sub(agent.position)
    .normalize()
    .multiplyScalar(agent.maxSpeed);
  return desired.sub(agent.velocity);
}

Flee: The Mirror of Seek

Flee reverses the desired direction — the agent moves away from the threat at full speed. A flee radius ensures the agent only reacts when the threat is close enough to perceive:

JavaScript
function flee(agent, threatPos, fleeRadius = 20) {
  const diff = agent.position.clone().sub(threatPos);
  if (diff.length() > fleeRadius) return new THREE.Vector3();
  return diff.normalize().multiplyScalar(agent.maxSpeed).sub(agent.velocity);
}

Arrive: Graceful Deceleration

Arrive adds a slowing radius . Outside it, the agent runs at . Inside, desired speed scales linearly down to zero:

At the exact target, desired speed is zero — the agent brakes to a perfect stop. Tune to taste: small values create a sharp stop, large values a sweeping deceleration curve.

JavaScript
function arrive(agent, targetPos, slowRadius = 10) {
  const desired = targetPos.clone().sub(agent.position);
  const dist = desired.length();
  if (dist < 0.01) return new THREE.Vector3();

  const speed = dist < slowRadius
    ? agent.maxSpeed * (dist / slowRadius)
    : agent.maxSpeed;

  return desired.normalize().multiplyScalar(speed).sub(agent.velocity);
}

Wander: Organic Random Motion

Picking a new random direction every frame produces incoherent jitter. Wander fixes this by placing an imaginary circle of radius at distance ahead of the agent, and walking a point around it by a small random angle each frame.

Circle center projected along current heading:

Wander target on the circle:

Because changes gradually, motion is smooth curves rather than random snapping — the agent drifts with apparent internal motivation, perfect for idle NPCs or ambient wildlife.

JavaScript
function wander(agent) {
  const wanderRadius = 3, wanderDist = 5, jitter = 0.4;

  agent.wanderAngle += (Math.random() - 0.5) * jitter;

  const fwd = agent.velocity.clone().normalize();
  if (fwd.lengthSq() < 0.01) fwd.set(0, 0, 1);

  const circleCenter = fwd.multiplyScalar(wanderDist);
  circleCenter.x += Math.cos(agent.wanderAngle) * wanderRadius;
  circleCenter.z += Math.sin(agent.wanderAngle) * wanderRadius;
  return circleCenter;
}

Obstacle Avoidance

Avoidance projects a detection cylinder ahead of the agent. When it intersects an obstacle, a lateral force steers the agent around it — stronger for imminent collisions, weaker for distant ones:

where is the unit vector perpendicular to forward, pointing away from the obstacle. In practice, a fan of three to five forward rays gives solid results for most game scenarios without expensive geometry queries.

Combining Behaviors: Weighted Blending

Steering behaviors are most powerful when combined. Each behavior produces a force vector; the final steering force is a weighted sum:

JavaScript
function computeSteering(agent, player, obstacles) {
  return new THREE.Vector3()
    .add(seek(agent, player.position).multiplyScalar(0.6))
    .add(wander(agent).multiplyScalar(0.15))
    .add(avoidObstacles(agent, obstacles).multiplyScalar(1.2))
    .clampLength(0, agent.maxForce);
}

Weighted blending has one failure mode: opposing forces can cancel each other, sending an agent straight into a wall it's nominally avoiding. Priority-based truncation fixes this by allocating a "steering budget" to behaviors in order of urgency:

JavaScript
function prioritySteering(agent, behaviors) {
  const result = new THREE.Vector3();
  let budget = agent.maxForce;

  for (const behavior of behaviors) {
    if (budget <= 0.001) break;
    const force = behavior(agent);
    const mag = force.length();
    if (mag <= budget) {
      result.add(force);
      budget -= mag;
    } else {
      result.add(force.normalize().multiplyScalar(budget));
      break;
    }
  }
  return result;
}

Arrange behaviors in urgency order — obstacle avoidance first, separation second, seek/flee third, wander last — and the agent always prioritizes self-preservation over higher-level goals.

Separation and Cohesion for Groups

Two more behaviors handle crowd dynamics:

  • Separation: Each agent steers away from nearby neighbors to avoid crowding. Repulsion is stronger for closer agents: where points away from neighbor .
  • Cohesion: Each agent steers toward the average position of its local group. Combined with separation and velocity alignment, this produces emergent flocking — the foundation of the Boids algorithm covered in a separate article.

Real-World Game Applications

Steering behaviors ship in virtually every major game with NPC movement:

  • GTA series: Pedestrians idle with wander, flee when startled by gunshots (flee radius triggered by sound), and seek destinations on their daily routines. Traffic AI uses arrive to stop at lights.
  • Halo (Bungie): Covenant enemies combine seek, flee, and obstacle avoidance. The famous "smart" feel of Elites flanking and taking cover emerges directly from these weighted behaviors interacting with the environment geometry.
  • The Sims (Maxis): Sims use arrive to approach furniture and activities, separation to avoid overlapping, and flee-like behaviors to escape fire or overflowing toilets.
  • Pac-Man: Each ghost targets a different map tile using seek variants. Blinky targets Pac-Man's current tile (pure seek). Pinky targets four tiles ahead of Pac-Man (predictive seek). Clyde switches between seek and flee based on proximity. All of them are just vector arithmetic evaluated once per frame.
  • StarCraft II: Unit movement uses arrive for stop commands, separation to prevent stacking, and obstacle avoidance around terrain. Those units that appear to "think" about approaching an enemy base are running these equations thousands of times per second.

Performance Considerations

A single steering force computation costs a handful of vector operations — negligible for up to a few hundred agents. The bottleneck for large crowds is neighbor queries: finding which agents are nearby for separation and cohesion. Brute-force comparison grinds to a halt beyond a few hundred agents. The solutions are the same as for other spatial problems — Spatial Hashing or Quadtrees reduce neighbor lookups to near-. Unity's DOTS system processes millions of steering agents in parallel across CPU cores using SIMD instructions.

For most games — a handful of enemies, a crowd of twenty pedestrians — no optimization is needed. Implement naively, iterate on feel, profile later.

Getting Started

The interactive demo above shows all four core behaviors live. To add steering to your own project:

  1. Give each agent a position, velocity, maxSpeed, and maxForce.
  2. Each frame, compute a steering force based on current game state.
  3. Add force to velocity (clamped to maxForce * dt), clamp velocity to maxSpeed, add velocity to position.
  4. Set visual rotation from velocity direction: angle = atan2(vel.x, vel.z).

Start with Seek and Arrive. Two behaviors carry you further than you might expect. Add Wander for idle characters, Flee for NPCs reacting to threats, and Separation to prevent agents from stacking. Tune weights and radii until the movement feels right. That tuning process is the craft.

Mechanics Library

The live demo from Navigation Meshes (NavMesh).

No.42AI & navigation

Navigation Meshes (NavMesh)

Learn how navigation meshes decompose walkable game environments into convex polygons for fast, smooth AI pathfinding — from Recast generation to the funnel algorithm.

1 demo8 min read

The live demo from Flow Fields.

No.31AI & navigation

Flow Fields

Learn how flow fields provide efficient pathfinding for hundreds of agents simultaneously by computing a single vector field that guides all movement, using the same three-layer algorithm behind RTS games like Supreme Commander.

1 demo9 min read

The live demo from Behavior Trees.

No.26AI & navigation

Behavior Trees

Behavior Trees are the hierarchical AI architecture behind enemies in Halo, The Last of Us, and Unreal Engine — learn how Selectors, Sequences, and Leaf nodes combine to create believable, modular AI with a live interactive demo.

1 demo10 min read

The live demo from Finite State Machines.

No.13AI & navigation

Finite State Machines

Discover how Finite State Machines power enemy AI in games from Pac-Man to Dark Souls, and learn to build your own FSM with states, transitions, and hierarchical extensions.

1 demo8 min read

Esc