Teams

Every agent carries one team id - a plain integer you assign whatever meaning you like. Agents spawn on team 0. Teams do two things: they filter queries, so you can ask for "the enemy units in this rectangle" in one call, and they override avoidance between specific pairs of teams.

Nothing about teams is required. Leave every agent on team 0 and the simulation behaves exactly as it did before teams existed.

Setting and reading

Crowd->SetAgentsTeam(Squad, 1);        // whole selection
Crowd->SetAgentTeam(Index, 2);         // one agent
TArray<int32> Teams = Crowd->GetAgentTeams({});   // parallel to GetAgentPositions

Team ids can be any integer, including negatives. They are yours - the simulation never assigns meaning to a particular number, and changing an agent's team at runtime takes effect on the next query or tick.

Filtering queries

Every selection query takes a Teams array. Pass an empty array to get every agent, or a list of team ids to keep only agents on those teams:

TArray<int32> Everyone = Crowd->GetAgents({});           // all agents
TArray<int32> Enemies  = Crowd->GetAgents({2, 3});       // teams 2 and 3
TArray<int32> InBox    = Crowd->GetAgentsInRect(Rect, {2});
TArray<int32> Marquee  = Crowd->GetAgentsInScreenRect(PC, Min, Max, {1});

Because empty means "everything", adding the filter didn't change what any existing call returns.

Two queries exist specifically for this:

Function What it does
FilterAgents(Indices, Teams) Narrows an index array you already have. Preserves order and drops out-of-range entries.
GetNearestAgent(Center, Radius, Teams, OutDistance) Closest agent to a point, or -1. Radius <= 0 searches everywhere.

GetNearestAgent is the "find me a target" call - closest enemy to this unit, closest friendly to this healer.

Team avoidance rules

By default every agent pushes every other agent using the global Strength and Settle Push values (see Avoidance). A team rule overrides both for one pair of teams:

Crowd->SetTeamAvoidance(1, 2, Strength, SettlePushStrength);

Rules are symmetric - (1, 2) and (2, 1) are the same rule, and setting one overwrites the other. Passing the same team twice sets the rule within that team:

Crowd->SetTeamAvoidance(1, 1, 60.0f, -1.0f);   // team 1 packs tightly among itself

Each of the two values is read the same way:

Value Meaning
< 0 Follow the global slider. Not a snapshot - if you later change Strength in the details panel, this pair follows it.
0 Disable that push. The pair stops pushing each other in that situation.
> 0 Use this value instead of the global one.

Setting both to 0 makes the two teams ignore each other completely and walk straight through one another - useful for ghosts, spectators, or units on separate logical layers that happen to share the map.

Crowd->SetTeamAvoidance(1, 2,  0.0f,  0.0f);   // teams 1 and 2 pass through each other
Crowd->SetTeamAvoidance(1, 2,  0.0f, -1.0f);   // don't jostle in transit, still step aside when idle
Crowd->SetTeamAvoidance(1, 2, -1.0f,  0.0f);   // normal jostling; idle units hold their ground

Managing rules:

Function What it does
ClearTeamAvoidance(A, B) Drops that pair's rule; it falls back to the global values.
ClearAllTeamAvoidance() Drops every rule.
GetTeamAvoidance(A, B, OutStrength, OutSettlePushStrength) Returns false if no rule is set, in which case both outputs read -1.

Cost

With no rules set, the avoidance loop does no team work at all - it checks once that the rule table is empty and skips every lookup. Measured against the same build without teams, the difference is inside run-to-run noise.

With rules active the lookup happens only for pairs that are actually touching, not every pair the spatial grid visits, which costs a few percent of tick time in dense crowds.

Seeing teams

Tick Show Teams in the Debug section to draw a coloured dot above every agent, one colour per team id. The agents nearest the camera also get their team id as text, out to Team Label Distance; the text is capped to keep the overlay readable in big crowds, while the dots are drawn for everyone.