Class AICombatSlots
Hands each attacker its own standing spot in a ring around a shared target, so a pack surrounds its victim instead of piling onto one point.
public static class AICombatSlots
- Inheritance
-
AICombatSlots
- Inherited Members
Remarks
Why the NavMeshAgent's own avoidance is not enough. Unity's local avoidance is very good at what it does: two agents crossing paths will slide around each other rather than interpenetrate. But it is a local solver, and it is only ever given one problem to solve — "do not overlap right now". It has no opinion about destinations. When five attackers are all told to path to the same point, avoidance faithfully keeps them from overlapping while they all continue pushing toward that point, which produces the familiar shoving, orbiting scrum: agents jitter against each other, get shunted out of attack range, path back in, and repeat. No amount of tuning agent radius or avoidance quality fixes it, because the destinations themselves are in conflict.
The fix is to stop asking for the same point. Each attacker claims an angular slot around its target and paths to that, so the destinations are already separated by more than an agent diameter and avoidance is back to handling the incidental crossings it is good at.
Ring capacity is derived from geometry rather than configured: the number of agents of a given radius that physically fit on a circle of a given radius. Attackers beyond that capacity are placed on an outer ring rather than being squeezed in, which is what produces the natural "front rank and back rank" look when a large pack engages.
Server-only state, keyed by character ID. Entries are released on disengage and pruned when a target's ring empties.
Methods
Claim(long, long, float, float, out int, out int, out int)
Claims (or refreshes) this attacker's slot in the ring around a target.
public static void Claim(long targetID, long attackerID, float combatRadius, float agentRadius, out int slot, out int ring, out int ringCapacity)
Parameters
targetIDlongThe character being attacked.
attackerIDlongThe attacking character.
combatRadiusfloatDesired distance from the target for the innermost ring.
agentRadiusfloatThe attacker's own agent radius.
slotintThe attacker's index within its ring.
ringintWhich ring the attacker is on. 0 is the innermost.
ringCapacityintHow many attackers that ring holds.
Clear()
Drops all slot state. Call on scene teardown.
public static void Clear()
GetAttackerCount(long)
Number of attackers currently engaging a target.
public static int GetAttackerCount(long targetID)
Parameters
targetIDlongThe target to query.
Returns
- int
The attacker count.
GetRingCapacity(float, float)
How many agents of a given radius physically fit on a ring of a given radius.
public static int GetRingCapacity(float combatRadius, float agentRadius)
Parameters
Returns
- int
Slot count for the ring.
Remarks
Circumference divided by the space one agent needs, floored to at least one so a very tight melee radius still produces a usable ring rather than a division by zero.
GetSlotPosition(Vector3, int, int, int, float, float)
Computes the world position of a slot around a target.
public static Vector3 GetSlotPosition(Vector3 targetPosition, int slot, int ring, int ringCapacity, float combatRadius, float agentRadius)
Parameters
targetPositionVector3The target's position.
slotintSlot index within the ring.
ringintRing index. 0 is innermost.
ringCapacityintSlots on the ring.
combatRadiusfloatDistance from the target for the innermost ring.
agentRadiusfloatThe attacker's agent radius, used to space outer rings.
Returns
- Vector3
The world position this attacker should stand at.
Release(long)
Releases an attacker's slot.
public static void Release(long attackerID)
Parameters
attackerIDlongThe attacker giving up its slot.
ReleaseTarget(long)
Removes every slot around a target. Called when the target dies or despawns.
public static void ReleaseTarget(long targetID)
Parameters
targetIDlongThe target whose ring should be cleared.