How-To: Broadcast Ability Lifecycle Events
Goal: fire a gameplay tag at a fixed moment in an ability's life - activation, end, cost, cooldown, cast success, cast interrupt - so that a different ability can trigger off it, or a running ability can wait on it, without either side holding a reference to the other.
UCrimsonGameplayAbility (or one of its four shapes) - the hooks live on that base class, not on Epic's UGameplayAbility.The six hooks
Every UCrimsonGameplayAbility carries an AbilityEventTags property of type FCrimsonAbilityEventTags. It is six independent FGameplayTagContainer fields, one per lifecycle moment. Leave a field empty and nothing is sent for that moment.
| Field | Fires when | Available on |
|---|---|---|
OnActivatedTags | The ability starts, from BroadcastActivationSideEffects. | Every Crimson ability |
OnEndedTags | The ability ends, including when it is canceled. | Every Crimson ability |
OnApplyCostTags | After ApplyCost has run, including any AdditionalCosts. | Every Crimson ability |
OnApplyCooldownTags | After ApplyCooldown has applied the cooldown effect. | Every Crimson ability |
OnSucceedTags | A cast runs to completion, from CompleteCast. | UCrimsonGameplayAbility_WithCastTime only |
OnFailedTags | A cast is interrupted mid-flight. | UCrimsonGameplayAbility_WithCastTime only |
UCrimsonGameplayAbility_Toggle, _Charges and _Channeling all derive straight from UCrimsonGameplayAbility, not from _WithCastTime. They show the OnSucceedTags and OnFailedTags fields in the details panel because the struct is shared, but nothing ever sends them. Use OnActivatedTags and OnEndedTags on those shapes.One more edge:
UCrimsonGameplayAbility_Charges overrides ApplyCooldown and returns early when a recharge timer is already running, so OnApplyCooldownTags fires on the first charge spent, not on every charge.Step 1 - fill in the tags
Open your ability Blueprint, click Class Defaults, and find the Crimson > Ability Events category. Expand Ability Event Tags and add a tag to the container for the moment you want. Any gameplay tag works - it does not have to live under a particular parent - but a dedicated Event. branch keeps these separate from state tags.
Event.Ability.Fireball.CastFinished reads as a moment in time. Ability.State.Casting reads as a condition that is true for a while. Mixing the two in one branch makes the tag picker hard to reason about later.Step 2 - listen for the tag
Each tag goes out through UAbilitySystemBlueprintLibrary::SendGameplayEventToActor on the ability's owning actor, so it is an ordinary GAS gameplay event. That gives you two standard ways to receive it, and you do not have to pick just one.
| You want to | Use | Where you set it up |
|---|---|---|
| Start a whole other ability | An ability trigger | The listening ability's Class Defaults -> Triggers |
| React inside an ability that is already running | The Wait Gameplay Event ability task | That ability's graph, or C++ |
To trigger an ability: open the listening ability Blueprint -> Class Defaults -> Triggers -> +. Set Trigger Tag to your tag and Trigger Source to Gameplay Event. The ability now activates whenever that tag is sent to the owner, and the event payload arrives on its Event ActivateAbilityFromEvent node instead of the usual Event ActivateAbility.
To react inside a running ability: in that ability's graph, Event ActivateAbility -> Wait Gameplay Event (set Event Tag to your tag) -> EventReceived. Tick Only Trigger Once off if the ability should keep listening for the whole activation.
showdebug abilitysystem lists active abilities on the target, and log LogAbilitySystem Verbose prints each activation attempt.Step 3 - read the payload
Every one of these events carries the same FGameplayEventData, built by MakeGameplayEventDataFromEventPayload. Three fields are filled in:
| Field | Holds | Cast it to |
|---|---|---|
Instigator | The ability's owning actor. | Your pawn or player state class |
OptionalObject | Owner actor, owner pawn, owner player controller, the ability's family tags, and its FGameplayAbilitySpec. | UCrimsonAbilityEventPayloadBase |
OptionalObject2 | The ability instance that sent the event. | UCrimsonGameplayAbility |
From the payload pin: Break GameplayEventData -> Optional Object -> Cast To CrimsonAbilityEventPayloadBase. That gives you Get Owner Actor, Get Owner Pawn, Get Owner Player Controller, Get Ability Family Tags and Get Ability Spec. To find out which ability sent it, Optional Object 2 -> Cast To CrimsonGameplayAbility instead.
Ability.Family.Fire.UCrimsonAbilityEventPayloadBase and refills it per event rather than allocating a new one. Read what you need inside your handler - do not store the pointer and read it later, because the next event on that same ability will have overwritten it.Where these run
All six broadcasts are authority only. The server sends them; the predicting client does not.
This matters because UCrimsonGameplayAbility defaults to LocalPredicted, so the ability's own code runs on both the owning client and the server. If the client sent these events too, an ability listening through a Trigger would activate on the client, predict itself, and RPC the server to activate it - while the server had already activated it off its own copy. That is a double activation, and with a short or InstancedPerExecution listener it lands as double damage.
| Machine | What happens |
|---|---|
| Server | Sends the event. Any triggered ability activates here, once. |
| Owning client | Does not send the event. It still runs the triggered ability, because GAS replicates the server's activation back to it. |
| Other clients | Do not send and do not receive it. Use a Gameplay Cue for anything they need to see. |
On Cast Completed and friends run on both sides - or a Gameplay Cue. See Concept: Multiplayer and Prediction.Ability events or augments?
Both systems let something react to an ability without the ability knowing about it, so it is easy to reach for the wrong one. They answer different questions.
| Ability lifecycle events | Augments | |
|---|---|---|
| What it is | A GAS gameplay event sent to the owner's ASC. | A data asset equipped into one of an ability's augment slots. |
| Who owns the reaction | The listener. Any ability can subscribe without being told. | The augment asset. It only runs while equipped. |
| Turned on and off at runtime | No - it is part of the ability's defaults and always fires. | Yes - that is the point. Equip and unequip per character. |
| Can change what the ability does | No. It is a notification, sent after the fact. | Yes. OnEffectApplying hands over the live spec through SpecHandleToModify. |
| Hook points | Six fixed lifecycle moments. | Six shipped Augment.Event.* tags plus any tag you declare. |
| Per-instance state | None. Each event is standalone. | Yes - stateful augments count hits, track stacks, and so on. |
| Runs on | Server only. | Server, and the owning client for OnActivate and OnEnd. |
Worked examples
Use a lifecycle event: interrupting a caster opens a punish window. Fill Fireball's OnFailedTags with Event.Ability.Fireball.Interrupted. Every enemy ability that wants to punish an interrupted cast adds that trigger tag to its own Triggers array. The enemy designer never opens Fireball, the Fireball designer never learns which enemies punish, and adding a tenth punisher is a change to the tenth enemy only. An augment cannot do this cleanly: augments are equipped onto the caster's ability, so making the caster carry a slot whose only job is to help enemies punish them is backwards.
Use a lifecycle event: a resource system that no ability knows about. Put Event.Combat.CostPaid in OnApplyCostTags on every ability that spends mana. A single "mana burn" ability sits there with a Wait Gameplay Event task counting them. The abilities that spend mana have one tag between them and never reference the resource system.
Use an augment: Fireball burns on crit. The reaction has to modify Fireball's own damage effect before it lands, which is exactly what Augment.Event.OnEffectApplying and SpecHandleToModify exist for. A lifecycle event carries no effect spec and fires after the effect has already been applied, so it is the wrong tool - and this is build content the player equips, which is the augment system's whole job. See How-To: Create and Equip an Augment.
Use an augment: every third hit of a combo staggers. Counting hits across activations is per-instance state, which lifecycle events do not have. See How-To: Write a Stateful Augment.
OnSucceedTags so unrelated systems can react, and carry augment slots so players can reshape what the cast actually does. The augment machinery also re-broadcasts its own events on UCrimsonAbilitySystemComponent::OnAugmentEventBroadcast, which is how the combo system observes them - but subscribing to that is a C++ delegate, not a class-default checkbox like a trigger tag.See also
- Concept: Augment System - what augments are and when they run.
- How-To: Add Your Own Augment Event - declaring a new
Augment.Event.*tag. - How-To: Make a Cast-Time Ability - the only shape that sends
OnSucceedTagsandOnFailedTags. - How-To: Use Toggle, Charges and Channeling - the shapes that do not.
- Concept: Multiplayer and Prediction - why these are authority only.
- API Reference - the full
FCrimsonAbilityEventTagsandECrimsonAbilityEventTagTypesurface.