Combat Contracts¶
namespace MassiveSwarmSystem.Runtime.Combat
Three small interfaces connect the swarm's attack layer to your game objects. Implement them on your own health and target scripts when you don't want the ready-made sample components. The swarm resolves them once when a target registers, then calls into whatever it found when a hit lands. There is no global manager and no physics-event dependency.
You may not have to implement anything
The sample components (PlayerCombatTarget, DestructibleCombatTarget,
SwarmAgentCombatTarget) already implement these contracts. Reach for the raw interfaces
only when you have an existing health system the swarm should call into. For the components,
their Inspector fields, and the turret/projectile workflow, see
Combat Integration.
How the swarm finds your contract¶
When a SwarmTarget registers, the manager resolves the contracts once
and caches them on the target:
GetComponentInParent<IDamageableWithContext>()— the receiver of swarm contact damage.-
GetComponentInParent<ITargetable>()— the auto-target gate for turrets and projectiles. -
Your component must sit on the
SwarmTarget's GameObject or a parent, never a child. - The lookup runs at registration (
OnEnable). If you add the component after the target is already registered, re-enable theSwarmTarget(or re-register the transform) so the swarm picks it up.
Swarm contact damage needs IDamageableWithContext, not plain IDamageable
The melee/contact path caches IDamageableWithContext specifically — every swarm hit carries
a hit point, direction, and pushback, so there is always a context. A component that
implements only IDamageable will not take swarm contact damage. Plain IDamageable is
enough only for the projectile and sample-helper path, which dispatches through
SwarmDamageableExtensions.ApplyDamage (below). When in doubt, implement
IDamageableWithContext (or subclass DamageableBase, which already does).
The damage call itself happens on the main thread inside the manager's FixedUpdate. The swarm
builds the context from the attacking agent's pose:
// Paraphrased from SwarmManager's attack pass — shown so you know what your
// ApplyDamage override receives.
SwarmDamageContext ctx = SwarmDamageContext.FromHit(
source: manager, // the SwarmManager that owns the agent
hitPoint: agentPos,
hitDirection: agentForward,
hitNormal: -agentForward,
pushbackStrength: attackProfilePushback);
damageable.ApplyDamage(damage, ctx);
IDamageable / IDamageableWithContext¶
public interface IDamageable
{
bool CanTakeDamage { get; }
bool ApplyDamage(float damage);
}
public interface IDamageableWithContext : IDamageable
{
bool ApplyDamage(float damage, in SwarmDamageContext context);
}
CanTakeDamage is checked before every hit — return false for i-frames, a dead state, or a
not-yet-spawned object. ApplyDamage returns true when damage was actually applied and false
when ignored.
Implementing the context overload on your own health component, with knockback straight from the hit data:
using MassiveSwarmSystem.Runtime.Combat;
using UnityEngine;
[RequireComponent(typeof(CharacterController))]
public class PlayerHealth : MonoBehaviour, IDamageableWithContext
{
[SerializeField] private float m_health = 100f;
private CharacterController m_controller;
private void Awake() => m_controller = GetComponent<CharacterController>();
public bool CanTakeDamage => m_health > 0f;
// Plain overload: the swarm never calls this one, but the projectile path and
// your own code might, so forward it to keep one code path.
public bool ApplyDamage(float damage) => ApplyDamage(damage, SwarmDamageContext.None);
public bool ApplyDamage(float damage, in SwarmDamageContext context)
{
if (!CanTakeDamage || damage <= 0f)
return false;
m_health -= damage;
if (context.HasPushback)
{
Vector3 push = context.GetPushbackDirection(transform.position) * context.PushbackStrength;
m_controller.Move(push * Time.fixedDeltaTime);
}
return true;
}
}
| Member | Description |
|---|---|
IDamageable.CanTakeDamage |
Gate checked before each hit. false makes the object immune right now. |
IDamageable.ApplyDamage(float) |
Applies damage with no hit metadata. Returns whether it landed. |
IDamageableWithContext.ApplyDamage(float, in SwarmDamageContext) |
Same, plus hit point / direction / pushback. The overload the swarm always calls. |
SwarmDamageContext¶
A small struct describing how a hit happened. Read the Has* guard before reading the matching
field — an empty context (SwarmDamageContext.None) has them all false.
| Member | Type | Description |
|---|---|---|
Source |
UnityEngine.Object |
What caused the damage (the SwarmManager for contact hits, your weapon for projectiles). |
HitPoint / HasHitPoint |
Vector3 / bool |
World-space impact position. |
HitDirection / HasHitDirection |
Vector3 / bool |
Normalized direction the hit travelled. |
HitNormal / HasHitNormal |
Vector3 / bool |
Surface normal at the impact. |
PushbackStrength / HasPushback |
float / bool |
Push magnitude from the attack profile; HasPushback is true when it's positive. |
HasSource |
bool |
true when Source is set. |
None |
SwarmDamageContext |
A shared empty context — all guards false. |
FromHit(source, hitPoint, hitDirection, hitNormal, pushbackStrength = 0) |
SwarmDamageContext |
Builds a populated context, normalizing the directions for you. |
GetPushbackDirection(receiverPosition, flattenToXZPlane = true) |
Vector3 |
Resolves a push direction from whatever data is present: HitDirection, else receiver-minus-HitPoint, else -HitNormal. Flattens to XZ by default so receivers don't launch upward. Returns Vector3.zero if nothing usable. |
SwarmDamageableExtensions¶
public static bool ApplyDamage(this IDamageable damageable, float damage, in SwarmDamageContext context);
A dispatch helper for the projectile and sample-helper path. It calls the context overload when
the receiver implements IDamageableWithContext, otherwise the plain overload — and treats a
null receiver as a no-op returning false. Call this from your own damage sources when you hold
only an IDamageable reference but have a context to pass.
ITargetable¶
public interface ITargetable
{
bool IsTargetable { get; }
Vector3 TargetPoint { get; }
Transform AimTransform { get; }
}
Implement this to make an object a valid auto-target for ProjectileWeapon and any custom
targeting you write.
| Member | Description |
|---|---|
IsTargetable |
Whether a weapon may pick this object right now. Return false when dead or out of play. |
TargetPoint |
World-space point a weapon aims at — the body center, not the pivot, for a character. |
AimTransform |
The transform used to resolve hierarchy ownership and avoid self-hits. |
Body radius lives on SwarmTarget, not here
ITargetable only governs auto-targeting. How wide a target's body is — what the swarm
surrounds and stops around — comes from the SwarmTarget component on the
same object. The two are independent.
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 while you code. This page has the full detail.
Related¶
- Combat Integration — the ready-made components, their Inspector fields, and the turret/projectile workflow.
- SwarmTarget — registering a target and where the body radius comes from.
- Attack Profile — what drives
damageandPushbackStrength, and how reach is measured.