Skip to content

SwarmManager

namespace MassiveSwarmSystem.Runtime

The central component that owns the whole simulation: the agent data, the spatial grid, the fixed-step simulation loop, target tracking, grounding, and blocking. You place one SwarmManager in the scene (alongside a SwarmVisualManager) and everything else talks to it.

Setting it up in the Inspector

This page is the scripting view. For placing the manager, assigning a SwarmSettings asset, and tuning the simulation in the Inspector, see Swarm Manager.

Getting the manager

SwarmManager.Instance is the active manager, set on Awake, and when it isn't cached yet (for example in the Editor before play) it falls back to a scene search. It's null when no manager exists. TryGetManager wraps that lookup in a bool check, with an overload that logs an error when none is found. To test for a manager without triggering a scene search while the game runs, use Exists:

using MassiveSwarmSystem.Runtime;

if (SwarmManager.TryGetManager(out SwarmManager manager))
{
    int alive = manager.ActiveAgentCount;
}
Member Description
Instance The active manager, or null. Set on Awake; falls back to a scene search when not yet cached.
Exists true when a manager is cached, without scanning the scene. In the Editor outside Play mode it does scan the scene, so an inspector gets a true answer.
TryGetManager(out SwarmManager manager) true if an active manager exists. Silent.
TryGetManager(out SwarmManager manager, bool logError) Same, but logs an error when none exists.
ActiveAgentCount How many agents are currently alive.
Capacity Maximum agents (from SwarmSettings).

Hold an agent with a handle, not a slot index

Per-agent methods take a SwarmAgentHandle, a small stable reference you get back from TrySpawnAgent. It keeps pointing at the same agent after other agents despawn, and it stops resolving once its own agent is gone, so a stale handle fails safely instead of acting on whoever later fills the slot. A raw int slot (such as the index a bulk reader hands you) is good only for the current step: a despawn swaps the last agent into the freed slot. Turn a slot into a handle with GetAgentHandle(slot) when you need to keep the reference. A default handle is invalid and never resolves; handle.IsValid reports whether it was ever minted.

Spawning agents

TrySpawnAgent adds one agent and returns false if the swarm is at capacity (or not set up). The position is automatically grounded using the manager's grounding settings. The simulation self-initializes on the first spawn, so you don't have to call Initialize() yourself.

// Spawn a ring of agents using a configured archetype.
for (int i = 0; i < 200; i++)
{
    float a = i / 200f * Mathf.PI * 2f;
    Vector3 pos = center + new Vector3(Mathf.Cos(a), 0f, Mathf.Sin(a)) * radius;

    if (!manager.TrySpawnAgent(pos, Vector3.forward, archetype, out SwarmAgentHandle handle))
    {
        break; // at capacity
    }
}
Overload Use it when
TrySpawnAgent(pos, forward) Quick spawn with the default radius and profiles.
TrySpawnAgent(pos, forward, out SwarmAgentHandle handle) Same, but you need a handle to the new agent.
TrySpawnAgent(pos, forward, SwarmBehaviorProfile) Override the behavior profile.
TrySpawnAgent(pos, forward, SwarmBehaviorProfile, SwarmMovementProfile) Override both profiles.
TrySpawnAgent(pos, forward, SwarmBehaviorProfile, SwarmMovementProfile, out SwarmAgentHandle handle) Both profiles, and capture a handle.
TrySpawnAgent(pos, forward, SwarmAgentArchetype, out SwarmAgentHandle handle) Spawn from an archetype (prefab + radius + profiles + attack profile). When the archetype has no prefab, the SwarmVisualManager's Fallback Agent Prefab (the built-in MSS Default Agent) is used; spawning only fails if neither the archetype nor the fallback provides a prefab.
TrySpawnAgent(in SwarmAgentSpawnState, pos, forward, out SwarmAgentHandle handle) Re-spawn from a state captured with TryGetSpawnState (see below).

SwarmBehaviorProfile needs a second using directive

The manager, the handle, the archetype and SwarmMovementProfile all live in MassiveSwarmSystem.Runtime, but SwarmBehaviorProfile sits in MassiveSwarmSystem.Runtime.Behaviors. Add using MassiveSwarmSystem.Runtime.Behaviors; for the profile overloads, or the type will not resolve.

Despawning agents

manager.DespawnAgent(handle);              // remove immediately
manager.DespawnAgentAsDying(handle, 1.5f); // play a 1.5s dissolve, then pool the visual
Member Description
DespawnAgent(SwarmAgentHandle handle) Frees the agent's slot immediately. Returns false for a stale handle.
DespawnAgentAsDying(SwarmAgentHandle handle, float dissolveDuration) Frees the slot now but keeps the visual on screen, writing a dissolve amount for dissolveDuration seconds before pooling it. Falls back to an immediate release when the visual can't dissolve (no VAT, no pool).
DespawnAgent(SwarmAgentHandle handle, SwarmDespawnReason reason) The same, and says why. The reason reaches anything listening to AgentDespawnedEvents, which is how a kill and a spawner trimming its cap stop looking identical.
DespawnAgentAsDying(SwarmAgentHandle handle, float dissolveDuration, SwarmDespawnReason reason) Dissolve, with a reason.

SwarmDespawnReason is Unspecified (the default when you do not pass one), Killed, ReachedGoal, LeftTheArea, CapacityReduced and Recycled. The Pressure Recycler raises Recycled itself when it moves an agent, and a despawn call never passes it.

Despawning is safe by handle

A despawn swaps the last agent into the freed slot, but handles ride along: DespawnAgent resolves the handle to the agent's current slot, so a despawn never hits the wrong agent. If you instead track agents by raw slot index, subscribe to AgentSlotChanged to mirror the swap (see Advanced), or sweep the dense range from the highest slot down.

0 1 2 3 4 5 Before A B C D E F despawned the last agent moves into the freed slot After A F C D E free A handle to F stays valid and rides the move. A stored slot index 1 used to mean B. Now it quietly means F.
The agent array stays dense, so a despawn in the middle is filled by moving the last agent down. This is why the API hands you a handle: it resolves to wherever the agent currently lives. A raw slot index you stored a frame ago is not wrong in an obvious way, it just refers to a different agent now, which is the kind of bug that shows up as one enemy taking another's damage.

Moving agents and applying forces

Use these to teleport agents or push them around (knockback, explosions, wind):

// Knockback away from a blast.
Vector3 dir = (agentPosition - blastCenter).normalized;
manager.AddAgentImpulse(handle, dir * 12f, externalControlDuration: 0.3f);

// Teleport an agent and reset its motion.
manager.RelocateAgent(handle, spawnPoint.position, spawnPoint.forward);
Member Description
RelocateAgent(SwarmAgentHandle handle, Vector3 position, Vector3 forward) Teleports the agent (grounded), clears velocity, steering, and external forces, and wakes it if dormant. A zero forward keeps the current facing.
AddAgentImpulse(SwarmAgentHandle handle, Vector3 impulseVelocity) Applies an instantaneous velocity change (ForceMode.Impulse) using the default external-control duration.
AddAgentImpulse(SwarmAgentHandle handle, Vector3 impulseVelocity, float externalControlDuration) Same, with an explicit duration the external velocity overrides steering.
AddAgentForce(SwarmAgentHandle handle, Vector3 force) Applies a continuous force (ForceMode.Force).
AddAgentForce(SwarmAgentHandle handle, Vector3 force, ForceMode forceMode) Choose the force mode.
AddAgentForce(SwarmAgentHandle handle, Vector3 force, ForceMode forceMode, float externalControlDuration) Full control. A negative duration uses SwarmSettings.DefaultExternalControlDuration; the resulting velocity is clamped to MaxExternalVelocity.

All of these return false for a stale handle, so a despawned agent quietly drops the call instead of moving another agent.

Reading agent state

Read a tracked agent's live simulation pose by handle. Each getter resolves the handle first, so a despawned agent returns false instead of handing back whoever reused its slot.

if (manager.TryGetAgentPosition(handle, out Vector3 pos))
{
    healthBar.transform.position = pos + Vector3.up * 2f;
}
Member Description
TryGetAgentPosition(SwarmAgentHandle handle, out Vector3 position) Current simulation position.
TryGetAgentForward(SwarmAgentHandle handle, out Vector3 forward) Current facing direction (normalized).
TryGetAgentVelocity(SwarmAgentHandle handle, out Vector3 velocity) Current velocity.
TryGetAgentRadius(SwarmAgentHandle handle, out float radius) Collision radius.
TryGetAgentDormant(SwarmAgentHandle handle, out bool isDormant) Whether the agent has settled and stopped steering.
TryGetAgentSurrendered(SwarmAgentHandle handle, out bool isSurrendered) Whether the agent has given up pushing under crowd pressure and is being carried by the mass. Separate from dormant: a surrendered agent is still awake to contacts and attacks, and can be both at once.
TryGetAgentImportanceLod(SwarmAgentHandle handle, out SwarmImportanceLod lod) Which LOD tier the agent is simulating at.
TryGetAgentTargetAwareness(SwarmAgentHandle handle, out float awareness) How aware it is of a target this step, from 0 to 1.
TryGetAgentRoll(SwarmAgentHandle handle, out float roll) The agent's roll, from 0 to 1, fixed for its life. It is the value every Shared field on its profiles reads, so use it to make your own per-agent values agree with them, such as more health for the fast ones.
TryGetAgentVisualPose(SwarmAgentHandle handle, out Vector3 position, out Quaternion rotation) The pose the agent is actually drawn at, after interpolation and render smoothing, rather than the raw simulation pose the getters above return. Use it to pin a health bar or a UI marker so it does not shimmer against the model. Also returns false when the agent has no visual bound to it.

Each returns false for a stale or invalid handle.

To process every agent in one pass (your own rendering, spatial queries, analytics) read the whole active range at once instead of resolving a handle per agent:

ReadOnlySpan<Vector3> positions = manager.ReadAgentPositions();
for (int i = 0; i < positions.Length; i++)
{
    // positions[i] is one live agent
}
Member Description
ReadAgentPositions() All live agent positions, in slot order.
ReadAgentForwards() All live facing directions.
ReadAgentVelocities() All live velocities.
ReadAgentRadii() All live collision radii.

Each returns a read-only, zero-copy ReadOnlySpan over the simulation's own array: no allocation, no per-agent lookup. The four spans share an index, so positions[i] and forwards[i] are the same agent. Two rules come with that speed: the span is valid only for the current frame, so don't store it; and slot order is transient, because a despawn swaps the last agent into the freed slot, so an index is not a stable identity between frames. Hold a SwarmAgentHandle when you need to follow one agent over time.

Targets

You normally don't call these. Adding a SwarmTarget component registers and unregisters automatically. Use the manager directly only for code-driven targets you don't want to represent with a component.

Member Description
RegisterTarget(Transform target) Registers a transform as a target. Returns true if it's now registered (idempotent).
RegisterTarget(Transform target, float priority, float selectionRadius) The overload written for exactly this case: register a transform and hand it a Priority and a Selection Radius without a component. Priority is clamped to at least SwarmManager.MinPriority (0.01), and a Selection Radius of 0 means unlimited reach. If the transform or a parent does carry a SwarmTarget, that component's own values win.
UnregisterTarget(Transform target) Removes a registered target.
ClearTargets() Removes all targets and forces every agent to retarget.
TryGetRegisteredTarget(int index, out Transform target) Reads a registered target by its position in the registered list.
TryGetActiveTarget(int targetSlot, out Transform target) Reads the target sitting in an active slot. Slot indices are the ones the simulation uses, so this is the lookup you want when working from an agent's target index or from per-target diagnostics.
FindActiveSlotForRegisteredTarget(int registeredIndex) The slot a registered target occupies, or -1 when the swarm isn't chasing it right now.
GetActiveTargetRegisteredIndex(int targetSlot) The registered-list index sitting in a slot, or -1 when the slot is empty. The inverse of the lookup above.
IsRegisteredTargetEngaged(int registeredIndex) Whether enough agents stand within the engagement radius for this target to hold an active slot however far it sits from the swarm centre. Always false while Enable Target Engagement Promotion is off.
RegisteredTargetCount / ActiveTargetCount / MaxActiveTargetCount How many targets are registered, how many are currently being chased, and the cap.
TargetTransform / TargetPosition The first registered, non-null target and its position.

Naming a target across frames

Unregistering removes from the middle of the registered list, so every later entry moves down one place and an index you stored yesterday can name today's neighbour. SwarmTargetHandle is the fix: it names the registration itself, and once that target unregisters it resolves to nothing instead of to whoever took its place. Use it for anything you keep, compare later, or hand to other code. The indexes and slots above stay the right tool inside a single frame.

Member Description
GetTargetHandle(SwarmTarget target) / GetTargetHandle(Transform target) A handle for a registered target, or default when this manager doesn't have it registered. The Transform overload is the one for targets you registered without a component.
GetActiveTargetHandle(int targetSlot) The handle of the target in a slot, or default for an empty slot.
GetRegisteredTargetHandle(int registeredIndex) The handle for a position in the registered list.
TryGetTargetTransform(SwarmTargetHandle handle, out Transform target) Reads the transform a handle names. false once that target has unregistered.
TryGetTarget(SwarmTargetHandle handle, out SwarmTarget target) The same for the component, so false also for a live target registered as a bare transform.
TryGetActiveTargetSlot(SwarmTargetHandle handle, out int targetSlot) The slot a handle's target holds right now. false while it is registered but not one of the targets being chased, which is an ordinary state rather than an error.
IsTargetRegistered(SwarmTargetHandle handle) Whether the handle still names a live registration.

A SwarmTarget component carries its own handle in SwarmTarget.Handle, so you rarely need GetTargetHandle unless you are working from a transform. Resolving a handle costs a couple of array reads and allocates nothing, so there is no reason to cache what it resolves to.

Telling agents something happened

Agents carrying a Perception Profile chase only what they have noticed, so game code needs a way to make them notice. A gunshot, an alarm, a hit they took from behind. The Swarm Noise Emitter is the drop-in component for it; these are what it runs on.

// Every agent within 25 units learns the player is worth chasing.
int reached = manager.EmitStimulus(muzzlePosition, 25f, playerTransform);
Member Description
EmitStimulus(Vector3 origin, float radius, Transform sourceTarget) Alerts every agent within radius of origin to a registered target, at full strength. Returns how many it reached. With a null source it is a noise about its own spot, covered below.
EmitStimulus(..., float strength) The same with a strength from 0 to 1. Below 1 the agent gives up sooner, because awareness fades at the profile's Forget Time from wherever it starts.
EmitStimulus(..., float strength, NoiseOcclusionMode wallOcclusion) The same again, with walls either ignoring the noise, quietening it, or stopping it. Both wall settings read the Surface Flow field, so they need Surface Flow Navigation on at Smooth or Eikonal quality.
NotifyAgentStimulus(SwarmAgentHandle handle, Transform sourceTarget) Makes one agent aware of a target, for a damage hook or a scripted reveal. false for a stale handle or an agent with no profile.
NotifyAgentStimulus(SwarmAgentHandle handle, Transform sourceTarget, float strength) The same with a strength from 0 to 1.

The damage hook is already wired in the samples. SwarmAgentCombatTarget calls the first of those two whenever a hit lands, using the attacker the damage context carries, so shooting an agent makes it notice the shooter without any code from you. See Combat Integration for the tickbox that turns it off.

Three things decide whether a call does anything.

Only agents with a Perception Profile respond. An agent without one already knows where every target is, so there is nothing to tell it, and a scene with no profile anywhere returns 0 from every call.

sourceTarget is normally a target holding an active slot, because what an agent knows is a set of bits over those slots. Pass null, or a transform outside the active set, and the call becomes a noise about its own spot instead. An agent with no target it sees or remembers walks to origin and forgets it after its Forget Time. That is how a thrown bottle or an alarm works. It needs Searching on in the agent's Perception Profile, and with Searching off the noise moves nobody. An agent that already knows a target keeps it, and the noise waits until that target is forgotten. A noise has no target to read a Surface Flow field for, so walls never stop it, whichever wallOcclusion you pass.

origin is where the agent will go looking. It is treated as the last place the target was seen, so pass the muzzle, the impact point or the alarm rather than the target's own position when those differ.

Call these from anywhere in your own code. They write per-agent state and read the spatial grid the last simulation step built, so agent positions are at most one step old, and the cost of radius grows with its square because the query walks the grid cells it covers.

Simulation events

The swarm tells you when things happen in it: an agent calling out to the ones around it, an agent leaving. You read those without touching the simulation, and without a component per agent.

using MassiveSwarmSystem.Runtime;
using UnityEngine;

public class GroanOnAlert : MonoBehaviour
{
    private SwarmEventReader<SwarmAlertCallEvent> m_reader;

    private void Update()
    {
        if (!SwarmManager.TryGetManager(out SwarmManager manager, false)) { return; }

        // Keeps your reader, and hands you a new one if the manager was replaced by a scene load.
        m_reader = manager.AlertCallEvents.KeepReader(m_reader);

        if (!m_reader.TryRead(out SwarmEventBatch<SwarmAlertCallEvent> batch)) { return; }

        foreach (SwarmAlertCallEvent call in batch)
        {
            Debug.Log($"An agent called out from {call.Origin}");
        }
    }
}

Three ways in, and which one you want

Events are raised inside the simulation step, in FixedUpdate, and dozens can land on one step. None of these run your code in there.

A reader, above, is the default. You ask once a frame, at the point in your own frame you choose, and you get the whole batch. Two scripts can read the same stream without taking events from each other, because each reader keeps its own place. A reader that stops asking for a while, because you disabled the listener or the game sat on a menu, is still owed everything it missed and gets all of it on the next read. SkipToLatest() drops that backlog, so a crowd that called out while nobody was listening does not arrive at once on your first frame back.

Subscribe to the batch when you want no per-frame code at all. Your method is called once a frame, and only on the frames that produced something:

manager.AlertCallEvents.BatchReceived += OnAlertCalls;   // remember to -= in OnDisable

private void OnAlertCalls(in SwarmEventBatch<SwarmAlertCallEvent> batch)
{
    // the nearest of them, the loudest, how many there were
}

Subscribe per event when each one is its own little job and you do not care what else arrived:

manager.AlertCallEvents.Received += OnAlertCall;   // remember to -= in OnDisable

private void OnAlertCall(in SwarmAlertCallEvent call) { ... }

Pick the batch over the per-event one whenever the answer is about the frame rather than about each event. One groan from the nearest of forty calls is that kind of answer, and a method called forty times cannot give it: each call arrives without knowing whether a nearer one is still coming. The batch is also the only place DroppedCount exists.

Both subscriptions cost you the same two things, and both are why the reader is the default: you have to unsubscribe when your object goes away, and when your method runs relative to other scripts is a Script Execution Order question. The batch is a ref struct and is only alive for the length of the call, so copy out what you need rather than storing it. batch.CopyTo(Span<T> destination) does that in one call and returns how many it wrote, which is the shorter of the batch and your span.

Forget the -= and the manager notices. A subscriber whose component has been destroyed is dropped the next time events go out, and the console gets one warning naming the component and the method. Treat that as a net rather than a substitute: it recognises a method you subscribed by name, not a lambda, and until it fires your destroyed object is still being called and still being kept alive.

Read and subscribe from the main thread. The manager writes these from FixedUpdate and nothing here takes a lock.

The streams

Member Carries
AlertCallEvents SwarmAlertCallEvent: which agent called (Caller), where it stood (Origin), what it called about (Target) and where that was (TargetPosition).
AgentDespawnedEvents SwarmAgentDespawnedEvent: which agent left (Agent), where it stood (Position), and why (Reason).
AgentDormancyEvents SwarmAgentDormancyEvent: which agent changed (Agent), where it stood (Position), and whether it fell asleep or woke (Dormant).

Every event also carries a Header with FixedStep, the simulation step that produced it, and Sequence, its place in the order of everything the manager has raised. Compare FixedStep against manager.FixedStepIndex to see how old an event is.

Agents and targets are named by handle, never by index, so an event still names the right one after the swarm has churned. A despawn event is the exception worth knowing: the agent is gone by the time you read it, so its handle no longer resolves. Compare it against a handle you were holding rather than looking it up, and take the position from the payload.

Listening to two streams at once is worth one caveat. Your method is called a stream at a time, every event of one kind before any of the next, so the calls do not interleave in the order the events happened. Departures go last on purpose. A despawn is what tells you to forget a handle, so anything else that agent did on the same frame reaches you before the forget does. When you need the real order across kinds, sort by Header.Sequence, which counts every event of every kind in the order the manager raised them.

Dormancy events arrive in bulk

A player rounding a corner can wake most of a sleeping crowd on one step, so a single batch can be close to the size of the swarm. Your handler runs once per event, so anything it instantiates, it instantiates that many times in one frame. Treat the batch as a batch: count it, sample it, or act on the few you care about.

If you keep your own set of who is asleep, drain AgentDespawnedEvents too. An agent that is removed while dormant does not report waking up, it reports leaving, so a set built from dormancy events alone grows stale entries. That includes agents moved by the Pressure Recycler, which report a despawn with the reason Recycled.

A wake is reported when it is noticed, not the instant it becomes true. Sleeping agents are re-checked on a rotating schedule to spread the cost, so a wake event's FixedStep can be up to Dormancy Wake Recheck Interval steps after the way actually opened. Falling asleep is reported on the step it happens.

Nothing is recorded until you ask

A stream nobody reads costs the simulation a null check and no memory at all. The buffer is created by your first reader or subscriber, so a scene that ignores a kind of event pays nothing for it.

The buffer sizes itself

There is no capacity to get right. The buffer starts small and grows, at the end of the frame, to whatever the busiest frame turned out to need. The cost is that the first surge past the current size loses its overflow, and says exactly how many:

if (batch.DroppedCount > 0)
{
    Debug.LogWarning($"Missed {batch.DroppedCount} events");
}

After that it fits and stops dropping. DroppedCount reading above 0 for more than the first surge means either you are not reading every frame, or you lowered CapacityCeiling below what the scene produces. A simulation reset is not counted as a loss: those events are discarded on purpose and your reader skips them quietly.

DroppedCount lives on the batch, so a per-event subscriber has nowhere to see it. When a stream loses events and nothing attached to it could have noticed, one warning goes to the console with the number, once per stream.

Two knobs sit on top, and neither is needed for correct behaviour. Prewarm(capacity) pays the memory up front for a project that cannot lose even the first surge, and CapacityCeiling puts a roof on how far the buffer will grow.

Advanced

For deeper customization and diagnostics. Exact semantics are in IntelliSense.

Member Description
Settings Direct access to the SwarmSettings asset. To read per-agent state, use the handle getters and span readers above, since the dense arrays are internal so a stray write can't corrupt the simulation.
GetAgentHandle(int agentSlot) A stable SwarmAgentHandle for the agent at a transient slot, for example while sweeping the active range with a span reader. Returns default for an empty slot.
TryGetAgentSlot(SwarmAgentHandle handle, out int agentSlot) Resolves a handle back to its current slot. false once the agent has despawned or been recycled.
AgentSlotChanged Event raised after a despawn or pressure-recycle, for code that tracks agents by raw slot. The SwarmAgentSlotChange argument carries the removed slot, the previous last slot, whether a swap-back happened, and (on a recycle) the respawned slot. Code holding a handle can ignore it. A reset does not raise it for each agent: listen to SimulationReset for that.
SimulationReset Event raised when the whole swarm is dropped at once, by ResetSimulation() or by calling Initialize() again on a running manager. No despawn or AgentSlotChanged goes out for those agents, so clear any raw-slot bookkeeping here. It fires after the clear, when ActiveAgentCount is 0. SwarmSpawnerDirector already listens and forgets the agents it owned.
TryGetSpawnState(SwarmAgentHandle handle, out SwarmAgentSpawnState) Captures an agent's prefab + profiles + radius so you can re-spawn an identical agent later. The state carries AgentPrefab, BehaviorProfile, MovementProfile, AgentRadius, PushResistance, AttackProfile and PerceptionProfile, and has a constructor for each: pass six arguments and the agent spawns with no perception, pass seven to give it a profile.
Initialize() Allocates buffers and binds the visual manager. Called automatically in Awake when Initialize On Awake is enabled (the default); falls back to the first spawn when that option is off. Call it manually only to pre-allocate.
ResetSimulation() Releases every agent and clears registered profiles, back to an empty swarm.
LOD & dormancy counts FullTierAgentCount, ReducedTierAgentCount, CheapTierAgentCount, DormantAgentCount, SurrenderedAgentCount, used by the stats overlay. The last two overlap rather than partition the swarm: an agent can be surrendered, dormant, or both.

Reading crowd pressure

How hard the swarm is pressing on a target, as a number your game code can act on. Camera shake as the player gets surrounded, a "you are being overwhelmed" warning, a door that gives way under enough bodies. The manager already computes this for its own bookkeeping, so reading it costs nothing extra and beats rolling your own OverlapSphere.

Member Description
TryGetTargetCrowdBlocking(out Vector3 normal, out int agentCount) Pressure on the primary target. normal points the way the crowd is pushing, and its length carries the strength. Returns false when nobody is pressing
TryGetTargetCrowdBlocking(int targetIndex, out Vector3 normal, out int agentCount) The same for one target slot. Returns false for an out-of-range index or an unpressed target
TryGetCrowdBlocking(Vector3 position, float radius, out Vector3 normal, out int agentCount) Measures around any world position rather than a registered target. Use it for a door, a barricade, or a spot the player has not reached yet. This overload measures on demand, so unlike the two above it is not free
TargetCrowdBlocking, TargetCrowdBlockingAgentCount The primary target's last values as plain properties, when you do not want the Try pattern
if (m_manager.TryGetTargetCrowdBlocking(out Vector3 pressure, out int pressing) && pressing > 12)
{
    m_cameraShake.Nudge(pressure.magnitude);
}

It is off by default, and it does not move anyone

Enable Target Crowd Blocking on SwarmSettings ships off, and all three methods return false while it is. Turn it on when you want the readout. It only measures: no agent moves differently because of it. The swarm never reads the value. The only caller in the package is the sample DemoPlayerController, through TryGetCrowdBlocking, so whatever else you build on top of it is the whole feature.

Driving Surface Flow from code

The Surface Flow Field has a runtime surface too, for toggling navigation per area, rebaking after the level changes shape, or sampling a direction for something that is not an agent.

Member Description
EnableSurfaceFlowNavigation Get or set the toggle. Setting it does not bake anything by itself
HasActiveSurfaceFlowNavigation Whether navigation is actually running, which needs both the toggle and a usable bake
RebuildSurfaceFlowNavigationNow() Tears the whole thing down and rebuilds it: map, target slots, and an immediate bake of every target field. This is the call for after procedural generation finishes or a wall comes down. It blocks while it runs, so keep it off the frame budget of normal play
TrySampleSurfaceFlow(int targetIndex, Vector3 worldPosition, out SurfaceFlowFieldSample sample) Reads the baked field at a position. The sample carries Status, CellIndex and Direction. Handy for a telegraph arrow or a companion that should route like the swarm
TryGetSurfaceFlowTargetDiagnostics(int targetIndex, out bool hasValidBake, out int reachableCount, out float bakeAge, out byte lastFailureReason) Why a target's routing is or is not working, and how stale its bake is in seconds
SurfaceFlowFallbackModeOverride A nullable override for the fallback mode, so you can change it for one room and set it back to null to return to the SwarmSettings value
SurfaceFlowFallbackMode The mode actually in force: the override when set, otherwise the settings value
HasSerializedSurfaceFlowBake, SerializedSurfaceFlowWalkableCellCount, SerializedSurfaceFlowLastBakeMilliseconds Whether a bake was saved with the scene, how many walkable cells it holds, and how long it took. Useful for a build-time check that someone remembered to bake

Flow modifiers from code

A Swarm Flow Modifier is the component behind doors, barricades and pushable props. These members read what the manager knows about the registered ones.

Member Description
FlowModifierCount How many flow modifiers are registered.
FlowBlockedAgentCount How many agents were pressed against a flow modifier's closed cells on the last simulation step.
FlowModifierLayerVersion A uint that changes whenever a modifier moves, opens, closes or changes cost. Store it and compare to see whether the layer changed.
TryGetAgentFlowBlocker(SwarmAgentHandle handle, out SwarmFlowModifier modifier) The flow modifier the agent was pressed against on the last simulation step. false for a stale handle or an agent pressed against none.
IsAnyAgentInside(SwarmFlowModifier modifier, float padding = 0f) Whether any agent's body overlaps the modifier's box seen from above, enabled or not. padding widens each body by that many units. A door can use it to wait for its frame to clear before closing. It walks every agent, so call it when a decision needs it, not every frame for every door.

Quick reference in your IDE

Key members carry a short XML summary, so hovering them in Visual Studio or Rider shows a one-line description. This page has the full detail.