[UE5] Gameplay Ability System: Deciding Whether to Adopt It

Created: 2026-07-21

An introduction to UE's Gameplay Ability System that takes you far enough to judge whether it is worth adopting. Covers the five main parts, Attributes and Gameplay Effects, how Cost and Cooldown work, restricting activation with Gameplay Tags, the C++ requirement, and when not to adopt it.

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++.

A contrast between tangled hand-rolled flags on the left and the five GAS parts neatly arranged on the right, with a soft blue clay figure

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

Sponsored

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 jobHand-rolled
Deciding whether it can activateYou manage flags like bCanUse
Spending MPDecrement a variable. You also write the "can I afford it" pre-check
Running the cooldownHold a Timer and report the remaining time to the UI
Applying the effectReduce the target's HP. Defense calculations go here too
Stacking buffs and debuffsDecide how duplicates behave, every time
Blocking on status effectsCheck "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.

A contrast between a hand-rolled diagram where flags and Timers multiply and tangle as skills grow, and a GAS diagram where the shared work is handled by the engine and Abilities sit side by side

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.

A relationship diagram with the Ability System Component at the center and Gameplay Ability, Gameplay Effect, Attribute Set, and Gameplay Tag hanging off it
PartRoleAnalogy
Ability System Component (ASC)The front desk for everything. One per ActorA reception counter
Gameplay AbilityThe contents of one skillThe instructions for "cast a fireball"
Gameplay EffectAn instruction to change an AttributeA slip saying "reduce HP by 30"
Attribute SetThe container for numbers like HP and MPA stat sheet
Gameplay TagA hierarchical string representing state or typeA 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.

A timeline showing a Base value of 10 with a +5 buff producing a Current value of 15, then automatically returning to 10 when the buff expires

What changes an Attribute is a Gameplay Effect (GE). GEs come in three duration types.

Duration typeBehaviorWhen to use
InstantChanges the Base value immediately and disappearsDamage, healing, MP cost
Has DurationChanges the Current value for a set time, then revertsA 5-second attack buff, a movement slow
InfiniteKeeps changing the Current value until explicitly removedEquipment 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).

Sponsored

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.

Two panels side by side: an Instant Effect for Cost reducing MP, and a Duration Effect for Cooldown granting a tag that disappears after 5 seconds
  • Cost: an Instant GE that changes Mana by -20. Assign it to the Ability's Cost Gameplay Effect Class
  • Cooldown: a Has Duration (5 seconds) GE that only grants a tag called Cooldown.Fireball. Assign it to the Ability's Cooldown 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.

A diagram showing Ability activation being blocked while a character carries the State.Stunned tag

Abilities have the following tag fields in their settings.

SettingMeaning
Ability TagsTags representing this Ability itself (Ability.Fireball)
Activation Blocked TagsTags that prevent activation while held (State.Stunned)
Activation Required TagsTags required for activation (Weapon.Equipped)
Activation Owned TagsTags 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 Data in the ASC's Details panel
  • Call InitAbilityActorInfo to 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.

A timeline showing MP going from 100 to 80 when the fireball is cast, reactivation blocked for 5 seconds, and no activation below 20 MP

Setup

ItemValue
PluginEnable Gameplay Abilities and restart the editor
C++ classThe UMyAttributeSet above (Health / Mana)
ASCAdd an Ability System Component to both the player and the target, register UMyAttributeSet, and call InitAbilityActorInfo
Initial valuesHealth = 100, Mana = 100
Gameplay TagsRegister Ability.Fireball / Cooldown.Fireball / State.Stunned
Target sideAn Actor with collision that can receive Overlaps (Generate Overlap Events enabled)
AssumptionSingle 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.

AssetDuration PolicySettings
GE_Cost_FireballInstantModifier: Mana / Add / -20
GE_Cooldown_FireballHas DurationDuration = 5.0, grants tag Cooldown.Fireball
GE_Damage_FireballInstantModifier: 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 Actor under GameplayEffect Components and put Cooldown.Fireball in its Add Tags
  • Before that: write it directly in Granted Tags in 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.

SettingValue
Ability Tags (called Asset Tags in UE 5.8)Ability.Fireball
Cost Gameplay Effect ClassGE_Cost_Fireball
Cooldown Gameplay Effect ClassGE_Cooldown_Fireball
Activation Blocked TagsState.Stunned
Instancing PolicyInstanced 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
GA_Fireball's event graph: from Event ActivateAbility, call Commit Ability, and on success go through Make Outgoing Spec and Spawn Actor to End Ability, while failure branches straight to End Ability

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 itCommit Ability is missing. This is the most common symptom
  • Nothing happens at all (Event ActivateAbility never even fires) → check the three foundation items from the top: the ASC exists, the Attribute Set is registered, InitAbilityActorInfo is called. Also check that Give Ability ran and that State.Stunned is not still applied
  • You can cast once and then nothing → you are not calling End Ability
  • You can cast during the cooldownCooldown Gameplay Effect Class is 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 Mana variable and Set 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).

Sponsored

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.

VerdictSituation
You do not need itAround five skills. Almost no status effects. You want to avoid C++
It makes life easierMore than ten skills. Buffs and debuffs stack. Status effects impose restrictions
Nearly mandatoryLarge 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