Last week we pulled dialogue out of the Blueprint graph and into a data-driven system. Abilities deserve the same treatment — but here the standard advice sends beginners straight into a wall.
Ask anywhere how to build abilities in Unreal and the answer is GAS: the Gameplay Ability System. It is genuinely excellent. It handles cooldowns, costs, attributes, tags, effects, and client prediction, and it's what Epic ships Fortnite on. It is also a plugin you enable in C++, a set of five interlocking concepts, and a solid week of your life before a fireball leaves your hand.
If you're building a single-player game solo, that trade is usually wrong. Here's the version that isn't.
What an ability actually needs
Strip it down. A working ability has four moving parts:
- Can I use it? (cooldown, resource, state)
- Pay for it.
- Do the thing.
- Tell the UI.
That's it. Everything else GAS gives you — replication, prediction, stacking effects, attribute aggregation — is real value, but only if you have the problem it solves.
The shape: one Component, N Data Assets
Put an AbilityComponent on your character. It owns nothing but a list and a map:
CB0
Each ability is a Primary Data Asset (UAbilityData) — the choice we covered in the DataTable vs DataAsset post. One asset per ability, editable by you at 4 AM without touching a graph:
CB1
Adding an ability becomes: right-click, create Data Asset, fill in six fields. No new Blueprint. No new class.
Cooldowns: one number, not a timer
The instinct is a Timer per ability. Don't. Timers are objects, they need cleanup, and they don't survive a save/load.
Store a timestamp instead:
CB2
One float per ability. GetRemaining(Tag) is Cooldowns[Tag] - Now, clamped at zero — that's your UI radial fill, for free, with no per-frame ticking. And because it's just numbers in a map, it serialises into your save file without a second thought.
Note the trap: GetTimeSeconds is world time, and world time pauses. If you pause the game and expect cooldowns to keep ticking, you want GetRealTimeSeconds — pick one deliberately rather than finding out later.
Blocking states with tags
"Can't cast while stunned." "Can't sprint while casting." The nested-boolean version of this rots fast: bIsStunned && !bIsCasting && bHasMana && !bIsInDialogue.
Give the component a tag container. Stun adds State.Stunned. Each ability data asset gets a BlockedByTags container. Activation asks one question:
CB3
New state? Add a tag. No boolean, no touching the activation code. This is the one idea worth stealing from GAS wholesale — you can use FGameplayTag without enabling the ability system plugin at all.
Telling the UI
The component broadcasts. OnAbilityActivated(Tag), OnCooldownStarted(Tag, Duration), OnAbilityFailed(Tag, Reason). The HUD binds once and never asks again — the dispatcher pattern, applied where it obviously belongs. Your HUD does not tick, and it does not hold a reference to your character.
When you should actually use GAS
Be honest about this rather than dogmatic. Switch to GAS when:
- You're going multiplayer and abilities need to feel responsive on a client. Prediction is genuinely hard and GAS already solved it.
- You need stacking, timed, aggregating effects: five poisons at once, each with its own duration and magnitude, all modifying one attribute.
- You have attributes that interact: armour reduces damage, resistance reduces armour, a buff modifies resistance.
If you're building a single-player action-RPG where a potion adds 50 health and a fireball takes 3 seconds to recharge, GAS is a framework tax with no return.
Common pitfalls
- A Timer per ability. Doesn't survive save/load, leaks on actor destruction, and gives you nothing a float couldn't.
- Cooldown logic in the character Blueprint. Now every ability-capable class needs it. That's what the component is for.
- Hard-referencing montages and VFX in the data asset. Every ability's animation loads when the character loads. Soft reference and async-load on activation.
- Booleans for state. They multiply. Tags don't.
- Starting with GAS "because it's the proper way," then abandoning it at 40% and shipping a half-GAS hybrid. That's worse than either option.
- Checking the cooldown in the UI instead of in the component. The UI is a view. Never let it be the gatekeeper.
30-second recap
- Four parts to an ability: check, pay, execute, notify.
- One
AbilityComponentper character, one Primary Data Asset per ability. - Cooldowns are a
Map<Tag, float>of timestamps — savable, tick-free, UI-ready. - Gameplay Tags for blocking states; you can use them without the GAS plugin.
- Dispatchers to the UI, never polling.
- Move to GAS for multiplayer prediction, stacking effects, or interacting attributes — not before.
Next Sunday: a settings menu from scratch — resolution, V-Sync, audio sliders, key rebinding, and the parts of it UE 5.3 already does for you.
— Marco
Comments
Leave a comment