Skip to content

SwarmAgent

SwarmAgent is the thin MonoBehaviour that lives on every agent visual GameObject. Use it to apply knockback, trigger a hit flash, or read which simulation slot the visual currently occupies.


Overview

Each time the swarm spawns a new agent, it pulls a pooled GameObject from SwarmVisualManager, attaches a SwarmAgent to it (or reuses the existing one), and binds it to the owning SwarmManager and that agent's current simulation slot. When the agent dies or is recycled, the visual is released back to the pool and the component unbinds — its Manager becomes null.

SwarmAgent is a temporary handle, not a persistent identity. The same component (on the same GameObject) will be bound to a completely different simulation slot after a despawn-and-respawn cycle.

Do not cache SwarmAgent references across frames

A reference you grabbed when an agent spawned becomes invalid the moment that agent despawns. The component silently unbinds — its Manager becomes null. Always resolve the SwarmAgent in the same call that triggered your reaction (e.g. OnCollisionEnter, OnTriggerEnter, OnDamageTaken), then discard it. Do not store it in a field that outlives that call. When you do need to keep a reference to one agent over time, grab its Handle and act through SwarmManager — see Handle below.


Public API Reference

Properties

Manager

public SwarmManager Manager { get; }

The SwarmManager that owns this agent's simulation slot. null when the visual is in the pool (unbound).

Use Manager when you need to call SwarmManager methods directly and have a SwarmAgent reference in hand. All force methods on SwarmAgent already check that Manager is non-null before forwarding.

Handle

public SwarmAgentHandle Handle { get; }

A stable SwarmAgentHandle for this agent's simulation slot, safe to cache across despawns. Returns default (invalid) while the visual is pooled. Pass it to the per-agent SwarmManager methods (AddAgentForce, DespawnAgent, RelocateAgent, …) instead of holding the SwarmAgent component — the handle keeps pointing at this agent as others despawn, and quietly stops resolving once this agent is gone.

AgentCollider

public Collider AgentCollider { get; }

The collider that SwarmVisualManager either found on the prefab or created automatically at spawn time. Use this when you need the exact collider for physics queries. Returns null when the visual has no collider (rare; the system adds one by default).


Force Methods

All force methods return bool: true means the force was accepted and applied, false means the agent is unbound or invalid. A false return is silent; no exception is thrown.

AddForce

public bool AddForce(Vector3 force)
public bool AddForce(Vector3 force, ForceMode forceMode)
public bool AddForce(Vector3 force, ForceMode forceMode, float externalControlDuration)

Applies a continuous force to the agent, using Unity's standard ForceMode semantics (Force, Acceleration, Impulse, VelocityChange). The force is forwarded to SwarmManager.AddAgentForce.

externalControlDuration sets how long (in seconds) external forces keep exclusive velocity control before swarm steering resumes. A negative value falls back to the manager's Default External Control Duration (from SwarmSettings); 0 resumes steering immediately. The shorter overloads that omit the parameter behave like passing 0.

Use AddForce for sustained pushback (e.g. a wind zone applying force each frame) or when you want Unity physics force semantics.

AddImpulse

public bool AddImpulse(Vector3 impulseVelocity)
public bool AddImpulse(Vector3 impulseVelocity, float externalControlDuration)

Applies a one-shot velocity impulse for knockback from a single hit. The impulse matches AddForce(impulseVelocity, ForceMode.Impulse), with one difference in the default: omit externalControlDuration here and it falls back to the manager's Default External Control Duration (from SwarmSettings), whereas the no-duration AddForce overloads resume steering immediately.

externalControlDuration sets how long external control overrides steering before it resumes: a negative value (or omitting it) uses the Default External Control Duration; 0 resumes steering immediately.


Tint Methods

The tint system lets multiple simultaneous sources (damage flash, selection highlight, freeze effect) each own their own color contribution. The agent renders whichever source has the highest effective intensity (intensity × alpha). The stack holds up to four contributions; if full, the weakest is evicted.

Use a stable, unique string as sourceId per system or effect type (e.g. "damage", "freeze"). Reusing the same sourceId replaces that slot rather than adding a second entry.

PulseTint

public void PulseTint(string sourceId, Color color, float duration, AnimationCurve curve = null, bool useUnscaledTime = false)

Starts a tint that fades from full intensity to zero over duration seconds, then clears itself automatically. The fade is linear unless you supply an AnimationCurve — the curve maps normalized time [0,1] to intensity [0,1].

useUnscaledTime: when true, the pulse animates even while Time.timeScale == 0 (e.g. during a pause menu). Defaults to false.

Calling PulseTint again with the same sourceId before the first pulse finishes restarts the pulse from full intensity.

SetTint

public void SetTint(string sourceId, Color tintColor, float intensity)

Sets a persistent tint contribution (no auto-fade). Call this every frame to drive an animated tint from your own curve or script. Clear it explicitly with ClearTint when done.

intensity is clamped to [0, 1].

ClearTint

public void ClearTint(string sourceId)

Cancels any running PulseTint and clears the slot for that sourceId.

ClearAllTints

public void ClearAllTints()

Cancels all running pulses and clears the entire tint stack. Called automatically when the agent is recycled — no manual cleanup needed on despawn.

TryGetResolvedTint

public bool TryGetResolvedTint(out Color tintColor, out float intensity)

Returns the winning tint color and intensity — whichever source currently has the highest effective contribution. Returns false (and leaves outputs at defaults) when the stack is empty.

Use this when you have a secondary renderer (not managed by VAT) that needs to mirror the same tint envelope.


Scripting Recipes

Knockback on hit

An enemy hits a physics trigger on the player's weapon, and nearby agents get knocked back.

using MassiveSwarmSystem.Runtime;
using UnityEngine;

public class KnockbackOnHit : MonoBehaviour
{
    [SerializeField] private float m_impulseStrength = 8f;
    [SerializeField] private float m_externalControlDuration = 0.3f;

    private void OnCollisionEnter(Collision collision)
    {
        SwarmAgent agent = collision.gameObject.GetComponent<SwarmAgent>();
        if (agent == null)
        {
            return;
        }

        // Direction away from the collision point, flattened to the XZ plane.
        Vector3 contactPoint = collision.GetContact(0).point;
        Vector3 direction = agent.transform.position - contactPoint;
        direction.y = 0f;
        if (direction.sqrMagnitude < 0.0001f)
        {
            direction = -transform.forward;
        }

        agent.AddImpulse(direction.normalized * m_impulseStrength, m_externalControlDuration);
    }
}

Damage flash on hit

Play a quick red flash when an agent takes damage. DamageableBase — the base class of SwarmAgentCombatTarget (see Combat Integration) — raises an OnDamageTaken event. Subscribe to it and pulse a tint on the agent. The StarterKit DamageFlashFeedback component is built the same way.

using MassiveSwarmSystem.Runtime;
using MassiveSwarmSystem.Runtime.Combat;
using UnityEngine;

[RequireComponent(typeof(SwarmAgent))]
public sealed class AgentDamageFlash : MonoBehaviour
{
    [SerializeField] private DamageableBase m_damageable; // the SwarmAgentCombatTarget on this agent

    private SwarmAgent m_agent;

    private void Awake() => m_agent = GetComponent<SwarmAgent>();

    private void OnEnable()
    {
        if (m_damageable != null)
        {
            m_damageable.OnDamageTaken += HandleDamageTaken;
        }
    }

    private void OnDisable()
    {
        if (m_damageable != null)
        {
            m_damageable.OnDamageTaken -= HandleDamageTaken;
        }
    }

    private void HandleDamageTaken(float damage, SwarmDamageContext context)
    {
        m_agent.PulseTint("damage", Color.red, 0.2f);
    }
}

Practical Usage Guidance

SwarmAgent is the right tool when your code already has a reference to a specific visual GameObject — from a collision event, a raycast hit, or a damage callback on SwarmAgentCombatTarget. It forwards directly to the simulation without requiring you to manage agent indices manually.

For forces: AddImpulse is for single-hit knockback; AddForce is for sustained per-frame push (wind zones, explosion radius). Both respect SwarmSettings limits (MaxExternalVelocity, DefaultExternalControlDuration).

For tint: PulseTint handles one-shot flashes (damage, freeze, stun). Only reach for SetTint when you need to drive the tint value from your own per-frame logic.

Where SwarmAgent does not help: if you need to read simulation data in bulk (every agent's position for your own rendering or spatial queries), use the manager's span readers like ReadAgentPositions(). SwarmAgent is per-visual only and is not a batch API.

Where SwarmAgent is the wrong pattern: do not use it for observing agent state across multiple frames (health polling, position sampling, proximity checks). Hold a SwarmAgentHandle and read through the manager's handle gettersTryGetAgentPosition, TryGetAgentVelocity, and the rest — instead.

Quickstart — typical hit-reaction setup

  1. Add SwarmAgentCombatTarget to your agent prefab to make agents damageable.
  2. In your damage handler, call GetComponent<SwarmAgent>() on the hit GameObject.
  3. Call AddImpulse to push the agent away, then PulseTint to flash it.
  4. Done. You don't need to cache any reference — resolve and use in the same call.