While you have three skills, you can write cooldowns and costs yourself. bCanUseFireball and Set Timer are enough. But once you have fifteen skills, and conditions like "cannot cast while poisoned," "MP cost is halved while buffed," and "everything stops while stunned" start interacting, the combinations of flags get out of hand.
UE has a system that takes this entire area off your hands: the Gameplay Ability System (GAS). It is powerful, and the cost of adopting it is real. The goal of this article is not to master GAS. It is to let you decide for yourself whether a hand-rolled skill system is enough or whether you should adopt GAS.
One prerequisite first. Adopting GAS requires C++. Blueprint alone will not get you there. This article assumes you have worked through Your First Step from Blueprint to C++.
What You'll Learn
- The scope of work GAS takes over for you
- The five main parts (ASC / Ability / Effect / Attribute Set / Tag)
- Attributes and the three duration types of Gameplay Effects
- How Cost and Cooldown are "expressed as Effects"
- How to express "you cannot fire right now" with Gameplay Tags
- Hands-on: a fireball costing 20 MP with a 5-second cooldown
- The criteria for deciding not to adopt it
What GAS Takes Over
GAS is not "a feature for making skills." It is a system that takes over the tedious shared work that surrounds skills.
Here is what you actually write when building a single skill by hand.
| The job | Hand-rolled |
|---|---|
| Deciding whether it can activate | You manage flags like bCanUse |
| Spending MP | Decrement a variable. You also write the "can I afford it" pre-check |
| Running the cooldown | Hold a Timer and report the remaining time to the UI |
| Applying the effect | Reduce the target's HP. Defense calculations go here too |
| Stacking buffs and debuffs | Decide how duplicates behave, every time |
| Blocking on status effects | Check "am I stunned" in every skill |
With three skills, you write this table three times. At fifteen, it becomes a 6-column by 15-row management problem, and the columns interfere with each other.

GAS moves the entire left column into the engine. What you write is only "what does this skill do."
What you pay in exchange is learning five parts and building a minimal foundation in C++. Whether that trade is worth it is the judgment call this article is here to help you make.
The Five Main Parts
GAS has a lot of part names, and that is where people give up. But only five of them are central.

| Part | Role | Analogy |
|---|---|---|
| Ability System Component (ASC) | The front desk for everything. One per Actor | A reception counter |
| Gameplay Ability | The contents of one skill | The instructions for "cast a fireball" |
| Gameplay Effect | An instruction to change an Attribute | A slip saying "reduce HP by 30" |
| Attribute Set | The container for numbers like HP and MP | A stat sheet |
| Gameplay Tag | A hierarchical string representing state or type | A sticky note (State.Stunned) |
The flow is simple. The player presses a button → the ASC is asked to activate an Ability → the ASC checks cost, cooldown, and tags → if it passes, the Ability runs → the Ability applies a Gameplay Effect to the target → the Effect changes an Attribute.
As a rule, in-game Attribute changes are written through Gameplay Effects. Damage, healing, and buffs all issue a slip (a GE) instead of setting the value directly. This is the single biggest convention in GAS, and it feels like a detour until you get used to it (administrative assignments such as setting initial values are not forbidden).
Gameplay Tags are covered first in Flexible Design Patterns with Gameplay Tags. GAS uses those tags everywhere, so reading that one first speeds this up considerably.
Attributes and Gameplay Effects
An Attribute is a number GAS manages. HP, max HP, MP, attack power, movement speed. Most of what you would call a game stat becomes an Attribute.
Attributes are float, but they are not plain variables. They hold two values: Base and Current.
- Base value: the baseline. Changes that are permanent, like damage and healing, move this one
- Current value: the Base value with the currently active buffs and debuffs layered on. This is the value actually used
If attack power has a Base of 10 and a +5 buff is active, Current is 15. When the buff expires, Current returns to 10 with no work on your part. Not having to write "restore the original value when the buff is removed" is thanks to this structure.

What changes an Attribute is a Gameplay Effect (GE). GEs come in three duration types.
| Duration type | Behavior | When to use |
|---|---|---|
| Instant | Changes the Base value immediately and disappears | Damage, healing, MP cost |
| Has Duration | Changes the Current value for a set time, then reverts | A 5-second attack buff, a movement slow |
| Infinite | Keeps changing the Current value until explicitly removed | Equipment bonuses, permanent auras |
This is easy to get wrong, so once more: damage is Instant (it cuts Base), buffs are Duration or Infinite (they move Current). Build attack damage as a Duration effect and HP will restore itself over time.
Note that setting a Period on a Duration or Infinite effect changes the picture. It executes each period with the same treatment as Instant, so the Base value is cut and does not come back. Poison and regeneration are built this way. "Duration reverts over time" applies only when no period is set.
Gameplay Effect assets are created as Blueprints parented to GameplayEffect. You never write a graph. They are complete once you set the Class Defaults, which makes them closer to data assets (the same idea as data-driven design with Data Assets).
Cost and Cooldown Are Effects Too
This is the part where GAS clicks.
MP cost and cooldown are just Gameplay Effects. Abilities have dedicated fields for them, but what you put in those fields are ordinary GE assets, with no special machinery involved.

- Cost: an
InstantGE that changes Mana by -20. Assign it to the Ability'sCost Gameplay Effect Class - Cooldown: a
Has Duration(5 seconds) GE that only grants a tag calledCooldown.Fireball. Assign it to the Ability'sCooldown Gameplay Effect Class
The cooldown GE changes no numbers at all. It just applies a tag for 5 seconds. Before activation, GAS checks whether that tag is present, and blocks activation if it is. That alone is the entire cooldown mechanism.
Both are applied the moment you call Commit Ability inside the Ability. When activation is attempted, GAS only checks whether you can pay and whether you are off cooldown; the MP is actually spent at Commit. This trips everyone up in practice, so keep it in mind up front.
A nice side effect is that this structure feeds the UI directly. You can query the ASC for the remaining cooldown time, so skill icon dimming and countdown text need nothing added on the Ability side (for how to build the display side, see Building a HUD with UMG).
Cost works the same way: GAS checks in advance whether the GE can be applied (that is, whether you have enough MP). If you cannot afford it, activation never happens, so you never end up in the half-finished state of "the MP was insufficient but the effect played anyway."
Expressing "You Cannot Fire Right Now" with Gameplay Tags
The biggest reason hand-rolled skill management collapses is condition flags. bIsStunned, bIsSilenced, bIsCasting, bIsDead. Every skill you add means more code checking all of them.
GAS expresses this as the presence or absence of a tag.

Abilities have the following tag fields in their settings.
| Setting | Meaning |
|---|---|
| Ability Tags | Tags representing this Ability itself (Ability.Fireball) |
| Activation Blocked Tags | Tags that prevent activation while held (State.Stunned) |
| Activation Required Tags | Tags required for activation (Weapon.Equipped) |
| Activation Owned Tags | Tags that stay applied to you while it runs (State.Casting) |
To stun something, you apply a GE that grants the State.Stunned tag. You touch no skill code at all. A brand-new skill with State.Stunned in its Activation Blocked Tags is automatically stopped by stuns too.
Tags are hierarchical on top of that. If you want to block on both State.Stunned and State.Frozen, give them a State parent and block on State as a group. A list of flags collapses into one point in a tree.
The Reality of Setting It Up
By now you probably want to adopt it, so let's look at what setup actually involves.
1. Enable the plugin. In Edit → Plugins, search for Gameplay Abilities, enable it, and restart the editor.
2. C++ becomes necessary. The Attribute Set has to be defined as a C++ class. You cannot make it in Blueprint. You also add dependency modules to your project's Build.cs.
// MyProject.Build.cs
PublicDependencyModuleNames.AddRange(new string[] {
"Core", "CoreUObject", "Engine", "InputCore",
"GameplayAbilities", "GameplayTags", "GameplayTasks" // add these three
});
An Attribute Set looks like this.
// MyAttributeSet.h
#pragma once
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "MyAttributeSet.generated.h"
// The standard macro that generates all four accessors at once
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)
UCLASS()
class MYPROJECT_API UMyAttributeSet : public UAttributeSet
{
GENERATED_BODY()
public:
// Attributes are held as FGameplayAttributeData, not float
UPROPERTY(BlueprintReadOnly, Category = "Attributes")
FGameplayAttributeData Health;
ATTRIBUTE_ACCESSORS(UMyAttributeSet, Health)
UPROPERTY(BlueprintReadOnly, Category = "Attributes")
FGameplayAttributeData Mana;
ATTRIBUTE_ACCESSORS(UMyAttributeSet, Mana)
};
3. Attach the ASC, register the Attribute Set, and initialize. This three-part setup is where people get stuck most at the entrance to GAS.
- Attach an Ability System Component to your character
- Register your Attribute Set with that ASC. Writing the class alone does not make it exist. Either create it as a subobject in C++ or add it to
Default Starting Datain the ASC's Details panel - Call
InitAbilityActorInfoto tell it who the owner is and which Actor actually acts. This is not exposed as a Blueprint node, so call it from C++
For a single-player Character, these three parts take about ten lines. Create the ASC and Attribute Set as subobjects in the constructor, and an Attribute Set living on the same Actor is discoverable from that ASC.
// MyCharacter.h (excerpt)
UPROPERTY() UAbilitySystemComponent* ASC;
UPROPERTY() UMyAttributeSet* Attributes;
// MyCharacter.cpp
AMyCharacter::AMyCharacter()
{
ASC = CreateDefaultSubobject<UAbilitySystemComponent>(TEXT("ASC"));
Attributes = CreateDefaultSubobject<UMyAttributeSet>(TEXT("Attributes"));
}
void AMyCharacter::BeginPlay()
{
Super::BeginPlay();
ASC->InitAbilityActorInfo(this, this); // owner, avatar (both self)
}
Miss any of the three and the symptom is identical: nothing happens. When an Ability does not run, check these three from the top and you will not flounder.
The Ability and Gameplay Effect assets themselves can be made in Blueprint. C++ is only needed for the foundation, and day-to-day skill work happens in Blueprint. Still, you have to pass through C++ once at the start.
4. The learning plateau is long. Even after learning the five parts, there is more ahead: Gameplay Effect Execution Calculation (computing final damage from attack and defense) and Gameplay Cue (synchronizing presentation).
Hands-On: Getting One Fireball Working End to End
To gather your evidence, let's get one skill working in the smallest possible form.
An attack skill in an ARPG, an item effect in a roguelike, a special move in a fighting game. "Pay a cost, wait out a cooldown, apply an effect to the target" is the same shape across genres. Here we build the minimal version.
We are building a fireball that costs 20 MP, has a 5-second cooldown, and reduces the target's HP by 30.
Here is what running it looks like. With MP at 100, pressing the button drops MP to 80 and launches a projectile, and the target it hits loses 30 HP. Press again right away and nothing happens until 5 seconds pass. Once MP drops below 20, again nothing happens. "I pressed it and nothing came out" works without writing any code.

Setup
| Item | Value |
|---|---|
| Plugin | Enable Gameplay Abilities and restart the editor |
| C++ class | The UMyAttributeSet above (Health / Mana) |
| ASC | Add an Ability System Component to both the player and the target, register UMyAttributeSet, and call InitAbilityActorInfo |
| Initial values | Health = 100, Mana = 100 |
| Gameplay Tags | Register Ability.Fireball / Cooldown.Fireball / State.Stunned |
| Target side | An Actor with collision that can receive Overlaps (Generate Overlap Events enabled) |
| Assumption | Single player. Multiplayer additionally requires Attribute replication settings |
The easiest way to set the initial Attribute values is to apply an Instant GE to yourself in BeginPlay. Setting the Modifier to Override = 100 is safe, because applying it twice will not give you 200.
Attribute changes are invisible, so print HP and MP with Print String every time, or put a simple UI in place. Without a way to see them, you cannot tell whether anything is working.
Step 1: Create Three Gameplay Effects
Right-click in the Content Browser → Blueprint Class → choose GameplayEffect as the parent, and create three. None of them get a graph; you only set the Class Defaults.
| Asset | Duration Policy | Settings |
|---|---|---|
GE_Cost_Fireball | Instant | Modifier: Mana / Add / -20 |
GE_Cooldown_Fireball | Has Duration | Duration = 5.0, grants tag Cooldown.Fireball |
GE_Damage_Fireball | Instant | Modifier: Health / Add / -30 |
The "granted tag" on GE_Cooldown_Fireball is the field for tags applied to the target while the effect lasts. The UI for this changed in UE 5.3.
- UE 5.3 and later: add
Grant Tags to Target ActorunderGameplayEffect Componentsand putCooldown.Fireballin itsAdd Tags - Before that: write it directly in
Granted Tagsin the Class Defaults
Only the location changed; the meaning is the same. If you cannot tell which generation an article online belongs to, suspect this difference.
Step 2: Create the Ability
Blueprint Class → choose GameplayAbility as the parent and create GA_Fireball. Set the following in Class Defaults.
| Setting | Value |
|---|---|
Ability Tags (called Asset Tags in UE 5.8) | Ability.Fireball |
| Cost Gameplay Effect Class | GE_Cost_Fireball |
| Cooldown Gameplay Effect Class | GE_Cooldown_Fireball |
| Activation Blocked Tags | State.Stunned |
| Instancing Policy | Instanced Per Actor |
Setting Instancing Policy to Instanced Per Actor reuses one instance per Actor. You can hold variables and wait on events inside the Ability without accumulating pointless instances. Instanced Per Execution exists if you want each cast to hold independent state, but Per Actor is the natural choice for a single-shot skill like this.
The graph looks like this.
GA_Fireball (event graph)
Event ActivateAbility
→ Commit Ability // ★ MP is spent and the cooldown starts here
(success)
→ Get Avatar Actor From Actor Info // the caster
→ Make Outgoing Gameplay Effect Spec (GE_Damage_Fireball, Level = 1)
→ Spawn Actor from Class (BP_Fireball, 100uu in front of the caster)
pass the Spec created above into the projectile's "DamageSpec" variable
→ End Ability
(failure)
→ End Ability // close without doing anything

Forget Commit Ability and MP is never spent and the cooldown never starts. The activation check only asks whether you can pay; the actual payment happens at Commit. If the first shot fires but you can also spam it, look here first. Commit can fail, so branch on the return value and close without spawning the projectile on failure.
Another key point is that the damage Spec (the slip) is created by the casting Ability and handed to the projectile. Create it on the projectile side and the information about who fired it becomes the projectile itself, which falls apart when you later want to reference attack power or add kill attribution. The slip is issued at the source.
And do not forget End Ability. An Ability that is not explicitly ended stays running, and with Instanced Per Actor the next activation is rejected. "I can only cast it once" is almost always this.
Step 3: Wire Up Activation
In the player's character Blueprint, grant the Ability and then activate it from input.
BP_MyCharacter (event graph)
Event BeginPlay
→ Ability System Component → Give Ability (Ability Class = GA_Fireball)
IA_Fire (Enhanced Input, Started)
→ Ability System Component → Try Activate Ability by Class (GA_Fireball)
Call Give Ability once on the server (for single player, an ordinary BeginPlay is fine). After that you just hit Try Activate Ability by Class. For setting up input, see Introduction to the Enhanced Input System.
The projectile (BP_Fireball) is an Actor with collision (Generate Overlap Events enabled) and a Projectile Movement component. Make it ignore its own owner so it does not hit the caster. On hit, apply the Spec it received from the Ability to the target's ASC.
BP_Fireball (event graph)
Variable: DamageSpec (Gameplay Effect Spec Handle) — passed in from the Ability
On Component Begin Overlap
→ Get Ability System Component from Other Actor
→ (if valid) Apply Gameplay Effect Spec to Self (Target = the other ASC, Spec = DamageSpec)
→ Destroy Actor
The to Self in the node name means "apply it to the ASC specified on the Target pin." Since you pass the other actor's ASC as the Target, the damage lands on them.
Verify
Press Play and cast the fireball.
- MP drops 100 → 80
- Pressing again right away does nothing. After 5 seconds it fires
- Five casts bring MP to 0, and nothing happens past that
- The target you hit goes 100 → 70 HP
You can confirm the tags at work, too. As a last check, apply a GE that grants yourself State.Stunned (Has Duration 3 seconds), then cast. Activation is silently blocked for 3 seconds. You never touched a single line of GA_Fireball, yet the one line you put in Activation Blocked Tags stops it. That's the payoff of the tag approach.
Here is how to narrow it down when it does not behave as expected.
- The projectile fires but MP does not drop and you can spam it →
Commit Abilityis missing. This is the most common symptom - Nothing happens at all (
Event ActivateAbilitynever even fires) → check the three foundation items from the top: the ASC exists, the Attribute Set is registered,InitAbilityActorInfois called. Also check thatGive Abilityran and thatState.Stunnedis not still applied - You can cast once and then nothing → you are not calling
End Ability - You can cast during the cooldown →
Cooldown Gameplay Effect Classis empty, or the tag grant (Grant Tags to Target Actor/Granted Tags) is not set - The target's HP does not drop → the target has no ASC or Attribute Set, or the projectile is not receiving Overlaps
Two things to take away.
- You could write the same behavior by hand: what we just built takes about an hour with a
Manavariable andSet Timer. The value of GAS is not being able to build this one skill; it is that the second one onward is almost pure configuration. To make an ice lance, copy two GEs, change the numbers, and create one Ability. Not a single line of checking logic gets added - It pays off when the conditions multiply: stunned, silenced, casting, equipment requirements. By hand, 15 skills × 5 conditions means 75 checks. GAS applies the same restriction to existing and new skills by adding one line to a tag field (see Flexible Design Patterns with Gameplay Tags)
Put the other way around, if you have five skills and almost no conditions, this structure is too heavy. A hand-rolled Enum-and-Timer approach is enough (see managing state with Enums and Switch and building health and damage with Apply Damage).
Bonus: Good to Know for Later
Where you attach the ASC is hard to change later. For single player, attaching it to Character is the natural choice. For multiplayer, where you want buffs and stats preserved through death and respawn, attaching it to PlayerState is the standard. GAS was built for multiplayer from the start, so make this call early if networking is on your roadmap (see multiplayer basics: replication and understanding the Game Framework).
Presentation can be moved into Gameplay Cues. This is a mechanism for invoking visual work such as "flash red and play a sound when damaged" through tags. It separates logic from presentation, which is worth learning if you are going all-in on GAS. You do not need it for your first skill.
You write HP clamping yourself. GAS has no automatic clamping. You override a UAttributeSet callback in C++ to implement it. For HP where an Instant GE moves the Base value, rounding the result in PostGameplayEffectExecute is the clearest approach; if you want to cap the buffed Current value, use PreAttributeChange. Adopting GAS alone does not prevent "healing past max HP."
Lyra is a good reference but too heavy as a first tutorial. Epic's official Lyra Starter Game is an excellent practical GAS example, but its abstraction is thick and grasping the whole picture on first read is rough. Get your own minimal setup working first and it becomes far easier to follow.
Summary
GAS is a system for handing the shared work surrounding skills over to the engine.
| Verdict | Situation |
|---|---|
| You do not need it | Around five skills. Almost no status effects. You want to avoid C++ |
| It makes life easier | More than ten skills. Buffs and debuffs stack. Status effects impose restrictions |
| Nearly mandatory | Large numbers of skills and item effects, plus multiplayer support |
The deciding factor is not the number of skills itself. It is whether the "can I activate this" conditions have started being shared between skills. From the moment they are shared, hand-rolled flag management gets exponentially harder, and the GAS tag approach starts paying off.
One more thing. If you are on the fence, you do not need it yet is the practical conclusion. You can adopt GAS later. When your hand-rolled skills pass five and the same condition-checking code keeps showing up, that is the signal to migrate.
How many skills does your game have right now, and how many of them have conditions like "cannot fire because of some other state"? Those two numbers will give you the answer.
Further Reading
- Flexible Design Patterns with Gameplay Tags — the tag basics GAS is built on
- Dividing Work Between Blueprint and C++ — deciding how much to write in C++
- Gameplay Ability System - UE5 official documentation