Gameplay Ability System·Gameplay Abilities·Programming·Unreal Engine

Introduction to GAS: The Six Pieces of Unreal's Gameplay Ability System

What the Gameplay Ability System actually is: the problem it solves, the six pieces it is built from, how one attack flows through all of them, and the three rules that prevent most GAS bugs.

If you look up the Gameplay Ability System ( GAS ) for the first time, you will find two things: people saying it is the best way to build abilities and stats in Unreal Engine, and people saying that the learning curve is brutal. Both things are true. I have been building combat and RPG systems with GAS for years on shipped games like Lords of the Fallen and Killsquad, and for me the hardest part was never a specific feature. The problem is that nobody gives you the map first, so every tutorial feels like it starts in the middle.

So this post is that map: what GAS actually solves, the six pieces it is built from, how one attack passes through all of them, and the three rules that will save you from most GAS bugs.

What GAS solves

Think for a moment how you would build abilities without it. Your character class gets a float Health and a hand-written TakeDamage(), then a bIsStunned boolean, then bIsBlocking and bCanAttack. Cooldowns become timers, and every time something needs to work in multiplayer you add another RPC somewhere in the codebase. I am not making fun of this approach, we have all shipped prototypes exactly like this, and for a small game with one or two attacks it is completely fine.

The problem is that this stops scaling the moment your systems start interacting. You add a poison that ticks over time, and now the stun needs to block the attack, and the attack needs to interrupt the block, and a second player needs to see all of it happening on their screen too. Every new mechanic you add has to be checked against all the ones you already have.

GAS exists because Epic kept running into these same three problems on their own games and packaged the answer as an engine plugin ( the same system their Lyra sample project is built on ):

  • Abilities and stats become data. An ability is an asset, a stat change is an asset. The logic lives in reusable classes and the numbers live in data, so designers can create and tune new attacks and buffs without touching C++ every time.
  • Stats are replicated and server-authoritative. You never write a health value directly, you apply an effect and the system handles replication and clamping for you.
  • Client prediction is built in. When the player presses a button, the ability runs immediately on their machine and the server confirms or rejects it afterwards. The game feels instant on a laggy connection without letting the clients decide the outcome, and you do not have to invent that protocol yourself ( believe me, you do not want to ).

That last point is the real reason to learn GAS in my opinion. The data-driven part you could build yourself, but networked ability prediction is one of those problems that looks doable at the beginning and then takes tons of time to get right.

The official entry point is Epic’s Gameplay Ability System documentation, and I keep a list of the GAS resources I actually recommend if you want to go deeper after this post.

When you should not use GAS

A quick warning before we start, because once GAS clicks you will be tempted to route everything through it. Pure UI logic does not belong in an ability. A scripted door that opens once does not need tag requirements and a cooldown. If nothing replicates and nothing is a stat, you simply do not need GAS there.

The test I use is this one: if the behavior changes a replicated number, is gated by character state, or has to feel responsive with latency, it belongs in GAS. If not, think twice.

The six pieces

GAS is built from six systems that work together. Let’s see what each one does, and where I have covered it in more depth.

  • The Ability System Component ( ASC ) is the hub of everything. It is an actor component ( UAbilitySystemComponent ) that owns everything else on this list: the abilities a character has, the effects currently applied to it, its attributes and its tags. When you are debugging and asking yourself things like “why did this ability not fire?” or “what buffs does this enemy have?”, the answer is always found by inspecting the ASC.
  • Gameplay Abilities are the actions of your character: attack, dodge, cast, reload. A UGameplayAbility is a self-contained piece of logic with its own activation rules, cost and cooldown. Abilities are granted to an ASC and activated through it, and I wrote about how ability instancing policies work and the ability tasks you will use inside them if you want to go deeper on how abilities work from the inside.
  • Gameplay Effects are the only correct way to change an attribute, and this surprises everyone at first: damage is a Gameplay Effect, but so is the stamina cost of your dodge, the cooldown of your fireball, a strength buff, and a poison that ticks every second. If something changes a number or stays on a character over time, it is a UGameplayEffect. Since UE 5.3 their behavior is built from modular pieces, which I covered in the Gameplay Effect Components post, and their duration rules have some real traps that I covered in Making Sense of Gameplay Effect Durations.
  • Attributes are the stats: Health, Stamina, Strength, MoveSpeed. They live in UAttributeSet objects owned by the ASC, they are replicated and server-authoritative, and they have pre-change and post-change callbacks where you can clamp values and react to thresholds. I have a full tutorial on creating your own Attribute Sets.
  • Gameplay Tags are how you represent state. They are hierarchical names like CombatStateStunned that gate everything: if an ability can activate, if an effect can apply, in what state a character is. Technically they are not part of the GAS plugin ( tags are an engine-wide module ), but GAS uses them for everything. I wrote about using Gameplay Tags to store game state before, and it is still one of my favorite patterns.
  • Gameplay Cues are the cosmetic part: the blood splat, the impact sound, the camera shake. They are triggered through special tags ( everything under GameplayCue ) and they are fire-and-forget by design. A cue should never affect gameplay state, and this is exactly what makes it safe to play them predictively on the clients. If a client mispredicts a particle effect nothing really happens, but if it mispredicts your health you have a desync.

How one attack flows through the pieces

The easiest way to understand the six pieces is to see them as a pipeline, so let’s follow one light attack from the button press to the blood splat:

  1. Input. The player presses attack. Your input layer sends a gameplay event to the character’s ASC ( or activates the ability directly, both patterns are common ).
  2. The ASC checks the gate. TryActivateAbility runs CanActivateAbility on your melee ability: are there blocking tags present ( ex: a stun )? Can the cost be paid? Is it on cooldown? As you can see, the three checks are answered with tags, attributes and effects, the pieces are already working together before the ability even starts.
  3. The ability runs. It commits its cost and cooldown ( two Gameplay Effects, the word “effect” keeps showing up ) and then plays the attack montage.
  4. The hit becomes an effect. When the swing connects, the ability builds a damage effect spec and applies it to the victim’s ASC.
  5. The effect changes an attribute. The damage calculation reads the offensive attributes of the attacker and the defenses of the victim, computes the final number, and lowers the victim’s Health. This happens on the server, and the new value replicates to every client.
  6. A cue plays the feedback. The damage effect fires a cue tag like GameplayCueCombatImpact, and the notify listening for that tag plays the VFX and the sound.

So the shape is: input → ASC → ability → effect → attribute → cue, with tags gating every step of the way. Every combat interaction you will build in GAS is a variation of this same flow. A heal, a fireball, a stagger, a buff pickup, they all walk the same path.

That being said, the pieces do not only flow in one direction. Effects can grant abilities ( a burning status that gives you a Panic ability while it lasts ), attribute callbacks can apply more effects ( stamina reaching zero applies a winded debuff ), and the tags granted by effects gate which other abilities and effects are allowed to run. So more than a pipeline, in reality it is a web, the flow above is simply the most common path through it.

Where does the ASC live?

There is one decision you should make consciously from day one: which actor owns the ASC. There are two common patterns in shipped UE5 games.

On the pawn. The character owns its own ASC. The setup is one InitAbilityActorInfo(this, this) call, players and AI run through the identical code path, and everything is simple. The catch is that the ASC dies with the pawn, so buffs, cooldowns and granted abilities are gone the moment you respawn.

On the PlayerState. The ASC lives on the PlayerState, which survives death and respawn, so your buffs and cooldowns carry over for free. This is what Epic’s Lyra sample does. It costs you a little bit of complexity, because the owner and the avatar are now different actors and you have to re-point the avatar on every possession.

None of the two is more correct than the other. In my case, working on action games where death is a hard reset anyway, pawn-owned is the natural fit, and it keeps AI enemies ( which have no PlayerState ) on the same code path as the players. If you are building something with fast respawns where loadouts and cooldowns should persist, then the Lyra pattern is worth the extra setup. The important thing is to decide it on purpose, if not the decision gets made by accident, and half of the “my buff disappeared” questions I get come exactly from that.

The three rules that prevent most GAS bugs

After all this, everything else in GAS is built on top of three rules. Nearly every GAS bug I have debugged over the years, mine included, comes from one of these rules being broken.

Rule 1: the server is authoritative. Gameplay-changing logic runs on the server. Clients predict to hide the latency, but they never decide. If you catch yourself changing a real value on a client and hoping that it sticks, you are fighting the framework.

That being said, let me be honest about how this evolves in production. I started strictly server-authoritative, which is the textbook model and the one GAS is really built for. Over the years, working on coop games, I have moved more and more authority to the client, because it simply makes networked combat feel more responsive, and in coop the players are on the same team, so cheating matters much less than in competitive PvP. But that is a trade you make later, once you understand the model well. While you are learning GAS, stay server-authoritative.

Rule 2: attributes change only through Gameplay Effects. Never write Health = X from gameplay code:

// This works in standalone and desyncs in multiplayer:
HealthSet->SetHealth(HealthSet->GetHealth() - 25.f);

// This is the GAS way: build an effect spec and apply it.
FGameplayEffectSpecHandle Spec =
    ASC->MakeOutgoingSpec(DamageEffectClass, /*Level=*/1.f, ASC->MakeEffectContext());
ASC->ApplyGameplayEffectSpecToTarget(*Spec.Data, TargetASC);

Why so strict? Because effects are the place where replication, prediction, clamping and stacking all hook in. A raw write skips all of those systems at once, and the value will snap back or desync the moment more than one machine is involved.

Rule 3: tags gate everything. “Is the player stunned?” is not a boolean on your character class anymore, it is a question you ask the ASC:

if (ASC->HasMatchingGameplayTag(
        FGameplayTag::RequestGameplayTag(TEXT("Combat.State.Stunned"))))
{
    return;
}

This is incredibly useful, because now you do not have a variable that tons of systems need to remember to check and reset. The stun is a tag granted by a stun effect, it disappears when the effect expires, and every ability that should fail while stunned simply lists CombatStateStunned in its blocked tags. If you design your states as tags from the beginning, a big part of the system becomes much simpler.

By the way, these rules also work as an alarm for your own code. Whenever you are about to add a raw boolean or a direct attribute write “just this once”, stop and ask yourself which rule you are breaking. The fix is almost always to express it as a tag or as an effect, and usually the resulting code ends up shorter, because you are reusing things that GAS already gives you.

Recap

  • Decide on purpose where the ASC lives, because that defines what survives a respawn: on the pawn everything resets with the character, on the PlayerState ( the Lyra pattern ) buffs and cooldowns carry over. Half of the “my buff disappeared” problems are this decision being made by accident.
  • Three rules prevent most GAS bugs: the server decides, attributes change only through Gameplay Effects, and states are tags instead of booleans. A raw write like Health = X works in standalone and desyncs in multiplayer.
  • The six pieces connect in one recurring shape: input → ASC → ability → effect → attribute → cue, with tags gating every step. If you can trace that flow from memory, every GAS tutorial after this one gets easier.
  • GAS gives you three things: data-driven abilities and stats, replicated server-authoritative attributes, and built-in client prediction. The prediction alone is worth the learning curve, and if the behavior does not touch a replicated number, character state or latency, you probably do not need GAS for it.

Keep reading

GiveAbilityAndActivateOnce: One-Shot Abilities Without the Cleanup

GiveAbilityAndActivateOnce: One-Shot Abilities Without the Cleanup

Grant an ability, activate it once, and let GAS remove the spec by itself.
Learning about Ability Instancing Policy in GAS

Learning about Ability Instancing Policy in GAS

So many instancing types!
Making Sense of Gameplay Effect Durations

Making Sense of Gameplay Effect Durations

Instant vs Has Duration vs Infinite