One fighter, many attackers - yet the brawl never collapses into a mob. A ring of attack slots circles the player, enemies queue for their turn, and every incoming strike is telegraphed so it can be read, parried, and answered. The fantasy is competence under pressure.
⚙ Unreal Engine 5
🥊 Combat Design
🧠 State Tree AI
🎯 Motion Warping
🥊 What This Mechanic Does
This is a third-person, hand-to-hand combat system built for fighting many enemies at once without the encounter dissolving into chaos. A ring of attack slots is anchored to the player and updated on a timer, enemies reserve the nearest free slot to approach, and only the enemy holding an attack token may actually strike. Every attack carries a telegraph window the player can read and answer with a parry or an evade.
The result is the signature feeling of the genre: outnumbered but never overwhelmed. The crowd pressures you, but the rules underneath quietly choreograph it - spacing the attackers, staggering their strikes, and rewarding clean reads with a counter.
The system in action - circling crowd, telegraphed strikes, and parry counters.
Why It Lives Here, Not in a Project
This system was prototyped as a focused study of crowd-combat architecture rather than as part of a larger title. The interesting design lives in the orchestration - how the slot ring, attack token, and parry windows cooperate - so it sits here as a self-contained mechanic: a portable combat skeleton that any brawler could be built on top of.
Combat DesignAI / State TreeMotion WarpingAnim NotifiesBlueprints
🎯 The Intent Behind It
The starting question was: "How do you make a player feel skilled while surrounded?" A naive crowd just rushes the player and overwhelms them with simultaneous hits. The genre's answer - pioneered by the Arkham games - is to impose order on the crowd so each strike is legible, and to give the player a clean defensive verb to answer it.
The goal was to reproduce that readable pressure: enemies that circle and posture realistically, a strict turn-taking system so attacks arrive one at a time, and a tight parry-or-evade window that makes defending feel like a read rather than a reflex-mash.
⭕
Order the crowd
A player-anchored ring of attack slots spaces enemies evenly so they surround without clumping.
🎟️
One strike at a time
An attack token gates who may swing, so the player is never hit by two enemies on the same frame.
⚡
Make defence a read
Telegraphed windows let the player parry or evade based on timing, not luck or button-mashing.
⭕ The Attack Slot Ring
The heart of the system is a ring of combat slots arranged in a circle around the player. Each slot is a small struct - S_Combat_Slot - holding a world location, the occupying enemy (if any), and an angle around the ring. Enemies don't walk straight at the player; they reserve a slot and move to its location, which keeps them evenly spaced.
Slot Angle (built once on startup)
angle[i] = i × ( 360 ÷ slotsNum )
A For Loop runs slotsNum times, building one slot per step and adding it uniquely to the ring.
θ = angle in radians (D2R). Re-anchored every 0.2s so the ring follows the player as they move.
Reserve the Nearest Free Slot
When an enemy wants to engage, it asks the player's combat component for a slot. The request loops the ring, skips occupied slots, and - among the free ones - picks the slot nearest to the enemy's current position by comparing distances. That enemy is written into the slot and the slot index is returned, so attackers naturally claim the closest opening rather than crossing the arena.
⭕ See It Move
A simplified top-down view of the ring. Adjust the number of slots and watch the attackers redistribute evenly around the player. The highlighted slot is the one currently holding the attack token.
Live Slot Ring
Player Reserved slot Attack token
Attack Slots
Slots
6
around the player
Spacing
60°
between slots
Attackers
1
holding the token
🧠 The Enemy State Tree
Each enemy runs a State Tree that walks it through the encounter loop. The states gate cleanly off the slot system and the attack token, so the AI is mostly bookkeeping around two shared resources.
State
What It Does
Exits When
Combat State
Switches the upper body to a combat stance, focuses the player, requests a slot.
Slot is granted
Chase
Moves to the reserved slot's world location.
Slot reached
Wait
Idles for 3.0s ±0.5 for natural, staggered pacing.
Timer elapses
Wait for Attack
Polls the player's attack token each tick.
Token is free & claimed
Attack
Fires the attack event - warp toward player, play the strike montage.
Montage done
Wait After Attack
Recovery idle of 2.0s.
Timer elapses
Stop Attack
Releases the token so another enemy can strike, then re-requests the nearest slot.
Loops to Combat
The Attack Token
A single Getting Attacked? boolean on the player's combat component acts as a one-slot mutex. In Wait for Attack, each circling enemy reads it every tick; while it's true, everyone holds. The moment it frees, the next enemy to tick claims it - which enemy wins is effectively random, giving the crowd an organic, unscripted feel. Stop Attack sets it back to false, handing the turn to the pack.
⚡ The Defensive Read
On the player's side, every incoming attack opens a parry window via an anim notify on the enemy's strike montage. When the player presses the counter input, the system reads that window and branches:
A
Window open → Parry
The player warps to face the attacker (Find Look at Rotation), plays the evade animation, then chains into a counter-punch via the Evade_Punch section - landing the hit in the attacker's direction.
B
Window closed → Evade
If the input lands outside the telegraph, the player only gets the plain evade animation - a dodge with no counter. Mistime it entirely and the hit connects.
Hitboxes Gated by Notify
Both the player's and the enemies' fists carry hitbox colliders that are disabled by default. An anim notify state enables them only during the active frames of a punch and disables them again on the way out - so damage registers on contact during the swing, not on overlap at any other time.
📜 Where the Logic Lives
The shared combat state - the slot ring and the attack token - lives in BPC_Player_Combat, a component on the player.
Putting it on the player means every AI can reach it through a single reference, so there's one source of truth for the ring rather than per-enemy bookkeeping. Right Click on Image > Open image in New tab to view them in full resolution.
Slots around the player
Slots are created when the Event Play Begins and updated for every 0.2 secs
Slots are added around the player (360°) when the game begins
Slots are updated every 0.2 through Set Timer by Event node
1
Add Slots - on startup
A For Loop over Slots Num builds the ring once. Each step computes angle = i × (360 / Slots Num), makes an S_Combat_Slot, and Add Uniques it to the slots array.
2
Update Slot Positions - every 0.2s
A Set Timer by Event loops every slot and recomputes its world location around the player with D2R → COS/SIN × RingRadius, writing it back via Set Array Elem. The ring tracks the player as they move.
3
Request Slot - called by the AI
Loops the ring, skips slots with an Occupying Enemy, and among the free ones picks the nearest by Distance (Vector). Writes the requesting enemy into that slot and returns its index. If none are free, returns -1.
4
Set Combat Mode Hands - on entering combat
EnterState casts to Enemy_Human_Base, calls Switch Weapon → Hand to swap the upper-body stance, and SetFocus on the AI controller toward the player.
5
Attack - motion-warped strike
Stops movement, marks self as the attacking actor, builds a normalised direction toward the player, then Add or Update Warp Target ("Attack Target") with Find Look at Rotation and Play Montage - so the lunge homes onto the player instead of whiffing into empty space.
6
Parry-face & counter
Locks movement, enables Use Controller Rotation Yaw, warps to a ParryFacing target via Find Look at Rotation, plays the evade montage, and on notify branches into the Evade_Punch section with Set Control Rotation toward the enemy so the counter lands in the right direction.
Blueprint Captures
The graphs behind each step. Drop the captures into ./projects/standalone-melee-combat/ to populate these slots.
Blueprints for Requesting a slot and attacking the player
🎯
Parry Graph
Gameplay showing Parry → Parry → Attack
🎯 Design Philosophy
A few intentional choices shape how the brawl feels moment to moment.
⭕
Order Over Chaos
The slot ring is invisible to the player but does the heavy lifting - it spaces attackers so the crowd reads as threatening, not as a pile-on.
🎟️
One Strike at a Time
The attack token guarantees the player is never double-hit on a frame. Pressure comes from sequence and timing, not from raw simultaneity.
⏱️
Telegraph Everything
Every strike opens a readable parry window. Defence is a skill expression - a read on the animation - rather than a panic input.
🎲
Organic Randomness
A ±0.5s wait and a first-come token race mean no two fights play out identically. The choreography is rules-based but never scripted.
Reference Study
The system draws on three touchstones. Batman: Arkham for the core grammar - the slot ring, the single-attacker cadence, and the counter prompt as the defining defensive verb. Mad Max for the grounded, weighty feel of the strikes and the way a brawl reads as desperate rather than balletic. Marvel's Spider-Man for the use of motion warping to make attacks and counters connect cleanly across distance, so a lunge never visibly whiffs.
Tuning Levers
The whole encounter feel can be re-tuned from a handful of fields. Slots Num and Ring Radius set how crowded and how close the fight is. The Wait timer (3.0s ±0.5) controls aggression pacing, and the parry-window notify length sets how forgiving the defence is. Widen the window for an accessible brawler; tighten it for a punishing one - all without touching the underlying logic.
Possible Extensions
The next step is unparriable attacks - strikes that open no parry window and instead must be evaded, forcing the player to read which defensive verb to use, not just when. From there: enemy archetypes that ignore the token rules (a grabber, a shielded enemy), and combo strings to break the move-variety ceiling.
📚 What I Learned
Building this surfaced lessons worth keeping for the next combat system.
⭕
Nearest-slot routing
Having enemies reserve the closest free slot - rather than a fixed assignment - made the crowd movement read as natural and intelligent with very little code.
⏱️
Reactions & windows
Tying attacks to telegraphed notify windows is what turns a brawl into a conversation. The window is the whole game - get its length right and the fight feels fair.
🎯
Warp = clean contact
Motion warping toward the target removed almost all the "whiffed into air" jank for free - a small node with a huge payoff in how grounded hits feel.
🚫 Things I Do Not Like
Honest critiques - the parts I'd rebuild given another pass.
⚠ Move variety is thin
The biggest limitation is animation count. With a small library, both the player and enemies recycle the same few strikes, so extended fights start to feel repetitive. Combos - chained strings that branch on continued input - would add the rhythm and variety the system is currently missing, and the architecture already supports them; it's the animation set that's the bottleneck.
🧭 Next Steps
Where this goes from here.
1
Unparriable attacks
Add strikes that open no parry window and must be evaded instead. This forces the player to read which defensive verb to use - parry vs. evade - turning defence from a timing check into a genuine decision.
2
Combo strings
Expand the animation set and add input-branching combos for both attacker and player, breaking the move-variety ceiling that limits longer fights today.