# Action Graph

> An Action Graph is a character's moveset: each node plays a move, a transition names the input that leads to the next, and a window on the animation says when that input counts.

Markdown version of https://rigbak.com/docs/universal-player-combat/action-graph, a page of the Universal Player Combat documentation. Link that page when citing it.

## Chain two attacks on one button (5 minutes)

1. **Create a graph with two moves**

   Right-click in the Content Browser, **UPC > Actions > Action Graph**, name it `AG_MyFirstGraph` and open it: the canvas holds **ROOT**, the character standing idle. Right-click the canvas, **Add Action Node**, twice. Name the cards `Light 1` and `Light 2` (**Node Name**) and give both the same **Montage**, a light attack of your own (`AM_Attack_Light` here).

   In **AG_MyFirstGraph**, under Action Node Data, set:

   | Property | Value |
   | --- | --- |
   | Node Name | Light 1 (replaces None) |
   | Montage | AM_Attack_Light |
2. **Wire the presses**

   Drag from **ROOT**'s right edge to **Light 1**'s left edge, select the router on the wire, and set its **Input Tag** to `UPC.Input.LightAttack`; **Fires On** already reads **Input Pressed**. Wire **Light 1** to **Light 2** the same way. A move with nowhere to go returns to ROOT when it ends.

   Graph nodes:

   - **ROOT**
   - **Light 1** (montage AM_Attack_Light)
   - **Light 2** (montage AM_Attack_Light)

   Connections:

   - ROOT to Light 1, through UPC.Input.LightAttack (pressed)
   - Light 1 to Light 2, through UPC.Input.LightAttack (pressed)

   *The finished graph: a light attack press leaves Root for Light 1, and another leads on to Light 2.*
3. **Mark when the next press counts**

   On `AM_Attack_Light`, add the **UPC Action Window** notify state over the frames where the next hit may start. An earlier press waits for it (one at a time); with no window, it waits for the montage's end.

   **Timeline: AM_Attack_Light** (1.6 s long). Tracks:

   - **Notifies**: UPC Action Window (0.7 s to 1.15 s)
   - **Light attack**: pressed (at 0.42 s); Light 2 (at 0.7 s)

   *The press waits until the window opens, then Light 2 starts.*
4. **Give the graph to an item and play**

   A drawn item hands its graph to the character through **Action Graph** on its **[Equippable](https://rigbak.com/docs/universal-player-combat/items#equippable-drawing-it)** fragment. Use the demo's fists, `Item_UPC_Unarmed` (`Demo/Weapons/Unarmed`; tick **Show Plugin Content**): the demo player fights with them while nothing else is drawn, sending `UPC.Input.LightAttack` from the left mouse button (right shoulder on a gamepad).

   A character of your own needs an Ability System Component on its PlayerState or pawn ([UPC Character Base](https://rigbak.com/docs/universal-player-combat/character-setup) has one). Save, open `Demo/Maps/LVL_UPC_Demo`, press Play and click twice. In play, **Compile** or a save hands an edit to the character; after adding or deleting a card, restart Play. Keep `AG_MyFirstGraph` on the fists: the [Action Graph Editor](https://rigbak.com/docs/universal-player-combat/action-graph-editor) and [Gates, Costs and Cooldowns](https://rigbak.com/docs/universal-player-combat/gates-costs-cooldowns) quick starts build on it.

   In **Item_UPC_Unarmed**, under Equippable, set:

   | Property | Value |
   | --- | --- |
   | Action Graph | AG_MyFirstGraph (replaces AG_UPC_Unarmed) |

The graph deals no damage; a swing lands through a melee trace window on its montage ([Melee Trace](https://rigbak.com/docs/universal-player-combat/melee-trace)).

---

## Chain moves into a combo

### Mark the window on the montage

The **UPC Action Window** notify has no settings. With no window, **When No Window Is Authored** (node **Execution**) decides: **At Completion** (default) waits for the montage's end, which suits a combo cut into montage sections; **From Entry** lets the press cut in at once.

### Early and late presses

Select empty canvas to set these on the graph; a node overrides the first two under **Execution**, **Advanced**.

| Setting | Does |
| --- | --- |
| **Default Input Buffer Mode** | Which press made before the window waits: **Accept Last** (default) the newest, **Accept First** the first, **Ignore** none. |
| **Late Input Grace** | A press up to 0.25 s after a move runs to its end still follows its links (a click 0.1 s after Light 1 ends plays Light 2). 0 turns it off. |
| **Held Input Restart Window** | Not a hold setting. A press only ROOT answers, such as a dodge mid-swing, waits for the move to end, then starts; it expires this long after the press (0.35 s: 0.3 s before the swing ends it plays, 0.5 s before it is forgotten; 0 never expires). Once the swing's action window is open, it drops instead. |

**Details panel: AG_MyFirstGraph** (data asset, in Action Graph Editor)

**Category: Action Graph**

| Property | Value |
| --- | --- |
| Default Input Buffer Mode | Accept Last |
| Held Input Restart Window | 0.35 |
| Late Input Grace | 0.25 |

*Select nothing on the canvas and the Details panel shows the graph's own settings.*

---

## Choose what a node plays

Pick one **Payload** per node. The node lasts as long as its payload runs; with none, it ends at once.

| Payload | Pick it for |
| --- | --- |
| **Play Montage** | Attacks, dodges, any animated move. New nodes start with it. |
| **Hold** | A stance with no animation, such as a block. Lasts until a transition, a cancel or a [hook](https://rigbak.com/docs/universal-player-combat/action-graph#add-logic-to-a-node-hooks) ends it. |
| **Delay** | A pause inside a combo: **Delay Seconds** (0.5), then Action Completed. |
| **Trigger Ability / Event** | A move run by a gameplay ability (a Blueprint ability from an ability set, [Items](https://rigbak.com/docs/universal-player-combat/items)): sends a gameplay event or activates abilities by tag. |
| **Play Paired Action** | A finisher or counter with a second character ([Paired Actions](https://rigbak.com/docs/universal-player-combat/paired-actions)). |

**Details panel: Light 1** (instanced object, in AG_MyFirstGraph)

**Category: Action > Action Node Data**

| Property | Value |
| --- | --- |
| Node Name | Light 1 |
| Costs | 0 Array elements |
| Gates | 0 Array elements |
| Hooks | 0 Array elements |
| Transitions | 1 Array element (greyed out) |

**Category: Action > Action Node Data > Execution**

| Property | Value |
| --- | --- |
| Payload | Play Montage |
| Montage | AM_Attack_Light |
| Play Rate | 1.0 |
| Section Name | None |
| When No Window Is Authored | At Completion |

**Category: Action > Action Node Data > Execution > Spacing**

| Property | Value |
| --- | --- |
| Stop Distance | -1.0 |
| Make Room Seconds | -1.0 |

**Category: Action > Action Node Data > Execution > Advanced (collapsed until clicked)**

| Property | Value |
| --- | --- |
| Input Buffer Override | Use Graph Default |
| Late Input Grace | 0.25 (greyed out, override unticked) |
| Retrigger From Root On Failed Start | false |

**Category: Action > Action Node Data > Cancellation**

| Property | Value |
| --- | --- |
| Can be Cancelled | false |
| Cancel Window Start | 0.1 |

**Category: Action > Action Node Data > State**

| Property | Value |
| --- | --- |
| Applied Tags | no tags |

**Category: Appearance (collapsed until clicked)**

| Property | Value |
| --- | --- |
| Use Custom Color | false |

*Select one action card and the Details panel shows that action. Transitions is read only: it fills from the wires when the graph compiles.*

---

## Cut a move short

|  | Interrupt transition | **Can be Cancelled** |
| --- | --- | --- |
| Wire | From the swing itself, **Timing** **Interrupt**: a parry or dodge out of a committed swing. | Wired only from Root, such as a Dodge. Ticked on the swing. |
| Effect | The press ends the swing and starts the transition's target. | An input the swing has no open transition for cuts it and runs from Root: a Dodge press plays the Root dodge. |
| Waits for | **Cancel Window Start** (0.1 s). | **Cancel Window Start**. |
| **Can be Cancelled** applies | No: an Interrupt ignores it and both lists. | Yes. **Cancellable by Inputs** limits cutting tags (empty: any input cuts); **Ignored Cancel Inputs** bars tags and beats the first list. |

Under **Can be Cancelled**, a move's own input cannot cut it, so mashing attack never restarts a swing. A press before Cancel Window Start waits like any other.

Graph nodes:

- **Light 1** (montage AM_Attack_Light)
- **Light 2** (montage AM_Attack_Light)
- **Parry** (montage AM_Parry)

Connections:

- Light 1 to Light 2, through UPC.Input.LightAttack (pressed)
- Light 1 to Parry, through UPC.Input.Guard (pressed)

*Two ways out of Light 1: the light attack waits for its action window, and the guard press, set to Interrupt, leaves for Parry at once.*

---

## Branch on more than the button

A transition's **Required Tags** and **Blocked Tags** read the character's tags; blocked beats required (a shield's moves require the drawn shield's tag).

**Conditions** must all pass. They gate a move and spend nothing; a cost on the target node spends ([Gates, Costs and Cooldowns](https://rigbak.com/docs/universal-player-combat/gates-costs-cooldowns)).

| Condition | Passes when |
| --- | --- |
| **Direction** | The stick pointed a chosen way at the press: a side step, a lunge. |
| **Vital Threshold** | A vital such as stamina is above, below or about a level. |
| **Target State** | The current target carries, or lacks, chosen tags: stunned, open to a riposte. |
| **Target Distance** | The current target is closer than, farther than or about a distance away. |
| **Cooldown Ready** | A cooldown has a charge ready. |
| **Paired Action Available** | A paired action from the listed sets can play now. |

Of several passing transitions on one input, the highest **Priority** wins, then the one with more conditions, then the first: a lunge with a Target Distance condition beats a plain strike.

---

## When a move ends or fails

Graph nodes:

- **Heavy** (montage AM_Heavy)
- **Recovery** (custom payload)
- **Out Of Breath** (montage AM_Tired)

Connections:

- Heavy to Recovery, through Auto
- Heavy to Out Of Breath, through Failed

*Heavy's two ways on: Auto when it finishes on its own, Failed when a cost on it could not be paid.*

- **Action Completed** leads on when the move finishes with no press waiting: a recovery after a heavy.
- **Action Failed**, wired out of a move that could not start (unaffordable cost, closed gate), leads to another move such as a stagger. Without it the press drops and a running move plays on.
- A move finishing with no press waiting and no Action Completed returns to Root.

**A held stance**: a press plays the raise, Action Completed leads to a **Hold** node whose **Applied Tags** carry `UPC.State.Blocking`, and **Input Released** leads from the hold to a **To Root** card (**Add Return To Root** on the canvas menu, [Action Graph Editor](https://rigbak.com/docs/universal-player-combat/action-graph-editor)). Wire the release out of the raise too, or letting go mid-raise stays in the guard.

Graph nodes:

- **ROOT**
- **Guard Raise** (montage AM_Guard_Raise)
- **Guard Hold** (custom payload)
- **To Root** (ends the action and returns to Root)

Connections:

- ROOT to Guard Raise, through UPC.Input.Guard (pressed)
- Guard Raise to Guard Hold, through Auto
- Guard Raise to To Root, through UPC.Input.Guard (released)
- Guard Hold to To Root, through UPC.Input.Guard (released)

*A held guard: letting go leads to To Root from the raise and from the hold.*

---

## The transition's settings

**Details panel: Transition Router** (instanced object, in AG_MyFirstGraph)

**Category: Transition**

| Property | Value |
| --- | --- |
| Entries | 1 Array element |
| Entries > Index [0] > Fires On | Input Pressed |
| Entries > Index [0] > Input Tag | UPC.Input.LightAttack |
| Entries > Index [0] > Priority | 0 |
| Entries > Index [0] > Timing | Wait For Window |
| Entries > Index [0] > Required Tags | no tags |
| Entries > Index [0] > Blocked Tags | no tags |
| Entries > Index [0] > Conditions | 0 Array elements |

*Select the router on a wire. Each entry in Entries is one transition with an output pin of its own.*

| Fires On | Fires at |
| --- | --- |
| **Input Pressed** | The Input Action triggering: the key going down, or a Hold trigger's time completing. |
| **Input Released** | A release after a press or a completed hold. |
| **Input Released Early** | A release before the trigger completes, such as before a Hold trigger's time. |
| **Action Completed** | The move's own end, with nothing chaining. |
| **Action Failed** | The move failing to start. |

**Input Tag**, **Priority**, **Timing** and **Conditions** show for the three input moments only.

---

## Settings on the Action Graph component

**Details panel: ActionGraphComponent** (component, in AUPCCharacterBase)

**Category: UPC > Action**

| Property | Value |
| --- | --- |
| Global Block Tags | UPC.State.ActionsBlocked |
| Action Speed | 1.0 |

**Category: UPC > Action > Debug**

| Property | Value |
| --- | --- |
| Debug Overlay Actions | false |

**Category: UPC > Action > Multiplayer (collapsed until clicked)**

| Property | Value |
| --- | --- |
| Dwell Grace Base Seconds | 0.1 |
| Max Server Inputs Per Second | 20 |

*Created by the character base, with the ability system it needs. A player or an enemy can carry one.*

| Setting | Does |
| --- | --- |
| **Global Block Tags** | Shuts the graph while the character carries one (a stagger, a cutscene): presses drop, releases pass. `UPC.State.ActionsBlocked` by default; empty turns it off. |
| **Action Speed** | Playback speed of the character's moves (1 as authored, 0.5 half); montages, delays and windows scale together, so a slower enemy keeps the same combo rules. |

A character on any move carries `UPC.State.ActionRunning`, unbroken across a chain; block an ability on it to keep it out of combos.

---

## Add logic to a node: hooks

A **hook** rides one node's lifetime. Add it to the node's **Hooks** list; each run gets its own copy.

| Hook | Does |
| --- | --- |
| **Locomotion Modifier** | Scales walk speed, acceleration and turn rate while the node runs: 1 no change, 0 none. |
| **Cancel On Movement** | Cuts the node when the character moves, to walk out of a recovery. Needs **Can be Cancelled**. |
| **Drain Vital** | Drains a vital each second the node runs. At zero, **End Behavior** finishes the node (default), cancels it or does nothing. |

---

## Drive a graph from Blueprint or AI

| Node | Does |
| --- | --- |
| **Process Action Input** | Sends one input tag and moment, as a button does; an AI attacks this way. Returns **Entered**, **Buffered**, **Dropped**, **Blocked** or **Forwarded**. |
| **Queue Input Sequence** | Feeds a list of inputs: **Chain** sends each as the last move starts, **Discrete** waits for idle. |
| **Set Action Graph**, **Clear Action Graph** | Swap the moveset; drawing an item calls them. |
| **Interrupt Action for External Ability** | Cuts the running move to Root whatever its cancel rules (a hit reaction, a cutscene). |
| **Get Action State** | Current node, window, time in the node, inputs it offers, any waiting press. |
| **On Action Node Changed**, **On Action Reset**, **On Action Cancelled**, **On Action Failed** | A node started, the graph returned to Root, an input cut a move, a move could not start. |

---

## Debug it

```
UPC.Debug.Actions 1            // Each character's graph, node, window and waiting press, above its head
Log LogUPCActionGraph Verbose  // The same in the log
```

**Debug Overlay Actions** on one character's component limits the overlay to that character. In Play In Editor the running card lights green ([Action Graph Editor](https://rigbak.com/docs/universal-player-combat/action-graph-editor#watch-a-character-run-the-graph)).

<details>
<summary>Payloads in detail (Advanced)</summary>

- **Play Montage**: **Play Rate** multiplies the montage's rate. **Section Name** picks the starting section (None or a missing name, which the compile reports, plays from the start). No Montage: the move ends at once with a warning.
- **Back Montage**, **Left Montage**, **Right Montage** replace Montage when the stick pushes that way against the body's facing as the move starts; empty plays Montage. **Turn To Input At Start** turns the body to the push and plays Montage while rotation follows movement; it needs the character base's Attack Approach component.
- **Stop Distance** and **Make Room Seconds** (Spacing) give an attack its own spacing, for a move whose reach differs from the weapon in hand, such as the demo's shield bash beside the sword (25 cm). Stop Distance wins over a window's, the weapon's and the style's; Make Room Seconds 0 turns the step back off for this move and above 0 turns it on under any style. -1, the default for both, follows the rest ([Attack Steering](https://rigbak.com/docs/universal-player-combat/attack-steering#where-the-stop-distance-comes-from)). Both need the character base's Attack Approach component.
- **Hold** and **Delay** open the action window as they start (**Open Action Window**, on), so an input leaves at once; a Hold with it off waits for a window only a hook can open. A Hold's **Applied Tags** stay on for the stance, for an anim layer to follow. A Delay of 0 ends at once.
- **Trigger Ability / Event**: **Send Gameplay Event** sends **Event Tag** with **Event Magnitude** and **Optional Object**; **Activate Ability By Tag** starts abilities carrying any **Ability Tags**. **End Immediately** (on) ends the node at once; off, it waits for a triggered ability, a cancel or a transition.
- **Retrigger From Root On Failed Start** (Execution, Advanced): a failed start reruns the press once from Root, so a targetless finisher becomes a new combo's first hit.

</details>

<details>
<summary>Payloads, conditions and hooks of your own (Advanced)</summary>

- **Payload**: a Blueprint child of `UPCActionPayload` overriding **Execute** joins the picker. Return true to keep the node alive (a montage it plays ends it), false to end it. With no montage, the component's **Open Action Window** and **Close Action Window** set the chain window.
- **Condition**: a Blueprint child of `UPCTransitionCondition` overriding **Evaluate** (and **Get Condition Display Name** for the router label).
- **Hook**: a Blueprint child of `UPCActionHook` receives **Enter Action**, **Exit Action** (with the end reason), **Tick Action** while **Tick** is ticked, and the montage's **Montage Notify** events. **Finish Node** (tick **Finishes Node**) ends its node; **Cancel Node** cuts it, honoring Can be Cancelled and Cancel Window Start; the graph picks the next node. A magnifier beside a Blueprint hook selects it in the Content Browser.

</details>

<details>
<summary>What happens to one input (Advanced)</summary>

An input enters at the top. The first stop that acts on it ends the walk.

1. **Global Block Tags** (Action Graph component): While any of these tags is on the character, a press drops and is not held for later. Releases still pass, so a held move can end.
2. **Pick the transition** (the current node's transitions): Transitions for this input and this moment compete when their Required Tags, Blocked Tags and Conditions pass: highest Priority, then most conditions, then first in the list.
3. **Interrupt** (the transition's Timing): A winning transition set to Interrupt fires at once when the running move is past its Cancel Window Start; the action window and the cancel rules play no part. A press made earlier goes on to the stops below.
4. **Cancel** (the node's Cancellation settings): A move marked Can be Cancelled, past its Cancel Window Start, that this input is allowed to cancel, is cut when it has no transition for the input or its window is closed. On Action Cancelled fires and the input runs again from Root.
5. **Chain** (Root, or an open action window): At Root, or while the running move's action window is open, the winning transition is taken.
6. **Wait** (Default Input Buffer Mode, or the node's Input Buffer Override): Otherwise the press waits in a one-press buffer, when this node or Root has a transition for it. It runs against this node when the window opens, or from Root when the move ends; a press only Root answers drops if the window opens first. The buffer mode decides which press is kept; anything else drops.

The input that started a move cannot cancel that move, so mashing the attack button cannot cut a swing back to its own start.

- The buffer rule covers presses; releases wait first come, first served. **Ignore** suits a move that should not queue the next attack, **Accept First** a charge. A waiting press that continues the combo counts however old.
- Only a move that ran to its end and returned to Root grants Late Input Grace (not one cut short or moved on through Action Completed); an input it has no link for starts from Root.
- Cancel Window Start limits cancels, Cancel On Movement and Interrupt transitions alike.
- A Global Block Tag present as a move completes sends it to Root, not along Action Completed.

| End | Means | What follows |
| --- | --- | --- |
| **Completed** | The montage or payload ended it. | A waiting press chains (from Root when only Root answers it), else Action Completed, else Root. |
| **Cancelled** | An input, hook or outside system cut it. | Root, no chain. |
| **Rejected** | The server refused a move the player's machine started. | The waiting press clears and the character returns to the server's node. |

</details>

<details>
<summary>The demo</summary>

- `AG_UPC_Sword`'s light combo chains At Completion: `AM_UPC_Sword_Combo` carries no action window. `AG_UPC_TwoHanded` and `AG_UPC_Unarmed` chain on authored windows.
- The sword's guard is the held stance above, in nodes named Guard Raise, Guard Hold and Guard Lower.
- The demo graphs' hops pick among `AM_UPC_Hop_F`, `AM_UPC_Hop_B`, `AM_UPC_Hop_L` and `AM_UPC_Hop_R` with direction montages.
- `BP_UPC_Enemy_Weak` runs at Action Speed 0.5.

</details>

<details>
<summary>What runs where (Multiplayer)</summary>

The server runs every character's graph; other machines play what it publishes. Each node is a predicted ability: the player's machine starts it at the press and the server confirms once the node is reachable, no Global Block Tag is on, gates are open, costs affordable and the payload may start. A refusal puts the player back on the server's node.

- For a remote player the server also refuses a chain sooner than the payload's earliest action window by more than **Dwell Grace Base Seconds** (0.1, advanced Multiplayer group) plus the round trip. A cut the node's rules forbid locks that player's moves until the cut node's window would have opened.
- A press after a draw, before the server has sent that weapon's moves, goes to the server and plays about 1.5 round trips later (**Forwarded**); **Max Server Inputs Per Second** (20) caps these per player.
- The server keeps a finished move for the late grace plus at least half a second, accepting a late press the player's machine played at once.
- **Action Speed** does not replicate and has no setter; set it on the Blueprint or placed character.
- From server code, cut with **Interrupt Action for External Ability**; a cancel made straight on the node's ability reads as a player skipping a recovery.
- Hooks run on the server and owning machine: gameplay on **Has Authority**, cosmetics on **Is Locally Controlled**.
- On the owner a node's Applied Tags can outlast it by about a round trip. A PlayerState ability system takes Mixed replication, an AI pawn's Full.
- `UPC.Debug.Net.RejectNextActivation 1` (editor builds) makes the server refuse the next move once; the overlay names the server's node while the two disagree.

</details>

<details>
<summary>C++</summary>

- `UUPCActionGraphAsset` holds the compiled `ActionNodes`; index 0 is Root. The editor's compile writes `FUPCActionNode::Transitions`; a graph built in code calls `ApplyGateEdgeTags()` once its gates and transitions are in place.
- `UUPCActionGraphComponent::ProcessActionInput(FGameplayTag, EUPCTransitionTrigger)` returns an `FUPCActionResult`; an overload takes the owned tags and an `FUPCInputContext` for directional conditions. `StartInputSequence` is Queue Input Sequence.
- A payload subclasses `UUPCActionPayload` and overrides `virtual bool Execute(UUPCPayloadHostAbility& Host)`, optionally `GetEarliestChainTime` and `GetCompletionTime` (both take Action Speed), `CanStart` and `CollectPreloadAssets`. The host's `PlayMontage` and `PlayDelay` apply Action Speed.
- A hook subclasses `UUPCActionHook` and overrides `EnterAction`, `ExitAction(EUPCActionEndReason)`, `TickAction` and `OnMontageNotify*`; `FinishesNode()` returns true for one that ends its node.
- A condition subclasses `UUPCTransitionCondition` and overrides `Evaluate_Implementation(const FUPCTransitionEvalContext&)` and `GetConditionDisplayName_Implementation`. Characters share one condition object, so it holds no state.
- `OnActionNodeChangedNative`, `OnActionFailedNative` and `OnActionEndedNative(NodeIndex, Reason)` are the native events. In editor builds, `FUPCDelegates::OnActionGraphActivated` fires when a component receives a graph.

</details>

<details>
<summary>Troubleshooting (Advanced)</summary>

**Nothing happens when I press the button**

- **No ability system.** The Output Log says the character has a graph and no Ability System Component. Add one.
- **The input does not reach the graph.** The item's Input Mappings must send the tag the transition listens for ([Input](https://rigbak.com/docs/universal-player-combat/input)).
- **A Global Block Tag is on the character**, such as `UPC.State.ActionsBlocked` from a stagger or interaction.
- **The buffer dropped it.** A node set to Ignore drops each press made before its window.
- **An edit made during play.** Press Compile, or save the graph.

</details>

---

Previous page: [AI Agent Guide](https://rigbak.com/docs/universal-player-combat/agents). Next page: [Action Graph Editor](https://rigbak.com/docs/universal-player-combat/action-graph-editor)
