Guide

Game crowd simulation explained

Harbor Outpost's market square shipped with 120 individually pathfinding NPCs — each running full A* on a shared navmesh every 0.5 seconds, plus per-agent obstacle avoidance and animation blending. On a mid-tier console the plaza alone consumed 14 ms of AI CPU; crowds clumped at stall corners, walked through each other, and froze when the player triggered a cutscene. The refactor replaced per-agent pathfinding with a crowd simulation layer: background pedestrians follow precomputed flow fields, near-player agents use lightweight Reynolds steering, and only quest-critical NPCs retain full navmesh paths. AI CPU dropped to 1.9 ms for 400 visible agents; the square reads as a living market instead of a pathfinding stress test. Crowd simulation is the discipline of moving many agents believably at scale — trading individual precision for aggregate motion that sells density without bankrupting the frame budget. This guide covers steering behaviors, flow fields, navmesh density tiers, crowd LOD and impostors, threading and budgets, a Harbor Outpost worked example, an architecture decision table, common pitfalls, and a production checklist.

Crowds vs individual AI

Full companion AI and quest NPCs need unique goals, dialogue hooks, and reliable paths to specific destinations. Background crowds need the opposite: statistically plausible motion, local collision avoidance, and zero gameplay authority. Treat them as a separate subsystem with its own update rate, spatial partition, and rendering path.

  • Hero agents — full navmesh, behavior trees, interactable. Budget: tens per scene, not hundreds.
  • Mid-tier agents — simplified goals (walk to stall, idle, wander loop). Steering + coarse path. Budget: low hundreds.
  • Background fill — flow-field followers or baked animation cycles on splines. Budget: thousands as impostors or GPU particles.

Mixing tiers in one update loop guarantees either hero NPCs stutter or crowds look brain-dead. Harbor tags every agent with a CrowdTier enum at spawn and routes updates through tier-specific schedulers.

Reynolds steering behaviors

Craig Reynolds' boids model (1986) remains the foundation of real-time crowd local motion. Three rules produce flock-like movement without global coordination:

Separation

Steer away from neighbors within a personal radius (r_sep, typically 0.5–2 m). Prevents overlap and clipping. Weight this highest in dense urban scenes.

Alignment

Match the average heading of neighbors within r_align. Produces coherent streams on sidewalks and queue lines.

Cohesion

Steer toward the centroid of neighbors within r_coh. Keeps groups together without rigid formations.

Combine the three force vectors with tunable weights, add a seek or arrive force toward a local waypoint, and clamp maximum acceleration. Update neighbor lists via a uniform spatial hash or grid — O(n) all-pairs is fatal past 50 agents. Harbor runs steering at 10 Hz for mid-tier agents; positions interpolate on the render thread at 60 fps.

Obstacle avoidance extension

Cast 3–5 short rays in the velocity hemisphere. If a ray hits static geometry, apply a lateral repulsion force. Cheaper than navmesh raycasts per agent and sufficient for background tiers.

Flow fields and navmesh density

Steering alone does not know that the market exit is northeast. A flow field (vector field, navigation field) precomputes a direction vector per grid cell pointing toward a goal. All agents in a cell read the same vector — one Dijkstra or BFS pass per goal region, not one A* per agent.

Building a flow field

  1. Rasterize walkable navmesh into a 2D grid (cell size 0.5–2 m).
  2. Run multi-source BFS from goal cells (exits, attractions, queue heads).
  3. Store normalized direction per cell; mark blocked cells as zero vectors.
  4. Agents sample the field at their feet position and blend with steering forces.

Rebuild fields when doors open, barricades fall, or large dynamic obstacles move. Harbor caches fields per “district” and invalidates on a 2-second debounce when destructibles change.

Density layers on navmesh

Not every walkable polygon should carry crowds. Tag navmesh areas with density masks: sidewalk, plaza, restricted. Spawners only place agents on allowed masks; flow fields respect mask boundaries so crowds do not shortcut through shop interiors. Pair with level streaming so crowd spawners activate only when their cell loads.

Crowd LOD, impostors and rendering

Simulation cost is half the problem; draw calls and skinned mesh transforms are the other. Apply LOD to both logic and visuals:

  • Near (0–15 m) — full skeletal mesh, steering at 10 Hz, shadow casters.
  • Mid (15–40 m) — lower-poly LOD, baked animation cycles (walk/idle only), no finger IK.
  • Far (40–80 m) — billboard impostors or static mesh snapshots updated every 0.5 s.
  • Horizon (>80 m) — GPU particle sprites or omitted entirely.

Use object pooling for agent instances: recycle transforms and animation states instead of spawning actors. Harbor batches mid-tier agents into instanced draws sharing one material atlas — 200 agents in two draw calls.

Variety without unique assets

Shuffle hat/accessory mesh slots, tint clothing via vertex color, and offset animation phase randomly at spawn. Players perceive diversity from motion and silhouette, not unique rigs.

Threading, budgets and scheduling

Crowd updates belong on worker threads. A typical frame:

  1. Main thread — publish player position, dynamic obstacle list, tier promotion/demotion radii.
  2. Worker pool — rebuild spatial hash, run steering + flow sampling for mid/background tiers.
  3. Main thread — apply interpolated transforms, cull by frustum, submit instanced draws.

Cap total crowd AI time (Harbor: 2 ms on console, 3 ms on PC). If over budget, drop farthest agents to impostor tier or freeze background updates for one frame. Never let crowd sim block hero navmesh queries — separate thread pools and navmesh tiles.

Promotion and demotion

When the player approaches a background agent, promote it to mid-tier (enable steering). When a quest script needs a specific pedestrian, promote to hero tier and pin its navmesh path. Demote on distance or when the script releases control. Hysteresis radii (promote at 12 m, demote at 18 m) prevent tier flicker.

Worked example: Harbor Outpost market square

Before: 120 hero-tier NPCs in a 60×40 m plaza, each with 0.5 s A* refresh. Corner stalls became attractors; 30+ agents stacked with collision disabled. Cutscene freeze iterated all 120 controllers synchronously — 8 ms hitch.

Partition: 8 flow fields (one per stall cluster + exits). Grid resolution 1 m. 320 mid-tier spawners on sidewalk masks, 80 hero slots reserved for vendors and quest givers.

Steering tuning: separation weight 2.5, alignment 1.0, cohesion 0.3, max speed 1.4 m/s walk / 2.2 m/s jog. Personal radius 0.8 m.

Results: 400 visible agents at 1.9 ms AI CPU; cutscene freeze touches only hero tier (12 agents, <0.3 ms); player collision with crowds uses simplified capsule shells, not full skeletal physics.

Architecture decision table

Approach Best for Trade-offs
Per-agent navmesh (hero only) Quest NPCs, companions, combatants Does not scale past dozens
Reynolds steering + local waypoints Mid-density plazas, protest scenes, stadium crowds No global optimality; tuning-heavy
Flow fields Directed streams (exits, queues, parade routes) Rebuild cost when obstacles change
Baked spline cycles Stadium loops, airport concourses Static; obvious repetition up close
Offline simulation playback Cinematic crowds, sports audiences Zero reactivity; large memory per clip
Third-party middleware (e.g. Recast crowds) AAA density, engineers available for integration License cost, pipeline coupling

Common pitfalls

  • Running full pathfinding on background agents. The classic performance cliff; reserve A* for heroes.
  • No spatial partitioning for neighbor queries. O(n²) separation checks collapse past 100 agents.
  • Identical spawn positions. Agents stack before separation forces spread them — looks broken for the first second.
  • Ignoring animation root motion. Steering moves the capsule but feet slide; blend locomotion speed to actual velocity.
  • Crowds without density masks. Pedestrians shortcut through interiors or off ledges.
  • Freezing all agents on pause. Hero scripts need freeze; background tiers can keep idle animation for life.
  • Same update rate for all tiers. Background agents at 2 Hz are invisible; heroes at 2 Hz feel laggy.
  • Multiplayer authority gaps. Crowd motion must be deterministic or client-only cosmetic; never let background agents block networked interactions.

Production checklist

  • Define crowd tiers (hero / mid / background) and per-tier budgets.
  • Tag navmesh with density masks before placing spawners.
  • Precompute flow fields per district or goal cluster.
  • Implement spatial hash for steering neighbor queries.
  • Run crowd simulation on worker threads with a hard ms cap.
  • Apply visual LOD: skeletal, instanced, impostor, culled.
  • Pool agent instances; batch instanced draws by material.
  • Add promotion/demotion radii with hysteresis around the player.
  • Stagger spawn times and animation phases for variety.
  • Validate corner stalls and chokepoints with 2× peak density.
  • Profile cutscene and pause paths — freeze hero tier only.
  • Document steering weights per biome for level design handoff.

Key takeaways

  • Crowds are a separate subsystem — not “lots of regular NPCs.”
  • Steering + flow fields replace per-agent pathfinding for background motion.
  • Tiered simulation and rendering let you show hundreds of agents for single-digit milliseconds.
  • Density masks and streaming keep crowds where the design intends them.
  • Promotion on proximity bridges believable background fill and interactable hero agents.

Related reading