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
- Rasterize walkable navmesh into a 2D grid (cell size 0.5–2 m).
- Run multi-source BFS from goal cells (exits, attractions, queue heads).
- Store normalized direction per cell; mark blocked cells as zero vectors.
- 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:
- Main thread — publish player position, dynamic obstacle list, tier promotion/demotion radii.
- Worker pool — rebuild spatial hash, run steering + flow sampling for mid/background tiers.
- 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
- Game open world design explained — POI density and when crowds sell the world
- Game level streaming explained — activate spawners per loaded cell
- Game pathfinding and navmesh explained — hero-tier paths and walkable masks
- Game LOD explained — reduce mesh and logic cost at distance