Back to projects
HSCombatComponent.cppC++ · Unreal Engine 5

Hack & Slash Combat System

A character action combat prototype in Unreal Engine 5, written in C++ instead of Blueprint. Attacks chain through combo windows that live on the animations, and hit detection, the camera and feedback are each their own component.

AnimNotifyCombo windowsInput bufferingSword tracesStyle ranking

My role

Solo project. I wrote all 30 gameplay classes across the combat, character and UI code, about 8,900 lines of C++. I started from Unreal's Third Person template, so the Variant_ folders in the repo are Unreal's sample content, not mine.

The problem

Combat feel comes down to timing. The next attack should chain if you press during the right part of the animation, hits should only land while the sword is actually swinging, and it all has to keep working when you're chaining attacks in the air. If that timing lived in code, I'd be updating numbers every time an animation changed. So I put the timing on the animations themselves.

How it works

  • Four attack types (light, heavy, air and rising), each with its own Try function on the combat component, so the input code doesn't need to know combat rules.
  • Combat is split into components instead of one giant character class: HSCombatComponent for combat state, HSStyleComponent for scoring, HSAttackMagnetComponent for targeting, HSHitFeedbackComponent for hit reactions, and HSDynamicCameraComponent for the camera.
  • Seven custom AnimNotify classes call into the component from the animation timeline. OpenComboWindow and CloseComboWindow mark when the next attack can chain, and a second pair handles moves that chain later in the animation.
  • Input buffering: if you press attack before the window opens, the press is saved and fires as soon as the window opens, so pressing a little early still chains instead of getting dropped.
  • Sword traces run from a notify state that only covers a swing's active frames, so hits are tied to the animation. Those overlap checks measured 0.02 ms.
  • Air combos keep going while the button is held and the character is falling, and wrap up on landing through an OnOwnerLanded hook.
  • The attack magnet slides the character toward a target during the wind-up instead of snapping, so it doesn't look like teleporting.
  • A seven-rank style meter scores varied attacks and drives its own HUD widget, along with damage numbers, a lock-on reticle and XP orbs.

Trade-offs

  • Since timing lives on the animations, a notify placed wrong means a combo window that never opens, and nothing in the C++ will warn you. You find it by playing.
  • Splitting combat into five components keeps each one small, but when one attack involves several of them at once (style, magnet and camera), the character has to coordinate them.
  • Each attack type has its own function on the component. That keeps input simple, but adding another attack type means adding another function instead of just adding data.

What I measured

Sword trace overlap checks0.02 msper frame
Whole gameplay thread5.20 msall systems

Measured with stat unit and stat game in a standalone Development build at 2560x1440 on an RTX 4080 SUPER and Ryzen 7 9800X3D, with AI logging turned on. In that capture the GPU took 28 ms, the whole frame, so the GPU was what limited the frame rate and the gameplay code was about 18% of the frame. Making the combat code faster wouldn't have raised the frame rate.