Combat Integration¶
Massive Swarm System ships a small combat layer that connects swarm attack profiles to your game objects. This page covers every contract and component involved so you can wire attack damage into your own player, enemies, and destructibles.
Overview¶
When a swarm agent lands a melee strike or contact tick, it calls ApplyDamage on whatever it finds at the target. One interface covers it: implement IDamageable, or attach one of the ready-made sample components, and damage flows through.
The system is intentionally thin. There is no global health manager, no required ScriptableObject, and no dependency on Unity's physics event system. Implement that interface or subclass one of the ready-made components, and attack damage flows through automatically.
Contracts¶
These interfaces live in the core runtime assembly, MassiveSwarmSystem.Runtime, and are safe to implement in any of your own scripts. They stay in place when you remove the samples.
IDamageable¶
The contract for anything that can take damage.
public interface IDamageable
{
bool CanTakeDamage { get; }
bool ApplyDamage(float damage);
bool ApplyDamage(float damage, in SwarmDamageContext context);
}
The swarm checks CanTakeDamage before calling ApplyDamage. Return false from CanTakeDamage to make the object temporarily immune (invincibility frames, dead state, not yet spawned).
ApplyDamage should return true if damage was actually applied, false if ignored (already dead, zero damage, etc.).
The second overload receives hit metadata: knockback direction, hit point, hit normal. Use it to push a character controller away from the attacker or to spawn a directional blood decal. Swarm contact and melee damage always calls this one.
Both overloads are required, and that is deliberate
Swarm contact damage only ever calls the context overload. If that overload were optional you could write a component that compiles, registers as a target, gets chased, gets attacked, and never loses a point of health, with nothing logged to tell you why. Requiring it means the compiler catches that instead. If you do not want the metadata, one line covers it:
SwarmDamageContext¶
A struct passed alongside damage that describes how the hit happened.
| Property | Type | What it contains |
|---|---|---|
Source |
UnityEngine.Object |
The object that delivered the hit. For a shot that is the bolt, not the shooter |
Attacker |
Transform |
Who is behind the hit. For a shot that is whoever fired it |
HitPoint |
Vector3 |
World-space impact position |
HitDirection |
Vector3 |
Normalized direction the hit came from |
HitNormal |
Vector3 |
Surface normal at the hit point |
PushbackStrength |
float |
Configured push magnitude from the attack profile |
HasHitPoint |
bool |
Guards before reading HitPoint |
HasHitDirection |
bool |
Guards before reading HitDirection |
HasHitNormal |
bool |
Guards before reading HitNormal |
HasSource |
bool |
Guards before reading Source |
HasAttacker |
bool |
Guards before reading Attacker |
HasPushback |
bool |
True when PushbackStrength > 0 |
Use SwarmDamageContext.None when you need an empty context (no hit metadata). Use SwarmDamageContext.FromHit(...) to build one with all fields populated.
Attacker is the one perception cares about. A swarm agent can only be pointed at a target the manager has registered, and a bolt is not one, so the shipped weapons walk up from themselves to the SwarmTarget that stands for whoever is holding them and put that Transform in the context. If your own weapon builds a context by hand, pass the same thing as the last argument to FromHit and agents will react to you rather than to your bullets.
GetPushbackDirection(receiverPosition, flattenToXZPlane = true) resolves a pushback vector from whichever hit data is available. It prefers HitDirection, falls back to the receiver-to-hit-point direction, then to HitNormal. With flattenToXZPlane left at its default the Y component is zeroed before the vector is normalized, so ground knockback stays level. Pass false when you want the vertical component kept, for example to launch a target upward. Either way the result feeds straight into a CharacterController.Move or Rigidbody.AddForce call.
SwarmHitPoint¶
A static helper that turns a physics cast hit into a contact point safe to put in a SwarmDamageContext. Read this one before you write your own weapon, because the bug it fixes is easy to hit and hard to recognize.
A sweep that starts already overlapping a collider comes back degenerate: Unity reports distance 0, a zero-vector point, and a normal that only mirrors the sweep direction. Pass RaycastHit.point straight through and your hit VFX and floating damage numbers spawn at the world origin instead of on the agent. A weapon swinging through a packed swarm starts overlapped on most frames, so in this asset that case is the normal one, not the edge case.
using MassiveSwarmSystem.Runtime.Combat;
Vector3 contact = SwarmHitPoint.Resolve(hit, sweepOrigin);
var context = SwarmDamageContext.FromHit(this, contact, direction, hit.normal, pushback);
| Method | What it does |
|---|---|
Resolve(in RaycastHit hit, Vector3 referencePoint) |
Returns hit.point for a real sweep. For a zero-distance overlap it returns the closest point on the collider to referencePoint, which is normally your sweep origin |
OnCollider(Collider collider, Vector3 referencePoint) |
The closest-point half on its own, for overlap queries that never produce a RaycastHit |
Box, sphere, capsule and convex mesh colliders resolve exactly. Concave meshes and terrain hand the input back unchanged from Collider.ClosestPoint, so those fall back to the collider bounds, then to its transform position. It lives in MassiveSwarmSystem.Runtime, so it survives sample removal. SwarmProjectile and RotatingWeapon both call it, and copying either of them gets you the correct behavior for free.
ITargetable¶
Marks an object as a valid auto-target for ProjectileWeapon and other targeting systems.
public interface ITargetable
{
bool IsTargetable { get; }
Vector3 TargetPoint { get; }
Transform AimTransform { get; }
}
IsTargetable lets you gate whether a weapon can pick this object right now. Return false when the object is dead or out of play. TargetPoint is the world-space position the weapon aims at; for a character it should be the body center, not the pivot. AimTransform is used by the targeting system to resolve hierarchy ownership and avoid self-hit.
Target Body Radius¶
The swarm reads how wide the target's body is from the SwarmTarget component on the target GameObject. See Targets for the body-radius source dropdown (Auto Detect / Manual) and the inspector readout.
All three sample target components (PlayerCombatTarget, DestructibleCombatTarget, SwarmAgentCombatTarget) implement ITargetable. They are damage handlers, and the body radius comes from the adjacent SwarmTarget component, not from the combat target component.
DamageableBase¶
DamageableBase is an abstract MonoBehaviour that implements IDamageable for you. It manages a health pool, fires events, and calls protected virtual methods so subclasses can respond without re-implementing the core health logic.
This one ships with the samples, not the core runtime
DamageableBase and CombatTargetBase compile into MassiveSwarmSystem.Samples.StarterKit, which is the removable Starter Combat Kit tier. The interfaces above are different: they live in MassiveSwarmSystem.Runtime and survive sample removal. If you subclass DamageableBase in your own scripts, either keep the Starter Kit installed or copy the two files into your own assembly first. See Samples & Cleanup.
When to use it: you want health tracking with events out of the box. Subclass it instead of implementing IDamageable directly.
When to use raw interfaces instead: you already have a health system and just need the swarm to call into it. Implement IDamageable directly on your existing component.
Inspector fields¶
| Field | What it controls |
|---|---|
| Max Health | Starting and maximum health value |
| Current Health | The live value. It is serialized, so you can watch it fall in the inspector during Play mode |
| Reset Health On Enable | When on, health is restored to maximum each time the component enables |
Events¶
| Event | Signature | Fires when |
|---|---|---|
OnHealthUpdated |
Action<float> |
Any health change (argument is the delta) |
OnDamageTaken |
Action<float, SwarmDamageContext> |
Damage was applied (argument is the amount taken) |
OnHealthRestored |
Action<float> |
RestoreHealth actually raised current health (argument is the amount restored). It does not fire for ResetHealth or for the refill that Reset Health On Enable performs |
OnDied |
Action<SwarmDamageContext> |
Health reached zero |
Runtime API¶
Read and write health from your own scripts without touching the serialized fields.
| Member | Type | What it gives you |
|---|---|---|
MaxHealth |
float, read-only |
The configured maximum |
CurrentHealth |
float, read-only |
Live health |
CurrentHealthNormalized |
float, read-only |
Health as 0..1, already clamped. This is the one to drive a fill bar with |
IsAlive |
bool, read-only |
True while health is above zero |
CanTakeDamage |
bool, read-only, virtual |
Whether a hit would land right now. Auto-targeting reads this, so overriding it changes who the swarm picks |
IsInvulnerable |
bool, read/write |
God mode. Damage is absorbed and health never changes |
GrantTemporaryImmunity(float seconds) |
void |
Absorbs all damage for the next seconds of game time. A second call while a window is open extends it and never shortens it |
IsTemporarilyImmune |
bool, read-only |
True while a window from GrantTemporaryImmunity is running |
TemporaryImmunityRemaining |
float, read-only |
Seconds left in that window, 0 when none is running |
ClearTemporaryImmunity() |
void |
Ends the window immediately |
RestoreHealth(float amount) |
bool |
Heals, clamped at max. Returns whether anything changed, and fires OnHealthRestored with the amount actually gained |
ResetHealth() |
void |
Refills to maximum. Deliberately does not fire OnHealthRestored |
IsInvulnerable is not serialized and has no inspector field, so nothing in a shipped scene can turn it on. It is also kept out of CanTakeDamage on purpose: an invulnerable target stays selectable, reachable and attackable on exactly the normal schedule, and the only difference the player sees is that it survives. That makes it the right switch for a measurement rig, and the wrong one for making the swarm ignore something.
For a timed window, such as protection after a respawn or a few seconds after a power-up, use GrantTemporaryImmunity instead. Hits are absorbed the same way and the swarm keeps attacking, but the window runs on its own clock. It ends by itself, a paused game does not use it up, and it never switches off an IsInvulnerable flag a test rig turned on.
Two things to watch when wiring a heal pickup. RestoreHealth returns false for an amount of zero or less and for a target already at full health, so use the return value rather than assuming the heal landed. And OnHealthRestored reports the clamped gain, not the amount you asked for, so a 50-point heal on a target 10 points from full reports 10.
Protected virtual methods¶
Override these in your subclass to add behavior at the right moment.
| Method | When it runs |
|---|---|
HandleDamageTaken(float, in SwarmDamageContext) |
After each damage application, before events fire |
HandleDeath(in SwarmDamageContext) |
When health first hits zero, before the OnDied event |
CanTakeDamageInternal() |
Called by CanTakeDamage to add your own immune conditions |
Do not override HandleDeath directly if you subclass CombatTargetBase
CombatTargetBase seals HandleDeath to guarantee the death VFX always spawns before your subclass logic. Override OnDeath instead. See CombatTargetBase below.
CombatTargetBase¶
CombatTargetBase extends DamageableBase and adds a pooled death-effect VFX system. All three ready-made target components inherit from it.
When to subclass it: you want death burst VFX plumbing without writing it yourself, or you want to keep your component compatible with the sample scene setup.
When to use plain DamageableBase instead: you don't need a death effect, or you're managing VFX in your own code.
Inspector fields (Death group)¶
| Field | What it controls |
|---|---|
| Death Effect Prefab | Optional pooled VFX prefab spawned when health hits zero. Leave empty to skip |
| Death Effect TTL | Seconds the effect lives before returning to the pool |
| Death Effect Anchor | Pivot uses this GameObject's pivot (or simulation pose for swarm agents). Center uses the world-space center of the resolved renderer's bounding box, useful when the pivot is at the character's feet |
| Death Effect Offset | World-space offset added to the resolved anchor position. Use a positive Y value to lift the burst above ground |
| Death Effect Anchor Renderer | Optional explicit renderer used when anchor mode is Center. Auto-finds the first renderer in the hierarchy if left empty |
Subclassing CombatTargetBase¶
Override OnDeath(in SwarmDamageContext context) to run your type-specific death response. The death VFX always spawns before OnDeath is called.
protected override void OnDeath(in SwarmDamageContext context)
{
// Your logic here: disable, destroy, trigger animation, etc.
}
Override GetDeathEffectPivot() to return a custom spawn position when the visual transform pivot is not where you want the burst, for example the center of a bounding box or a simulated pose position.
Sample Components¶
Every sample component on this page compiles into MassiveSwarmSystem.Samples.StarterKit, the Starter Combat Kit tier, and works as-is in your scenes. They are also the reference implementations to copy when you need custom behavior.
Add them via Component → Massive Swarm System → Combat → ... or by searching the Add Component menu.
PlayerCombatTarget¶
Use this on the player character to handle swarm damage and auto-targeting by ProjectileWeapon.
Inherits from CombatTargetBase. Implements ITargetable.
Inspector fields
| Field | What it controls |
|---|---|
| Aim Point | Optional override for the aim point reported to targeting systems. Falls back to the CharacterController center, then a collider center, then the Transform pivot |
| Is Targetable | Lets you remove this object from targeting lists without disabling the component |
| Disable Game Object On Death | When on, the whole GameObject is deactivated when health reaches zero |
| Spawn Protection | Seconds the player takes no damage after it spawns or respawns. Enemies still chase and attack, and their hits do nothing until it runs out. Default 2. 0 turns it off |
Spawn protection starts every time the component is enabled. That covers the start of the scene and every respawn from PlayerRespawnController, which re-enables the player only once the spawn portal has opened, so the window begins as the body appears. Add an Invulnerability Blink Feedback so the player can see it running (see InvulnerabilityBlinkFeedback).
Body radius for reach calculations is configured on the adjacent SwarmTarget component (typically Auto Detect from the player's CharacterController / collider).
Health and death events come from DamageableBase. Subscribe to OnDied, OnDamageTaken, or OnHealthUpdated in your player controller to drive UI and game state.
DestructibleCombatTarget¶
Use this on world objects that should be destroyable by swarm agents or projectiles: barricades, crates, turrets, doors.
Inherits from CombatTargetBase. Implements ITargetable.
Inspector fields
| Field | What it controls |
|---|---|
| Aim Point | Optional override for the aim point. Falls back to the collider center, then the Transform pivot |
| Is Targetable | Removes this object from targeting lists while keeping it alive |
| Death Response | Disable Game Object (default), Destroy Game Object, or None, which lets the death VFX and debris remain |
| Disable Colliders On Death | When on, all Collider components on this GameObject and its children are disabled on death. Most useful with Death Response = None so agents can pass through the debris |
Body radius for reach calculations is configured on the adjacent SwarmTarget component (Auto Detect picks up the collider).
SwarmAgentCombatTarget¶
Use this when individual swarm agents should be damageable, for example when the player fires a ProjectileWeapon at the swarm.
Inherits from CombatTargetBase. Implements ITargetable. Requires a SwarmAgent component on the same GameObject.
Aim Point sits at the top of the component, outside the groups below. It is an optional Transform that targeting systems aim at. Left empty, they aim at the agent's collider center, then the Transform pivot.
This component lives on the agent visual GameObject
Swarm agent visuals are pooled and reused. Do not cache a reference to SwarmAgentCombatTarget beyond the current frame, because the GameObject may be returned to the pool at any moment. Subscribe to OnDied or OnDamageTaken in the same frame you resolve the component, and unsubscribe when done.
Inspector fields (Damage Response group)
| Field | What it controls |
|---|---|
| Fallback Pushback Strength | Pushback magnitude used when the damage context has a pushback direction but no explicit strength |
| Pushback Strength Multiplier | Scales all incoming pushback before it is applied to the simulation. Use to tune how far agents are knocked back |
| Knockback Duration | How long the external-force control window lasts after a hit. Negative values use the default from SwarmSettings |
| Flatten Pushback To Movement Plane | When on, the pushback direction is flattened to the XZ plane before being applied, preventing agents from launching vertically |
Inspector fields (Perception group)
| Field | What it controls |
|---|---|
| Damage Reveals Attacker | On by default. A hit makes the agent aware of whoever landed it, so a shot from behind turns it round instead of only shoving it. Needs a Perception Profile on the archetype and a SwarmTarget on the attacker. Turn it off for agents that should react only to what they see and hear |
| Death Dissolve Duration | Seconds the VAT mesh dissolves between death and pool return. Set to 0 to skip the dissolve and despawn instantly. Draws under this group in the inspector, though it has nothing to do with perception |
Being shot counts as noticing. Awareness jumps straight to full with no sight check, because a bullet leaves no doubt. From there the agent behaves as though it had heard you: it walks toward the attacker, and if it gets close enough to see you, sight takes over and it stops forgetting. If it never gets that close, awareness runs out at the profile's Forget Time and it goes back to idling. A killing blow skips this, and so does every scene with no Perception Profile in it, which is how a 1.0 project keeps behaving the way it did.
Body radius. This component does not carry one, and the shipped MSS Combat Agent prefab has no SwarmTarget on it. An agent only needs a body radius when something is treating it as a target in return, which means a second swarm chasing these agents. Add SwarmTarget yourself for that case and its Auto Detect will pick up the agent's collider.
Death behavior. When health hits zero, the component calls SwarmManager.DespawnAgentAsDying (if dissolve duration is greater than zero) or SwarmManager.DespawnAgent (instant). The simulation slot is freed and the visual is returned to the pool.
Scripting per-agent reactions. Once you have a hit callback here, you can reach SwarmAgent on the same GameObject to apply knockback (AddImpulse) or a damage flash (PulseTint). See SwarmAgent for the full API reference and a copy-pasteable knockback recipe.
Raycasts and projectiles hit the agent's drawn body, which can sit several units off the simulation position in a packed crowd, while the swarm's own attacks use the simulation position. Physics & Colliders: Hitting agents explains which position to read for which kind of check, and how solid or trigger agent colliders change what your scripts receive.
On-Hit Feedback: Damage & Heal Numbers¶
The StarterKit ships drop-on feedback helpers that react to the health events any DamageableBase fires: a hit flash (DamageFlashFeedback, white at 75% alpha by default), a world-space health bar (HealthBarDisplay), and floating damage/heal numbers (FloatingTextFeedback).
Add them from the Scene View overlay or the Add Component menu. The two overlay groups do not offer the same set. Swarm Agent appears for agent prefabs and carries Flash When Hit, Spawn On Death and Floating Combat Text. Health Bar lives in the Combat Helpers group, which only shows for objects with a SwarmTarget.
FloatingTextFeedback¶
Shows rising damage and heal numbers over anything with a DamageableBase. It listens for OnDamageTaken and OnHealthRestored and hands them to a text manager that spawns and pools the numbers. There is no per-agent Update, so it scales to large swarms.
Add it with the overlay's Floating Combat Text button or via Component → Massive Swarm System → Combat → Floating Text Feedback. It has to sit on the same GameObject as the damageable it reads.
Inspector fields
| Field | What it controls |
|---|---|
| Damage Style | Style asset for damage numbers. Auto-fills with MSS Floating Text Damage when the component is added |
| Heal Style | Style asset for heal numbers. Auto-fills with MSS Floating Text Heal when the component is added |
| Use Hit Point | On: a damage number spawns at the weapon's hit point when the hit carried one, otherwise above the target. Off: always above the target. Heal numbers always spawn above the target |
| World Offset | Where numbers spawn relative to the target when no hit point is used (world units) |
Already on the shipped combat agents
The MSS Combat Agent prefab and the demo VAT guardians carry FloatingTextFeedback by default, so the sample swarms pop numbers the moment you shoot them. Remove the component from a prefab if you don't want them there.
Styling the numbers. A FloatingTextStyle asset sets the color, font size, how long a number lasts, how far it rises, its sideways drift, and a leading character (the shipped heal style uses +). It also holds the merge window: repeated hits on one target inside that window fold into a single climbing number instead of stacking. Duplicate a shipped style, tweak it, and drop it into the slot to restyle without touching code. Under Advanced the style also points at the label prefab a number is drawn on. That slot fills itself in when the style is created and most projects never touch it, but because it is a direct reference rather than a lookup by name, the label art only reaches your build when something in it actually shows a number.
The text manager. A FloatingTextManager owns the pool and ticks every number from one Update. It creates itself the first time a number is shown, so you never place one by hand. To change the on-screen cap (64 by default, oldest recycled past it) or swap the font on every number, add a FloatingTextManager to the scene (Component → Massive Swarm System → Combat → Floating Text Manager) and set its fields.
DamageFlashFeedback¶
Tints the target's renderers on hit and fades the tint out. Safe on a whole swarm: one shared driver ticks every flash, and only a component that is currently flashing registers with it, so an agent nobody has shot costs nothing per frame. It handles VAT-animated agents as well as ordinary renderers.
| Field | What it controls |
|---|---|
| Visual Root | Transform whose renderers get tinted. Falls back to this object's own Transform, which is usually what you want |
| Flash Color | The overlay color. Default is white at 75% alpha |
| Flash Duration | How long one flash lasts, in seconds. Default 0.12 |
| Flash Curve | Tint strength over that duration. The default ramps from full to nothing, so the flash decays. Flatten it for a hard blink |
| Use Unscaled Time | Keeps the flash running at normal speed while the game is paused or in slow motion |
InvulnerabilityBlinkFeedback¶
Blinks the model while its DamageableBase is inside a GrantTemporaryImmunity window, which on a PlayerCombatTarget is the spawn protection. The blink is timed from the end of the window, so the model is always visible in the last moment before hits start landing again.
Only mesh and skinned mesh renderers blink. A health bar or a weapon trail stays on screen. It hides them with forceRenderingOff and leaves their enabled flag alone, so it never conflicts with other code that shows or hides the same renderers.
| Field | What it controls |
|---|---|
| Visual Root | Transform whose renderers blink. Falls back to this object's own Transform |
| Blink Interval | Seconds the model stays hidden, then shown, while immune. Default 0.1 |
For the player or a boss, not the swarm
It checks the damageable in its own Update, one callback per instance. That is nothing on a player and too much on a thousand agents.
HealthBarDisplay¶
A world-space bar on a Canvas that fills from a DamageableBase and turns to face the camera.
| Field | What it controls |
|---|---|
| Fill Image | The UI Image whose fill amount tracks health. Its Image Type must be Filled or nothing moves |
| Target | The DamageableBase to read. Leave it pointing at the object the bar belongs to |
| Billboard Camera | Camera the bar turns toward. Falls back to Camera.main at startup |
Not for every agent in the swarm
This one billboards in its own LateUpdate, so each instance costs a Unity callback every frame. That is fine for the handful of things it was built for: the player, a boss, a few turrets. Put it on a thousand agents and you have added a thousand callbacks per frame, which is exactly the cost this asset exists to avoid. If you want bars over swarm agents at scale, drive the billboarding from one shared updater instead, the way DamageFlashFeedback does.
ProjectileWeapon¶
A self-contained turret/ranged-weapon component that finds the nearest ITargetable, rotates toward it, and fires pooled SwarmProjectile instances. Designed for the sample turrets but works on any GameObject.
Add it via Component → Massive Swarm System → Combat → Projectile Weapon.
The weapon also needs a SwarmProjectile prefab assigned. That prefab is a thin pooled MonoBehaviour that moves itself in Update. Each frame it first sweeps the stretch it is about to cover, with a sphere cast (or a ray when Projectile Hit Radius is zero), then moves. The nearest thing on the Projectile Collision Mask that the sweep reaches ends the flight. A damageable gets IDamageable.ApplyDamage, and a solid collider it cannot damage, such as a wall, stops the bolt with no damage.
Targeting group¶
| Field | What it controls |
|---|---|
| Muzzle | Transform used as the raycast origin and projectile spawn point. Falls back to the weapon Transform |
| Targeting Radius | Sphere radius (world units) searched each fixed frame for valid targets |
| Target Mask | Layer mask for the overlap query. Only colliders on these layers are considered |
| Target Trigger Interaction | Whether the overlap query includes trigger colliders |
| Overlap Capacity | Maximum number of colliders the overlap buffer can hold per query |
Turret Aim group¶
| Field | What it controls |
|---|---|
| Yaw Pivot | Optional Transform rotated horizontally toward the target. Falls back to the weapon Transform |
| Pitch Pivot | Optional Transform rotated vertically. Usually a child of the yaw pivot |
| Yaw Limits | Horizontal rotation limits (min and max). -180 to 180 allows full rotation |
| Pitch Limits | Vertical rotation limits (min and max), clamped to ±89 |
| Aim Turn Speed Degrees | Rotation speed in degrees per second. Set to 0 to snap instantly |
| Fire Aim Tolerance Degrees | Maximum angle between current aim and target before a shot is allowed |
Sight group¶
| Field | What it controls |
|---|---|
| Require Line Of Sight | When on, the weapon only fires if no obstructing collider is between muzzle and target |
| Sight Obstruction Mask | Layers that block line of sight. Defaults to Nothing, so no layer blocks sight until you assign one |
| Sight Radius Padding | Extra radius for sight sphere casts. Small non-zero values catch thin blockers near the path |
Sight Obstruction Mask defaults to Nothing
Out of the box, Require Line Of Sight is on but no obstruction layers are set, so walls and obstacles never block firing. Assign the relevant layers (walls, floors, cover) to get expected behaviour.
Firing group¶
| Field | What it controls |
|---|---|
| Projectile Prefab | The SwarmProjectile prefab to spawn. Required; the weapon does nothing without it |
| Projectile Root | Optional parent Transform for spawned projectiles (keeps the hierarchy tidy) |
| Owner Root | Optional Transform whose hierarchy is ignored by targeting and self-hit checks. Falls back to transform.root |
| Fires Per Second | Fire rate |
| Damage | Damage applied to the first damageable each projectile reaches. A wall in front of it stops the shot first |
| Projectile Pushback Strength | Pushback strength passed to SwarmDamageContext.FromHit on impact |
| Projectile Speed | Travel speed in world units per second |
| Projectile Lifetime | Seconds a projectile stays in flight before it despawns itself, when it has not hit anything |
| Projectile Hit Radius | Sphere-cast radius for collision detection. Zero uses a pure raycast |
| Projectile Collision Mask | Layers the projectile collides with. A solid collider on them that it cannot damage, such as a wall, stops it, so leave out the ground and scenery if shots should fly past them. Starts on Everything |
| Projectile Trigger Interaction | Whether projectile collision includes trigger colliders |
| Projectiles Per Shot | Number of projectiles launched each shot. Above 1, the extra projectiles fan out across the multi-shot spread |
| Multi Shot Spread Degrees | Total fan angle spread across the projectiles when more than one fires per shot |
Pool sizes are configured on the SwarmPoolManager component under its Shared Pool Defaults group, not on this component: Initial Pool Size (default 64) is how many instances are prewarmed the first time a prefab is requested, and Max Pool Size (default 512) caps how many are kept alive for reuse. The manager keeps three separate sets of pools, one for projectiles, one for effects and one for everything else spawned through it, so a burst of impact VFX cannot starve the bolts.
Audio and Muzzle Flash groups¶
Both are optional. Leave them empty and the weapon fires silently with no flash.
| Field | What it controls |
|---|---|
| Audio Source | The AudioSource that plays the fire sound. Put one on the weapon or its muzzle. The rest of the Audio group stays greyed out until this is assigned |
| Fire Sfx | Clip played once per shot. A volley of several projectiles still plays one sound |
| Fire Volume | Volume passed to PlayOneShot |
| Fire Pitch Range | Minimum and maximum pitch. Each shot draws a random value in between, so repeated fire does not sound looped |
| Muzzle Flash Prefab | VFX spawned at the muzzle on each shot. It goes through the same SwarmPoolManager as the projectiles, so it is pooled rather than instantiated |
| Muzzle Flash TTL | Seconds before the flash is released back to the pool |
The SwarmProjectile prefab¶
The weapon fires these, but the impact belongs to the projectile, so this is where you add a spark on hit. Add the component via Component → Massive Swarm System → Combat → Swarm Projectile. Speed, damage, lifetime and layer masks are passed in by whatever launches it; the fields below are authored on the prefab.
| Field | What it controls |
|---|---|
| Hit Effect Prefab | VFX spawned at the impact point when the bolt hits something damageable. Pooled through SwarmPoolManager. Empty means no impact VFX. It also plays where a wall stops the bolt, unless Obstacle Hit Effect Prefab is set |
| Hit Effect TTL | Seconds before that VFX returns to the pool. Default 2 |
| Hit Effect Orientation | Hit Normal (default) faces the effect away from the surface it struck, Bullet Forward matches the direction of travel, Identity keeps whatever rotation the prefab was authored with |
| Hit Effect Rotation Offset Euler | Extra rotation applied on top of the chosen orientation. Use it when the prefab's own forward axis is not the one you want pointing outward |
| Obstacle Hit Effect Prefab | VFX spawned where the bolt is stopped by something it cannot damage, such as a wall. Put an AudioSource with a clip on it for an impact sound. Left empty, Hit Effect Prefab plays there instead |
| Trail | Optional TrailRenderer for a tracer streak. Assign the one on the prefab, not a scene object. It is cleared on launch, so a recycled bolt does not draw a line from wherever it last died |
Impact points go through SwarmHitPoint, so a point-blank shot into a packed crowd puts the effect on the agent rather than at the world origin.
TwinStickWeapon¶
The player-fired counterpart to ProjectileWeapon. Where ProjectileWeapon is a turret that picks its own target and fires on a timer, TwinStickWeapon has no opinion about targets: it fires along wherever the player is aiming while the trigger is held. Projectiles, pooling, hit detection and damage are the same shipped SwarmProjectile pipeline ProjectileWeapon uses. The only thing this component adds is deciding when to pull the trigger and in which direction.
Ships with the Full Demo, and needs the Input System package
TwinStickWeapon lives in MassiveSwarmSystem.Samples.InputSystem, part of the Full Demo tier. That assembly compiles only when com.unity.inputsystem is installed, the same requirement as the Input System adapters. It also reads aim and the fire signal from a DemoPlayerController, covered in Input Integration.
Add via Component → Massive Swarm System → Input → Twin Stick Weapon.
How it fires¶
Each Update, the component polls DemoPlayerController.FireHeld. While held and the cooldown from Fires Per Second has elapsed, it calls DemoPlayerController.TryGetAimDirection for a direction and launches a shot. If aim is unavailable for a frame (a pointer resting exactly on the player is the usual cause), it falls back to the weapon's own forward direction instead of skipping the shot, so a held trigger keeps firing instead of stuttering.
Every shot takes one round from the magazine. The reload starts on the shot that empties it and lasts Reload Time, and the weapon cannot fire until it ends. Ammo is never used up, so the magazine only decides how often firing pauses. Pressing the Reload Action starts the same reload early, unless the magazine is already full.
Inspector fields¶
| Field | What it controls |
|---|---|
| Player | The DemoPlayerController that supplies aim and the fire signal. Left empty, the one on this GameObject is used. |
| Muzzle | Where shots leave from. Left empty, this GameObject's own position is used. |
| Follow Muzzle Tilt | On: shots tilt up or down with the Muzzle's forward axis (limited to about 30 degrees either way), so they leave along the barrel of a rifle held by an animated character. The heading still comes from the aim. Off: shots always fly level, which suits a turret or a capsule player. Needs a Muzzle assigned. |
Projectile group¶
| Field | What it controls |
|---|---|
| Projectile Prefab | The SwarmProjectile to fire. Pooled through SwarmPoolManager, so the same prefab is reused rather than instantiated per shot. Required; without one the trigger does nothing and a warning is logged once. |
| Muzzle Flash Prefab | Optional VFX spawned at the muzzle on each shot, pooled the same way. |
| Muzzle Flash TTL | Seconds the muzzle flash stays alive before it returns to the pool. |
Audio group¶
Optional. Leave Audio Source empty and the weapon fires silently.
| Field | What it controls |
|---|---|
| Audio Source | The AudioSource the shot plays through. Put one on the player or the muzzle. |
| Fire Sfx | Sound played once per shot. |
| Fire Volume | How loud each shot is. A held trigger stacks several shots at once, so this wants to sit lower than a single sound would. |
| Fire Pitch Range | Minimum and maximum pitch. Each shot draws a random value in between, so rapid fire does not sound like a looping hum. |
| Reload Sfx | Sound played once when a reload starts, through the same Audio Source. A clip about as long as Reload Time ends as the weapon is ready again. Left empty, reloading is silent. |
| Reload Volume | How loud the reload sound is. Default 0.6. |
Fire group¶
| Field | What it controls |
|---|---|
| Fires Per Second | Shots per second while the trigger is held. Kept in real time, so it does not change with frame rate. |
| Projectile Speed | Travel speed in units per second. |
| Damage | Damage applied to the first damageable thing the shot reaches. A wall in front of it stops the shot first. |
| Projectile Pushback Strength | How hard a hit shoves the body it lands on. 0 kills without moving anything. |
| Projectile Lifetime | Seconds a shot stays in flight before it gives up. Speed times lifetime is its range. |
| Projectile Hit Radius | Radius of the shot's sweep. 0 makes it a thin ray, which misses a moving body more often than it looks like it should. |
| Spread Angle | Widest angle a shot can stray to either side of the aim, in degrees, from 0 to 45. Each shot picks its own angle inside that range. The spread is horizontal only, so it never sends a shot into the floor. 0 fires dead straight. Default 2. |
Magazine group¶
| Field | What it controls |
|---|---|
| Magazine Size | Shots fired before the weapon has to reload. 0 fires without ever reloading. Default 30. |
| Reload Time | Seconds a reload takes. 0 refills the magazine at once. Default 1.5. |
| Reload Action | Optional Button action that reloads before the magazine is empty. Left empty, the weapon only reloads when it runs out. |
Noise group¶
| Field | What it controls |
|---|---|
| Fire Noise | Optional SwarmNoiseEmitter triggered at the muzzle on each shot, which is what turns firing into a way of drawing a crowd. Left empty, the gun is loud to the player and silent to the agents. See Perception for the emitter's own fields. |
Layers group¶
| Field | What it controls |
|---|---|
| Projectile Collision Mask | What stops a shot. Include the walls, or shots pass through buildings. |
| Target Mask | What a shot is allowed to damage. Narrower than the collision mask on purpose: a wall stops a bullet without taking damage from it. |
| Projectile Trigger Interaction | Whether shots register against trigger colliders. Swarm agent colliders can be triggers, so Collide is normally what you want. |
FiresPerSecond is exposed as a public read-only property, for a HUD or upgrade system that wants to display or scale the fire rate without touching the serialized field. MagazineSize, AmmoInMagazine, IsReloading and ReloadProgress (0 to 1) report the magazine the same way, and Reload() starts a reload from code. It returns false when the weapon has no magazine, the magazine is full or a reload is already running.
DemoAmmoCounter (Component → Massive Swarm System → Samples → Demo Ammo Counter) is the HUD Zombie City builds on those properties. It prints the shots left over the magazine size at the bottom of the screen, changes colour for the last few rounds, and shows a bar that fills during a reload. It draws nothing for a weapon whose Magazine Size is 0.
Choosing What to Use¶
| Your situation | What to add |
|---|---|
| Player character needs health + swarm damage + auto-targeting | PlayerCombatTarget |
| World prop needs health + swarm damage + auto-targeting | DestructibleCombatTarget |
| Individual agents need to take player damage | SwarmAgentCombatTarget on the agent prefab |
| You want a ready-made turret that fires at agents | ProjectileWeapon + a SwarmProjectile prefab |
| Player character needs a twin-stick weapon that fires where they aim | TwinStickWeapon + a SwarmProjectile prefab, driven by a DemoPlayerController |
| You want floating damage / heal numbers over a target | FloatingTextFeedback (one click from the overlay; already on the shipped combat agents) |
| You have your own health system and just need swarm damage to call into it | Implement IDamageable directly |
| You want health tracking with events but no VFX | Subclass DamageableBase |
| You want health tracking + death VFX | Subclass CombatTargetBase and override OnDeath |
For the reach formula that governs when an agent can land a hit, see Attack Profile: How Reach Is Measured.
Sample Helpers¶
Lightweight components for adding melee and contact damage to non-swarm GameObjects. They use the same IDamageable contract as the rest of the combat layer.
Add them via Component → Massive Swarm System → Combat → ... or by searching the Add Component menu.
Rotating Weapon¶
A sweeping melee weapon that rotates between two angles, performs a sphere-cast + overlap-sphere pass along the arc, and calls IDamageable.ApplyDamage on anything it touches. Each activation hits a given target at most once, regardless of how many frames the sweep overlaps it.
Use this for enemies or hazards that need a procedural swing attack: spinning blades, arm swings, area denials.
Timing
| Field | What it controls |
|---|---|
| Swing Duration | How long one full sweep takes (start angle to end angle) |
| Delay Between Activations | Idle time after a swing completes before the next one starts |
| Initial Delay | Wait before the very first swing after the component enables |
Swing Rotation
| Field | What it controls |
|---|---|
| Swing In World Space | On by default. Holds the arc at a fixed world orientation, so the owner turning never spins it, while the weapon still follows the owner's position. Turn it off to swing relative to the owner's facing, like a handheld slash |
| Swing Plane | XZ rotates the weapon around the Y axis (top-down sweep); XY rotates around the Z axis (side-on sweep) |
| Start Angle Degrees | Local rotation angle at the beginning of the sweep |
| End Angle Degrees | Local rotation angle at the end of the sweep |
Hit Sphere
| Field | What it controls |
|---|---|
| Hit Sphere Distance From Base | How far along the weapon the hit sphere sits. This is a local offset, so it scales with the weapon transform, and with Size Multiplier at runtime, rather than being a fixed world distance |
| Hit Sphere Radius | Radius of the hit sphere. Scaled by the GameObject's lossy scale at runtime |
| Query Capacity | Maximum colliders processed per sweep or overlap query |
| Collision Mask | Layers the swing physically checks. Defaults to Everything, so walls and props can block targets behind them |
| Target Mask | Layers eligible to take damage. Colliders outside this mask can still block the swing through Collision Mask |
| Query Trigger Interaction | Whether the query includes trigger colliders |
Damage
| Field | What it controls |
|---|---|
| Damage | Flat damage passed to IDamageable.ApplyDamage |
| Pushback Strength | Written into SwarmDamageContext for receivers that use it |
| Owner Root | Optional transform excluded from hit detection. Falls back to transform.root if not set. The attacker written into the damage context is resolved on its own, from the nearest SwarmTarget at or above the weapon, and only falls back to this when there is none |
Visuals
| Field | What it controls |
|---|---|
| Visual Root | Optional child object toggled on/off during the swing. When not set, the component enables and disables child Renderer components instead |
| Use Scale Tween | When on, the weapon scales in at swing start and out at swing end |
| Scale Tween Duration | Duration of the scale-in and scale-out phases |
Audio
Optional. With no Audio Source the weapon swings silently and the rest of the group stays greyed out.
| Field | What it controls |
|---|---|
| Audio Source | The AudioSource that plays both sounds. Put one on the weapon |
| Swing Sfx | Whoosh played at the start of every swing, whether or not it connects |
| Swing Volume | Volume for the whoosh |
| Swing Pitch Range | Minimum and maximum pitch, drawn at random per swing so a spinning blade does not sound like a loop |
| Hit Sfx | Impact sound, played once per swing the first time the arc connects. A swing that hits five agents still plays it once |
| Hit Volume | Volume for the impact |
Debug
| Field | What it controls |
|---|---|
| Draw Debug Gizmos | Draws the swing arc and hit sphere in the Scene view. On by default |
Growing the weapon at runtime
SizeMultiplier is a script-only property, with no serialized field and nothing to find on the prefab, so it is worth knowing it exists. It applies uniform growth on top of the authored local scale and moves the model, the swing reach and the hit-sphere radius together, which means what the player sees is what the swing hits. 1 is the authored size, and it clamps to the range 0.1 to 8.
That is the whole implementation of a bigger-weapon upgrade.
Spawn Prefab On Death¶
Spawns one pooled prefab when the DamageableBase on the same GameObject reports OnDied. Loot drops, gibs, a scorch decal, an ammo pickup. It works on anything with a DamageableBase, not just swarm agents: props, towers, the player.
Add it with the overlay's Spawn On Death button or via Component → Massive Swarm System → Combat → Spawn Prefab On Death.
| Field | What it controls |
|---|---|
| Prefab | What to spawn, through SwarmPoolManager. Empty disables the component |
| Initial Pool Size | How many to prewarm the first time this prefab is requested. Default 16. Later spawns reuse the existing pool and ignore this value, so raising it after the first request changes nothing |
| Spawn Offset | World-space offset from the dying object's pivot. The default lifts the spawn 0.3 units so a pickup does not appear inside the floor |
| Inherit Rotation | On, the spawned instance takes this transform's rotation. Off, it spawns with identity rotation |
This is separate from the death VFX slot on CombatTargetBase, which keeps its own anchor and centering rules for particle bursts. Use that one for the death effect and this one for something that stays in the world.
Touch Damage Source¶
Applies damage to any IDamageable that enters its trigger collider. Requires a trigger collider on the same GameObject. Adds a kinematic Rigidbody automatically at runtime if none is present.
Use this for hazards or enemies that damage on contact: lava zones, spinning saws, enemy bodies.
Damage
| Field | What it controls |
|---|---|
| Damage | Amount applied per damage event |
| Reapply Delay | Seconds between re-applications while the target stays inside. Set to 0 or below to hit once per continuous touch |
| Pushback Strength | Written into SwarmDamageContext for receivers that use it |
| Owner Root | Optional transform excluded from self-overlap. Falls back to transform.root if not set |
Target Filter
| Field | What it controls |
|---|---|
| Target Filter Mode | None hits everything; Tag requires a matching tag; Layer requires a matching layer; Tag And Layer needs both; Tag Or Layer needs either |
| Required Tag | Tag compared when the filter mode includes Tag. Empty by default, so set a tag before choosing a Tag-inclusive filter mode, or no target will match |
| Target Layers | Layer mask compared when the filter mode includes Layer |
Required Tag must exist in your project
If Required Tag names a tag that has not been defined in your project's Tag Manager, Unity throws a runtime exception. The component catches it and logs an error once, but no damage is applied until you fix the tag.
Quickstart: wire up damage in this order
- Add
PlayerCombatTargetto your player GameObject. Set Max Health to match your game's health scale. - Subscribe to
OnDiedto trigger your game-over or respawn flow. - Add
DestructibleCombatTargetto any world objects that should also be targetable (turrets, barricades). - On agent prefabs where you want the player to shoot back, add
SwarmAgentCombatTarget. Set Death Dissolve Duration to0if the VAT material has no dissolve shader. - To give a turret ranged attacks, add
ProjectileWeapon, assign aSwarmProjectileprefab, and set Target Mask to the agent layer. - For damage and heal numbers over a target, add
FloatingTextFeedback(the agent prefabs already have it). The styles fill in on their own. - If reach calculations feel off, check Attack Profile: Target Body Radius and confirm your targets return a valid radius.