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.
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 does | On a server + owning client event (OnActivate, OnEnd) | On a server only event (OnEffectResolved) |
|---|---|---|
| Authoritative work - spawn an actor, apply an effect, change state | Runs twice. Guard it on Is Authority | Correct as written. No guard needed |
Local feedback - Play Sound at Location, Spawn Emitter | Heard by the instigator only. Other players are simulated proxies and never run the action | Heard by nobody. The server has no listener |
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.
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.
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 Target | Plays on | Use for |
|---|---|---|
Caster (Self) | The instigator's ability system | Buff glows, cast flashes, self-heals - anything belonging to the caster |
Target (Enemy/Ally) | Each target's own ability system, once per target | Impacts and hit reactions. The cue is positioned on the victim and follows them |
Ground (World) | The caster's ability system, positioned at the target location | Effects 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.
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.
- Set Number of Players to 2 and Net Mode to Play As Client.
- Play, and trigger the augment from one client.
- Watch the other client's window.
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.
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.
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 from | How it is resolved | What an augment does to it |
|---|---|---|
| Object Slots | GetConfigurableObject - the contract default, or the highest-priority equipped augment's override | One Swap Object mutation replaces the asset for every cue that reads the slot |
| Value Properties | GetConfigurableValue - the ability's augmented property cache | Any Ability Property mutation changes the number the cue receives |
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.
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.
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.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