How-To: Play Sound and VFX from an Augment

Goal: attach a sound, particle effect or camera shake to an augment so that every player who should perceive it actually does - including the other players watching, and including augments that only run on the server.

Prerequisites
You have an augment with at least one event group. See How-To: Create and Equip an Augment. Multiplayer terms used here are explained in Concept: Multiplayer and Prediction.

Why a local Play Sound does not work

An augment action is a plain UObject. It has no owning actor, no replication and no RPCs, and Execute is an ordinary function call. It runs on exactly the machines where the ability broadcast the event, and nothing it does crosses the network by itself. So a Play Sound at Location or Spawn Emitter at Location node inside an action is heard and seen only on the machines that happened to run it - and for both kinds of event, that is the wrong set.

Your action doesOn a server + owning client event (OnActivate, OnEnd)On a server only event (OnEffectResolved)
Authoritative work - spawn an actor, apply an effect, change stateRuns twice. Guard it on Is AuthorityCorrect as written. No guard needed
Local feedback - Play Sound at Location, Spawn EmitterHeard by the instigator only. Other players are simulated proxies and never run the actionHeard by nobody. The server has no listener
The failure is quiet
A Play Sound at Location on Augment.Event.OnActivate works perfectly in single-player PIE, and in your own window in multiplayer. It is simply inaudible to every other player, and nothing reports that. The same action on Augment.Event.OnEffectResolved is silent everywhere. Neither case logs a warning, because nothing went wrong - the node ran exactly where it was asked to.

A GameplayCue has no such problem, because replication is the cue system's job rather than your action's. From the server a cue multicasts to every relevant client; from a predicting client it plays locally under that client's prediction key, and the server's copy is absorbed so it never double-plays. The same cue is therefore correct on every event - which is the real reason to prefer it. You should not have to know an event's network context before you can attach a sound to it.

Step 1 - make the cue

A cue is a gameplay tag under GameplayCue plus a notify asset that reacts to it. The tag is the contract; the asset holds the sound or particle.

Declare the tag in Project Settings > Project > GameplayTags > Manage Gameplay Tags, for example GameplayCue.Augment.Ignite. The GameplayCue root is required - the cue manager only scans tags beneath it.

Then Content Browser > right-click > Blueprint Class, and pick a base by how long the effect lasts: GCN Burst for a one-shot (impacts, sparks, a hit sound), GCN Looping for something that persists until removed. Set the asset's GameplayCue Tag class default to the tag you declared, and fill in its Burst Effects (Sound, Particle, Camera Shake) - no graph work is needed for a simple effect.

Verify
The new tag appears in the Cue Tag dropdown in the next step. If it does not, the tag is not under the GameplayCue root, or the editor needs a gameplay tag refresh.

Step 2 - add the action to the event group

In the augment editor, select the event group you want the feedback on, then add Actions > Feedback > Action: Gameplay Cue from the palette. Set Cue Tag to the tag from step 1 and leave Cue Execution on Execute for a one-shot.

That is the whole step - the action is data, so there is no graph to write and nothing to guard. Add it to as many event groups as you like; it behaves correctly on all of them.

For a persistent effect, set Cue Execution to Add on the group that starts it and add a second Gameplay Cue action with Remove and the same Cue Tag on the group that ends it - Augment.Event.OnEnd is the usual partner. Nothing removes an Add cue for you.

Verify
Play in the editor and trigger the augment. You should see and hear the cue. If nothing happens, the notify asset's GameplayCue Tag does not match the tag on the action - that mismatch is silent, because an unhandled cue is not an error.

Step 3 - choose where it plays

Cue Target decides whose ability system plays the cue, which decides where it is positioned and what it follows.

Cue TargetPlays onUse for
Caster (Self)The instigator's ability systemBuff glows, cast flashes, self-heals - anything belonging to the caster
Target (Enemy/Ally)Each target's own ability system, once per targetImpacts and hit reactions. The cue is positioned on the victim and follows them
Ground (World)The caster's ability system, positioned at the target locationEffects that belong to a place rather than an actor

Target falls back to a world-positioned cue when a target has no ability system component, so an impact on destructible scenery or a wall still plays rather than being dropped.

Magnitude reaches the notify as RawMagnitude, for handlers that scale themselves by it. Tick Use Event Magnitude to send the firing event's value instead: on Augment.Event.OnEffectResolved that is the damage actually dealt after mitigation, so a cue can scale to how hard the hit landed.

One cue, many outcomes
The action forwards the event's context tags to the notify as AggregatedSourceTags. On Augment.Event.OnEffectResolved those carry Damage.Result.Crit, Damage.Result.Blocked and the damage type - so a single notify can branch on a crit instead of needing a separate cue and a separate action per outcome.

Step 4 - verify in multiplayer

This is the step that catches the bug this page exists for, and single-player PIE cannot catch it.

  1. Set Number of Players to 2 and Net Mode to Play As Client.
  2. Play, and trigger the augment from one client.
  3. Watch the other client's window.
Verify
Both windows play the cue - the one that triggered it and the one watching. If only the instigator's window plays it, the feedback is still a local node somewhere rather than a cue. If neither plays it, check the notify asset's tag as in step 2.

Step 5 - drive the cue from the contract

Everything so far plays a fixed effect. To make the cue follow the ability's own data - so a fire augment and an ice augment share one cue and one notify - hand the cue the contract's resolved values.

Fill in Object Slots on the action with the contract object slots you want, for example Ability.FX.Impact and Ability.SFX.Impact. Fill in Value Properties with contract properties such as Ability.Property.Radius. The action resolves each one against the ability and ships the result with the cue.

Why the cue cannot look these up itself
Resolving a contract slot needs the ability instance and its augmented property cache, and abilities are instantiated only where they activate - the server and the owning client. On a simulated proxy, which is every other player's machine, there is no instance to ask. So the machine running the action resolves the values and sends the answers; the payload rides on FGameplayCueParameters::EffectContext, which replicates.

Read them back in the notify with Get Cue Payload Object and Get Cue Payload Value, keyed by the same tag.

In your GCN Burst Blueprint, override On Burst. The chain is On Burst -> Get Cue Payload Object (Tag = Ability.FX.Impact, Expected Type = NiagaraSystem) -> Is Valid (Branch) -> Spawn System At Location, using the Location from the cue parameters.

Get Cue Payload Value works the same way for numbers - feed Ability.Property.Radius into the spawned system's Set Niagara Variable Float, and the effect follows any augment that widened the radius.

The tag is the role
Keying by the slot tag is what makes several entries work. Two USoundBase assets are distinguishable as Ability.SFX.Impact and Ability.SFX.Tail; a cast could only tell you both are sounds. That is also why there is no need for a bundle asset - the contract's own slot tags already name each role.
Where the value comes fromHow it is resolvedWhat an augment does to it
Object SlotsGetConfigurableObject - the contract default, or the highest-priority equipped augment's overrideOne Swap Object mutation replaces the asset for every cue that reads the slot
Value PropertiesGetConfigurableValue - the ability's augmented property cacheAny Ability Property mutation changes the number the cue receives
Keep the lists short
The payload is serialized on every execution of this cue, and cues are high-frequency. Tags travel as indices and assets as network GUIDs, so a handful of entries is cheap - but do not ship the whole contract. Send what the notify actually reads.
Requires the Crimson effect context
The payload rides on FCrimsonGameplayEffectContext. If your project has not registered the Crimson AbilitySystemGlobals class in DefaultGame.ini, the action logs a warning and the cue plays without the payload. See Quick Start step 1, which sets this up.

Writing your own action instead

Custom actions are the other half of the same rule. Feedback replicates through a cue; authoritative work needs an authority check, because a predicted event runs your action on the server and on the owning client both.

Context.bIsAuthority is set for you by every dispatcher, so the guard is one pin or one if. The context also tells the action which augment it belongs to via Context.OwningAugment - actions are instanced sub-objects of an augment asset, but nothing used to hand them that asset, so an action could not read its own augment's identity, tags or description.

Create a Blueprint class from CrimsonAugmentAction and override Execute. Drag the Context pin into Break Crimson Augment Action Context - the struct exposes Target Data, Owning Ability, Event Tags, Event Magnitude, Augment Level, Is Authority and Owning Augment.

For anything authoritative, branch on Is Authority first: Execute -> Break Crimson Augment Action Context -> Branch (Is Authority) -> True: your spawn / apply / state change. Feedback does not go behind that branch - it goes through a cue, which handles the split itself.

Do not use Is Authority to gate feedback
It is the wrong tool for that half of the problem. Gating a Play Sound at Location on Is Authority makes it play on the server only, which is worse - on a dedicated server nobody hears it at all. Is Authority answers "may I change the world", not "who should perceive this". Only a cue answers the second question.

Reading your own augment

Context.OwningAugment is the augment asset the running action was authored on. Use it when an action's behavior depends on the augment itself rather than on the event - reading its identity tags, its display name for a floating message, or its own data to scale by.

It changes as the dispatcher moves between augments
One action context is shared by every augment on the ability, so OwningAugment and Augment Level are rewritten for each augment in turn. Read them during Execute. Never cache either on the action - an action is an instanced sub-object of a data asset, so anything stored on it is shared state that outlives the execution.
It can be null
A lifecycle group fired without a source augment leaves it unset, so branch on it rather than assuming. Is Valid in Blueprint, a null check in C++.

Execute -> Break Crimson Augment Action Context -> Owning Augment -> Is Valid (Branch), then read the augment's own properties off the pin - its tags, its level curve, its description.

See also

  • How-To: Add Your Own Augment Event - which events exist and where each one runs
  • Concept: Ability Contract - object slots, properties, and what augments may swap
  • Concept: Augment System - actions, event groups and the shared action context
  • Concept: Multiplayer and Prediction - why predicted events run twice
  • API Reference - the Gameplay Cue action's full property list