// docs/gameplay-moveset-system/combo-graph.md
READ

Combo Graph

Author chains visually — node types, transitions, conditions, motion inputs

The Combo Graph is the system's authoring surface. You drop nodes for each step in a chain, connect them with transitions, and configure each transition's input + conditions + priority. The runtime walks this graph one node at a time as inputs arrive, dispatching events to whatever Action Modules are attached to the active node.

Creating a graph

Content Browser → Add → Gameplay → Moveset → Combo Graph. Open it — the graph editor opens with a Root node already placed.

The graph auto-compiles on every property edit and on asset load. The toolbar's Compile button forces a recompile (rare; useful only after large refactors).

Node types

Attack Node

The bread-and-butter node. Each Attack Node represents one attack step:

  • AttackTagFGameplayTag identifier (e.g. Moveset.Attack.Primary.A). The runtime feeds this to the active Animation Chooser to resolve the actual UAnimMontage. Same tag → different montage per weapon.
  • ModulesTArray<FActionModuleEntry> of per-event Blueprint modules attached to this node. See Action Modules.

Root Node

The graph's entry point. Combo cursor starts here on every Apply Moveset and on every Reset Combo. Root supports Modules (e.g. for Moveset.Event.Ready).

Reset Combo Node

A terminator that loops the cursor back to Root without terminating the moveset. Use it at the end of a chain that should loop:

Root → Attack.A → Attack.B → Attack.C → Reset Combo (back to Root)

If the player keeps pressing during each Combo Window, the chain loops indefinitely. If they stop, Attack.C plays out naturally and Moveset.Event.ComboDropped fires when its montage ends.

Transitions

Click any edge between nodes to configure its transition. Fields:

Field Purpose
RequiredInput.RequiredAction The UInputAction that must fire. Auto-binding listens to this action while the source node is the cursor.
RequiredInput.RequiredTriggerEvent Started, Triggered, Completed, Canceled (default Started).
RequiredInput.RouteTag Optional FGameplayTag route — useful when the same Input Action drives multiple paths (light vs. heavy).
RequiredInput.MotionInput Optional motion-input pattern (see below). The press is only valid if the buffer matches the registered pattern.
Conditions FGameplayTagQuery evaluated against the component's identity tags. Transition is skipped if it fails.
Priority Higher priority is checked first. Use to layer specialised transitions (dodge attack) above the default.

The runtime evaluates outgoing transitions of the current cursor in priority order on each input. The first transition whose RequiredInput matches and whose Conditions pass wins.

Branching with conditions

Conditions let one input drive different attacks based on game state:

Root → [Light, Conditions: HasTag(State.Dodging)]   → DodgeAttack    (Priority: 10)
Root → [Light, Conditions: HasTag(State.Airborne)]  → AerialAttack   (Priority: 10)
Root → [Light, no conditions]                       → NormalAttack   (Priority: 0)

Your dodge system calls MovesetComponent->AddTag("State.Dodging") while the dodge is active. The combo graph picks the highest-priority transition whose conditions are satisfied. When no special-case transition applies, the unconditional NormalAttack wins.

Route tags

When the same Input Action backs multiple branches (e.g. one button cycles through stances), use RouteTag to disambiguate:

Attack.A → [LMB, RouteTag: Heavy] → Attack.HeavyB     (player set "Heavy" stance tag)
Attack.A → [LMB, RouteTag: Light] → Attack.LightB     (player set "Light" stance tag)

The active route tag is whatever your stance system feeds to MovesetComponent::SetActiveRouteTag.

Motion inputs (fighting-game style)

Register a directional pattern on the InputBufferComponent (the Apply Moveset action creates one alongside the MovesetComponent — FindComponentByClass<UMovesetInputBufferComponent>):

FMovesetMotionInputPattern QCF;
QCF.PatternTag = FGameplayTag::RequestGameplayTag("Moveset.MotionInput.QCF");
QCF.DirectionalSequence = { {0,-1}, {1,-1}, {1,0} };  // Down → Down-Forward → Forward
QCF.Tolerance = 0.35f;
QCF.MaxDuration = 0.3f;

InputBuffer->RegisterMotionPattern(QCF);

In the combo graph, set the transition's RequiredInput.MotionInput.PatternTag to Moveset.MotionInput.QCF. The transition only fires when the press lines up with the recorded directional sequence within the configured tolerance and duration.

You can register any number of patterns. Common picks:

Pattern Sequence
QCF Down → Down-Forward → Forward
QCB Down → Down-Back → Back
DP Forward → Down → Down-Forward
HCF Back → Down-Back → Down → Down-Forward → Forward
360 Any → six neighbouring directions tracing a full ring

Tolerance is the dot-product slack on each step (0 = exact direction, 1 = any direction). MaxDuration caps how long the whole sequence can take from first to last input.

Combo Window

The Combo Window AnimNotifyState is what makes attacks feel responsive. Inside the window:

  • A press is buffered rather than executed immediately.
  • The buffered input is replayed against the cursor when the window closes.
  • The replay cancels the remainder of the current montage and plays the next attack from the start.

Outside the window, presses still hit TryComboAdvance but typically fail (the cursor isn't at a node whose outgoing transitions match the input). They fall into the input buffer and are evaluated again on the next valid eligibility check.

Place the Combo Window starting from the late part of the active animation (after the hit has landed but before the recovery returns to idle). A typical authoring pattern:

Frame    0   ─────────────[ Hit window ]──────────[ Combo Window ]──── End
                                                  ↑                  ↑
                                                  press → buffer    resolve buffered input

If you want to allow cancelling EARLIER than the natural recovery, extend the Combo Window left. If you want strictly serial chains, leave a gap between the hitbox and the window so presses during the active hit get dropped.

Combo Point

Moveset: Combo Point is a one-shot AnimNotify (not state). Place it on a montage timeline at the moment you want a feedback hook to fire — typical use cases:

  • "Perfect timing" rhythm-style indicator on the UI.
  • Audio cue scrubbing for chained-attack confirmations.
  • Ability points / meter increment.

When the notify fires, Moveset.Event.ComboPoint dispatches to Action Modules on the active node. No special routing required — wire it to whatever you want.

Authoring tips

  • Compile errors surface inline. A transition with no RequiredAction and no MotionInput is unreachable; the editor flags it red. A node with no outgoing edges is a chain terminator (intentional or not — the editor doesn't warn, but you'll see OnComboDropped fire after that node's montage ends).
  • Keep priorities sparse. Use 0 / 10 / 100 instead of consecutive integers — leaves room to insert specialised transitions later without re-numbering everything.
  • Share graphs across weapons. Two MovesetDefinitions referencing the same UComboGraphAsset get cross-weapon combo continuity for free. See Modes.
  • Reset Combo vs. dead-end. Reset Combo loops; a node with no outgoing edges drops the chain. Pick deliberately — the difference is whether OnComboDropped fires immediately at the end of the last attack (dead-end) or whether the player can keep pressing into a fresh chain (reset).