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, 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) …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).

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.
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.

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 — 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).
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 index.
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 — the dense arrays are internal so a stray write can't corrupt the simulation.
GetAgentHandle(int slot) 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 slot) 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 — used by the stats overlay.

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.