Hack & Slash Combat System
A character action combat prototype in Unreal Engine 5, built in C++ rather than Blueprint. Attacks chain through animation-driven combo windows, with hit detection, camera behaviour and feedback split into independent components.
my role
Solo project. I wrote all 30 gameplay classes across the combat, character and UI modules, roughly 8,900 lines of C++. The project was scaffolded from Unreal's Third Person template, so the Variant_ folders in the repository are stock engine sample content rather than my work.
the problem
Character action combat lives or dies on timing. A combo has to accept the next input during a specific span of an animation and reject it outside that span, hits have to register on the frames where the blade is actually swinging, and the whole thing has to stay readable when the player is chaining four attack types together in the air. Driving that from a tick-based state machine means duplicating animation timing in code and re-syncing it every time an animator touches a montage. The design goal was to make the animation itself the source of truth for timing, so combat feel could be tuned on the timeline rather than in C++.
approach
- 01Four distinct attack types drive the move set through a single component API: light, heavy, air and rising, each entered through its own Try* call so input handling stays out of the combat logic.
- 02Combat behaviour is split across single-responsibility components rather than accumulating on the character class: HSCombatComponent for state, HSStyleComponent for scoring, HSAttackMagnetComponent for target acquisition, HSHitFeedbackComponent for impact response, and HSDynamicCameraComponent for framing.
- 03Timing is animation-driven rather than tick-driven. Seven custom AnimNotify and AnimNotifyState classes call into the component from the montage timeline: OpenComboWindow and CloseComboWindow bracket the span where the next input is accepted, with a separate delayed-window pair for moves that chain later in the animation.
- 04Sword traces fire from a notify state spanning only the active frames of a swing, so hit detection is bound to the animation rather than polled every frame. The measured cost of those overlap queries is 0.02 ms.
- 05Aerial combat runs on a hold loop, taking button-held and falling state each frame so juggles can be sustained mid-air and resolved cleanly on landing through an OnOwnerLanded hook.
- 06The attack magnet performs a timed slide toward a valid target during attack startup rather than snapping, so directional input reads as intentional without teleporting the character.
- 07A seven-tier style ranking system scores varied, chained attacks and drives its own HUD widget, alongside damage numbers, a lock-on reticle and XP orbs.
trade-offs
- 01Timing lives on the montage timeline, not in code. That makes combat feel tunable without a recompile, but it also means the system is only as correct as its notify placement: a mis-set notify produces a combo window that silently never opens, and nothing in C++ will catch it.
- 02Splitting behaviour across five components keeps each one small, but state that spans them (an attack that is simultaneously scoring style, magnetising toward a target, and driving the camera) has to be coordinated through the character rather than living in one place.
- 03Attack entry points are exposed as separate BlueprintCallable functions per attack type. That keeps input handling decoupled, but adding a sixth attack type means touching the component's public API rather than passing data.
measured results
Measured with stat unit and stat game in a standalone Development build at native 2560x1440, RTX 4080 SUPER / Ryzen 7 9800X3D, with AI logging enabled. The frame was GPU-bound at the time of capture (Draw 28.22 ms against a 28.22 ms frame), so gameplay was roughly 18% of frame cost and further optimising combat code would not have moved frame rate. The near-zero Blueprint time reflects the system being written in C++ rather than scripted. These are Development-build numbers and therefore pessimistic; a packaged Shipping build would be lower.