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, 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. |
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). |
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.
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. |
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. |
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. |
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. ResetSimulation clears every agent at once without raising this event, so reset your own slot bookkeeping alongside it. |
TryGetSpawnState(SwarmAgentHandle handle, out SwarmAgentSpawnState) |
Captures an agent's prefab + profiles + radius so you can re-spawn an identical agent later. |
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, and nothing in the package consumes the value, so whatever 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 |
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.
Related¶
- Swarm Manager: Inspector setup and simulation tuning.
- SwarmTarget: the component-based way to add targets.
- Spawners: the built-in spawners call
TrySpawnAgentfor you. - Combat Integration: applying damage and reacting to agent death.