// docs/gameplay-moveset-system/architecture.md
READ

Architecture

How the runtime turns a button press into an authoritative hit

Gameplay Moveset System ships as two C++ modules: MovesetRuntime (everything the cooked game needs) and MovesetEditor (UncookedOnly — graph editor, factories, asset definitions). Together they implement a state machine that resolves "which attack should play right now" from input + combo state, executes the attack as a montage with hitbox sweeps, and replicates the relevant state across all network roles.

This page walks the data flow start to finish, then pulls each subsystem apart.

Layered view

┌─────────────────────────────────────────────────┐
│                    YOUR GAME                     │
│   Damage System, VFX, Sound, Movement, Camera    │
│           ↑ OnHit, OnAttackStarted, etc.         │
│           ↑ Action Modules (per-node BPs)        │
├─────────────────────────────────────────────────┤
│              MOVESET SYSTEM                      │
│                                                  │
│  MovesetComponent (state machine + replication)  │
│       ├── ComboGraphInstance (cursor + advance)  │
│       ├── InputBufferComponent (motion patterns) │
│       ├── ActiveHitboxes (registered by ANS;     │
│       │   sweep + dispatch in TickComponent)     │
│       └── DispatchMovesetEvent → Action Modules  │
│                                                  │
│  MovesetDefinition (DefaultMode + AlternateModes)│
│  ComboGraphAsset  (compiled node/transition data)│
│  AnimNotifyStates (Combo Window + Hitbox)        │
├─────────────────────────────────────────────────┤
│              UNREAL ENGINE                       │
│  AnimMontage, Enhanced Input, GameplayTags,      │
│  Chooser Tables, Subobject Replication           │
└─────────────────────────────────────────────────┘

The plugin owns the middle layer. Your game owns the top layer (cosmetic + numerical decisions). The bottom layer is unmodified UE — the plugin doesn't fork or extend any engine class, only registers AnimNotifyStates, components, asset types, and a chooser variant.

Request lifecycle: press → hit

Trace one frame from "the player presses LMB" to "the enemy takes damage":

  1. Enhanced Input fires IA_PrimaryAttack (Triggered) on the locally-controlled pawn.
  2. Auto-binding callback on MovesetComponent builds an FMovesetInputCommand { RequiredAction = IA_PrimaryAttack } and calls TryComboAdvance.
  3. ComboGraphInstance evaluates the current cursor's outgoing transitions in priority order. The first transition whose RequiredInput matches and whose Conditions (FGameplayTagQuery) pass wins.
  4. If the previous attack's Combo Window is still open, the input is buffered. When the window closes (montage notifies NotifyEnd on the Combo Window ANS), the buffered input is replayed against the cursor and steps 3-7 happen for that input.
  5. The chosen transition's TargetAttackTag is resolved to a UAnimMontage through the active FMovesetModeData::AnimationChooser (Chooser Table). The chooser receives Owner + the attack tag in its context container.
  6. Internal_ActivateAttack plays the montage on the character's USkeletalMeshComponent, sets CurrentAttackTag (replicated to simulated proxies), and dispatches Moveset.Event.AttackStarted to any Action Module attached to the new node.
  7. Moveset: Hitbox AnimNotifyState fires NotifyBegin at its placed timeline position. The component registers the hitbox (geometry from FMovesetModeData::Hitbox, attached to the resolved socket) and starts sweeping it in TickComponent.
  8. Each frame the sweep finds candidates, runs the filter pipeline (per-target cooldowns, max-hits, ignore-tags, owner exclusion), and for each survivor calls ReportHitOnHit delegate fires + Moveset.Event.Hit dispatches to Action Modules.
  9. Your damage / VFX code reacts through one of those hooks. The plugin doesn't touch numbers.

Every step has a fallback: hitbox notifies that fire on a coalesced server tick, swap requests during an open combo window, missing chooser entries, missing component on the actor — all are handled with logged warnings, not crashes.

The agent loop, in one paragraph

MovesetComponent is a finite state machine. Its state at any moment is (ActiveSelection, CurrentAttackTag, ComboCursor, BufferedInput, ActiveHitboxes). Inputs and tick events transition between states. Replication mirrors (ActiveSelection, CurrentAttackTag) — that's the minimum the simulated proxy needs to play the cosmetic montage. The autonomous proxy keeps its own predicted CurrentAttackTag and an authoritative copy of everything else from OnRep_ActiveSelection.

Networking model

  • MovesetComponent is a CDO/BP-added stably-named replicated sub-object. It does not get spawned at runtime.
  • ActiveSelection (FMovesetActiveSelection { Definition, Mode }) replicates atomically with an OnRep that auto-initialises the local component on remote sides. This means a server-only Apply Moveset causes autoproxies / simproxies to set themselves up via the OnRep — no extra wiring.
  • CurrentAttackTag replicates with COND_SkipOwner. Simulated proxies receive it through OnRep_CurrentAttack and play the matching montage locally. The autonomous proxy keeps its own predicted value.
  • Server_DispatchInput RPC mirrors autoproxy presses to the authority — necessary because the server's view of a client-controlled pawn has no UEnhancedInputComponent.
  • Server_InitializeFromClient RPC ensures the authority initialises when Apply Moveset only runs on the client.
  • Hitbox sweeps run on Authority and AutonomousProxy. Simulated proxies don't simulate sweeps — they're cosmetic only.
  • Authority-side hitbox coalescing. On Auth the server's pose tick can be coarser than animation-frame resolution, so a hitbox ANS that lasts 8 animation frames can collapse into 8 atomic NotifyBegin → NotifyEnd pairs. The component coalesces same-key registrations into one long-lived hitbox and defers unregister by a couple of TickComponent passes, so the sweep pipeline accumulates real Prev → Curr deltas. Automatic, no project-side wiring.

The result: all three call patterns (server-only, client-only, both-parallel) work without disconnects or special-casing.

See Replication for the full failure-mode catalogue and how each is contained.

Key classes

Class Purpose
UMovesetComponent Core state machine — attacks, combos, hitbox sweep + dispatch, event routing, replication.
UMovesetDefinition Data asset: DefaultMode + AlternateModes map of FMovesetModeData.
UComboGraphAsset Compiled combo structure (edited via the visual node graph).
UComboGraphInstance Runtime cursor + transition matching.
UMovesetInputBufferComponent Input recording, buffer querying, motion-input pattern matching.
UAsyncAction_ApplyMoveset Blueprint entry point. Single OnMovesetEvent pin (discriminate by Payload.EventTag).
UActionModule Abstract Blueprint base for tag-routed per-node modules.
UAnimNotifyState_MovesetComboWindow Combo Window on a montage timeline.
UAnimNotifyState_MovesetHitbox Hitbox slot binding on a montage timeline.
UAnimNotify_MovesetComboPoint Combo point marker (UI feedback hook).
IMovesetAnimProvider Optional interface — actor provides its USkeletalMeshComponent to the system.
IMovesetMeshSourceProvider Optional interface — actor resolves a MeshSlot tag to a SceneComponent (e.g. equipped weapon).
IMovesetHitTarget Optional interface on hit candidates — provides per-target ignore tags for filter gating.
UMovesetSettings Project-wide settings (UDeveloperSettings).

Key structs

Struct Purpose
FMovesetActiveSelection { Definition, Mode } — replicated atomically, the source of truth for "what's running on the component".
FMovesetModeData { ComboGraph, AnimationChooser, Hitbox, Config } — one per Mode of a Definition.
FMovesetHitboxData Hitbox geometry + filter parameters (shape, extent, socket, channel, max hits, cooldowns, ignore-query).
FMovesetHitResult Hit payload: HitActor, HitLocation, HitNormal, NativeHit, AttackTag.
FMovesetEventPayload Unified event payload: EventTag, AttackTag, PreviousAttackTag, HitResult, FailureReason.
FMovesetInputCommand Combo input descriptor: RequiredAction, RequiredTriggerEvent, RouteTag, optional MotionInput.
FMovesetMotionInputPattern { PatternTag, DirectionalSequence, Tolerance, MaxDuration } — registered on InputBufferComponent.
FMovesetConfig Per-mode override knobs (input buffer size / window, combo reset time, …). Negative = inherit project setting.

Project Settings

Edit → Project Settings → Plugins → Moveset System:

Setting Default Description
Default Buffer Size 30 Input buffer capacity (frames).
Default Buffer Window 0.5s How long inputs stay valid in the buffer.
Default Motion Input Window 0.3s Time window for motion-input sequences.
Default Combo Reset Time 2.0s Time after last input before the combo resets and OnComboDropped fires.
Default Trace Channel ECC_Pawn Channel used by hitbox sweeps when their bUseProjectDefaultTraceChannel flag is set.
Max Hitbox Step Distance 500.0 Per-tick prev → curr socket distance cap (cm). Sweeps exceeding this skip the current tick (teleport guard). 0 = off.
Draw Hitboxes false Debug-draw active hitboxes every tick.
Hitbox Active Color red Wireframe colour when a hitbox is active and didn't hit anything this tick.
Hitbox Impact Color yellow Wireframe colour flashed for one frame when the hitbox registers a hit.