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 calls Bind to wire it to a (SwarmManager, agentIndex) pair. When the agent dies or is recycled, the visual is released back to the pool and AgentIndex is reset to -1.

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 AgentIndex will silently be -1 and the Manager will be 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.


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.

AgentIndex

public int AgentIndex { get; }

The index of this agent's slot in SwarmAgentData. Returns -1 when the visual is in the pool (unbound).

Pass this to any SwarmManager or SwarmAgentData method that takes an agentIndex. It is guaranteed stable for the lifetime of one spawn — it will not change while the agent is alive, but it will change (or become -1) after a despawn.

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

IsInstanced

public bool IsInstanced { get; }

true when this agent is rendered via Graphics.DrawMeshInstanced (GPU-instanced path). false for standard per-GameObject rendering.

The public force and tint methods work on both paths.


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) the simulation hands exclusive velocity control to external forces. When omitted or negative, the manager's Default External Control Duration (from SwarmSettings) is used. Setting it to 0 lets swarm steering resume immediately.

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, equivalent to AddForce(impulseVelocity, ForceMode.Impulse). Use this for knockback from a single hit.

externalControlDuration works the same way as in AddForce: how long the simulation yields velocity control to the impulse before swarm steering resumes.


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 the VAT or instanced path) 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. This fits naturally in an OnDamageTaken handler on SwarmAgentCombatTarget (see Combat Integration).

private void OnDamageTaken(float damage, SwarmDamageContext context)
{
    SwarmAgent agent = GetComponent<SwarmAgent>();
    if (agent == null)
    {
        return;
    }

    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 query or modify simulation data in bulk (all agents within a radius, all agents of a given archetype), work directly through SwarmManager and SwarmAgentData. 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). Simulation data lives in SwarmAgentData arrays; read those directly through SwarmManager.AgentData 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.