Indices & Stable IDs
Every agent in a crowd has two handles: an index and a stable id. Knowing which to use, and when, is the single most important thing to get right when scripting against the crowd.
Index - fast, but not stable
An index is the agent's slot in the crowd's arrays (0 … GetAgentCount()-1). All the
per-agent getters and order functions take indices, and the query functions
(GetAgentsInRect, GetAgentsInRadius, GetAgentsInScreenRect) return them.
Indices are not stable across removals. When an agent is removed, the simulation fills its slot with the last agent (swap-and-pop), so some other agent's index changes. An index is only guaranteed valid within the same tick you obtained it.
TIP
For the common loop - select this frame → order this frame → render this frame - indices are exactly right and you never need ids. Reach for ids only when you hold a reference to a specific agent across ticks.
Stable id - durable, for tracking over time
A stable id is unique per crowd actor, never reused (not even across
ResetSimulation), and fixed for the agent's whole lifetime. Use ids whenever you need
to remember a particular agent between frames - a unit's health bar, a selection that
persists, a "go here when you arrive" follow-up.
Translating between the two
// Single:
int32 Id = Crowd->GetAgentId(Index); // -1 if Index out of range
int32 Index = Crowd->GetAgentIndex(Id); // -1 if no such agent (e.g. removed)
// Batch (one call for a whole selection):
TArray<int32> Ids = Crowd->GetAgentsIds(Indices); // -1 per out-of-range entry
TArray<int32> Indices = Crowd->GetAgentsIndices(Ids); // -1 per unknown id
A returned -1 means the agent is gone - the standard way to detect that a tracked unit was removed.
The pattern
Selection time: indices ──GetAgentsIds──► store ids (durable handle)
│
Each later use: stored ids ──GetAgentsIndices──► indices (re-resolve)
│
▼
drop the -1s (removed), use the rest
So: convert to ids when you store, convert back to indices when you act.
Events speak ids, broadcast as indices
The movement events (OnAgentsReachedGoal, OnAgentsStuck,
OnAgentsOrdersCanceled) are buffered internally as stable ids - because an event can
outlive the tick that produced it while indices reshuffle - but the actor resolves them to
current indices before broadcasting, so your handler receives ready-to-use indices.
Agents removed between the event and the broadcast are simply dropped from the batch.
The spawn/remove events (OnAgentsSpawned, OnAgentsRemoved) also deliver indices, and
fire before the tick that applies the change, so those indices are still valid in the
handler (see Events for the exact timing).
Custom per-agent data
The crowd doesn't store arbitrary gameplay data per agent. The intended pattern is to key
your own Map<int32, ...> by the agent's stable id - ids survive the swap-and-pop
shuffles that would scramble an index-keyed map.