SuperAPI AI Directory

gameplay-ability-system

Coding & development

View repository

Updated 11-06 16:22

About this skill

Gameplay Ability System (GAS)

GAS is Epic's framework for abilities, attributes, and effects with networking built in. It is powerful but heavyweight — adopt it for games with many interacting abilities/stats; for a couple of simple actions, plain components may be simpler.

When to use this skill

  • Implementing player or enemy abilities with cooldowns, costs, and tag gating.
  • Attributes (health/stamina/mana/armor) modified by buffs, debuffs, and damage.
  • Networked, server-authoritative ability activation with client prediction.
  • Gameplay Cues for cosmetic effects that replicate without bespoke RPCs.
  • Async ability logic: waiting for animation, input, events, or delays inside an ability.

Setup

  1. Enable the Gameplay Abilities plugin (.uplugin → Plugins, or add GameplayAbilitiesPlugin to your .uproject).
  2. Add "GameplayAbilities", "GameplayTags", "GameplayTasks" to your module's Build.cs PublicDependencyModuleNames (see module-and-build-system).
  3. Call UAbilitySystemGlobals::Get().InitGlobalData() exactly once at startup — typically in UAssetManager::StartInitialLoading or your game module's startup function. This is required for target data and montage prediction; omitting it causes silent failures.

Core pieces

Type Role
UAbilitySystemComponent (ASC) Hub: holds granted abilities, active effects, attribute sets, owned tags
UAttributeSet Declares attributes (FGameplayAttributeData) and overrides change callbacks
UGameplayAbility A granted, activatable ability with cost, cooldown, and async logic
UGameplayEffect (GE) Data-driven attribute/tag change: Instant, HasDuration, or Infinite
FGameplayAbilitySpec A granted ability instance (class, level, input ID, source object)
UAbilityTask Async step inside an ability (wait for event, montage, delay, target data)
Gameplay Cues Cosmetic feedback (VFX/SFX) keyed by GameplayCue.* tags, network-efficient

Where the ASC lives

The ASC can sit on the Pawn or on a separate object (commonly APlayerState):

  • Multiplayer player characters: ASC on the PlayerState so it survives respawn. The Pawn implements IAbilitySystemInterface::GetAbilitySystemComponent() returning the PlayerState's ASC. Call ASC->InitAbilityActorInfo(OwnerActor, AvatarPawn) on the server in PossessedBy and on the client in OnRep_PlayerState (or BeginPlay for listen-server pawns).
  • AI / simple actors: ASC directly on the Pawn; call InitAbilityActorInfo(this, this).
// Example: Pawn that delegates to PlayerState's ASC
class AMyCharacter : public ACharacter, public IAbilitySystemInterface
{
    GENERATED_BODY()
public:
    virtual UAbilitySystemComponent* GetAbilitySystemComponent() const override
    {
        if (AMyPlayerState* PS = GetPlayerState<AMyPlayerState>())
            return PS->GetAbilitySystemComponent();
        return nullptr;
    }
};

GAS does not support a single Actor having multiple ASCs (ambiguous queries). Multiple Actors can share one ASC (e.g. equipment routing to the character's ASC).

Attributes

Attributes are FGameplayAttributeData properties in a UAttributeSet subclass. Use the ATTRIBUTE_ACCESSORS macro pattern (documented in AttributeSet.h:419) to generate the four helpers: a static FGameplayAttribute getter, a float current-value getter, a setter that routes through the ASC, and a base-value initter.

// MyAttributeSet.h
#pragma once
#include "AttributeSet.h"
#include "AbilitySystemComponent.h"
#include "MyAttributeSet.generated.h"

// Generates Get<Name>Attribute(), Get<Name>(), Set<Name>(), Init<Name>()
#define ATTRIBUTE_ACCESSORS(ClassName, PropertyName) \
    GAMEPLAYATTRIBUTE_PROPERTY_GETTER(ClassName, PropertyName) \
    GAMEPLAYATTRIBUTE_VALUE_GETTER(PropertyName) \
    GAMEPLAYATTRIBUTE_VALUE_SETTER(PropertyName) \
    GAMEPLAYATTRIBUTE_VALUE_INITTER(PropertyName)

UCLASS()
class MYGAME_API UMyAttributeSet : public UAttributeSet
{
    GENERATED_BODY()
public:
    // ReplicatedUsing is required for clients to see attribute changes
    UPROPERTY(BlueprintReadOnly, Category="Attributes", ReplicatedUsing=OnRep_Health)
    FGameplayAttributeData Health;
    ATTRIBUTE_ACCESSORS(UMyAttributeSet, Health)

    UPROPERTY(BlueprintReadOnly, Category="Attributes", ReplicatedUsing=OnRep_MaxHealth)
    FGameplayAttributeData MaxHealth;
    ATTRIBUTE_ACCESSORS(UMyAttributeSet, MaxHealth)

    UFUNCTION() void OnRep_Health(const FGameplayAttributeData& OldHealth);
    UFUNCTION() void OnRep_MaxHealth(const FGameplayAttributeData& OldMaxHealth);

    // Clamp here; do not trigger game logic (use PostGameplayEffectExecute for that)
    virtual void PreAttributeChange(const FGameplayAttribute& Attr, float& NewValue) override;
    // React to confirmed changes (death, UI updates, etc.)
    virtual void PostGameplayEffectExecute(const FGameplayEffectModCallbackData& Data) override;
};

Register the attribute set by creating it as a subobject on the ASC's owning actor:

// In the owning actor's constructor
AttributeSet = CreateDefaultSubobject<UMyAttributeSet>(TEXT("AttributeSet"));

The ASC auto-discovers UAttributeSet subobjects on the same actor. Never write attribute FGameplayAttributeData fields directly at runtime — always apply a UGameplayEffect so that prediction, replication, and aggregation work correctly.

See references/attributes-and-effects.md for PreAttributeChange vs PostGameplayEffectExecute usage, clamping patterns, and meta-attribute (damage) patterns.

Abilities

// GA_Dash.h
UCLASS()
class MYGAME_API UGA_Dash : public UGameplayAbility
{
    GENERATED_BODY()
public:
    UGA_Dash();

    // Primary override: do ability work here. Must call CommitAbility then EndAbility.
    virtual void ActivateAbility(const FGameplayAbilitySpecHandle Handle,
        const FGameplayAbilityActorInfo* ActorInfo,
        const FGameplayAbilityActivationInfo ActivationInfo,
        const FGameplayEventData* TriggerEventData) override;

    virtual bool CanActivateAbility(const FGameplayAbilitySpecHandle Handle,
        const FGameplayAbilityActorInfo* ActorInfo,
        const FGameplayTagContainer* SourceTags = nullptr,
        const FGameplayTagContainer* TargetTags = nullptr,
        FGameplayTagContainer* OptionalRelevantTags = nullptr) const override;
};

Key ability lifecycle: CanActivateAbility (read-only check) → CommitAbility (consume cost + cooldown) → ability logic (launch tasks) → EndAbility. Failing to call EndAbility leaves the ability in an active state indefinitely, blocking further uses and any abilities it blocks.

Grant on server:

// Server only — GiveAbility is authority-only
FGameplayAbilitySpecHandle Handle = ASC->GiveAbility(
    FGameplayAbilitySpec(UGA_Dash::StaticClass(), /*Level=*/1, /*InputID=*/INDEX_NONE));

Activate:

ASC->TryActivateAbilityByClass(UGA_Dash::StaticClass());
// or by handle:
ASC->TryActivateAbility(Handle);

Cost and cooldown are themselves UGameplayEffect assets referenced by CostGameplayEffectClass and CooldownGameplayEffectClass on the ability. Set these in Blueprint subclasses.

See references/gameplay-abilities.md for instancing policies, net execution policies, tag gating, FGameplayAbilitySpec fields, and event-triggered activation.

Gameplay Effects

GEs are usually data-only Blueprint assets (subclass UGameplayEffect). Choose a duration policy (EGameplayEffectDurationType: Instant, HasDuration, Infinite), add Modifiers (attribute + operation + magnitude), and optionally add GE Components (grants/requires/removes tags, immunity, stacking). Apply from C++:

// Apply a damage GE from one ASC to another
FGameplayEffectContextHandle Ctx = SourceASC->MakeEffectContext();
Ctx.AddSourceObject(this);
FGameplayEffectSpecHandle Spec = SourceASC->MakeOutgoingSpec(
    DamageEffectClass, /*Level=*/1.f, Ctx);
if (Spec.IsValid())
{
    SourceASC->ApplyGameplayEffectSpecToTarget(*Spec.Data.Get(), TargetASC);
}

UGameplayEffect became component-based in 5.3 (UGameplayEffectComponent subclasses). The legacy monolithic properties still work but new behavior is authored via GE Components. See references/attributes-and-effects.md for GE Components, execution calculations, and stacking.

Ability Tasks

Tasks handle async steps inside an ability. Use NewAbilityTask<T> (not NewObject) and call ReadyForActivation() to start it. Always override OnDestroy to unregister callbacks.

// Inside ActivateAbility — wait for a montage notify then end
void UGA_Dash::ActivateAbility(...)
{
    if (!CommitAbility(Handle, ActorInfo, ActivationInfo)) { EndAbility(...); return; }

    UAbilityTask_PlayMontageAndWait* Task =
        UAbilityTask_PlayMontageAndWait::CreatePlayMontageAndWaitProxy(
            this, NAME_None, DashMontage);
    Task->OnCompleted.AddDynamic(this, &UGA_Dash::OnMontageCompleted);
    Task->OnCancelled.AddDynamic(this, &UGA_Dash::OnMontageCancelled);
    Task->ReadyForActivation();
}

void UGA_Dash::OnMontageCompleted()
{
    EndAbility(CurrentSpecHandle, CurrentActorInfo, CurrentActivationInfo,
               /*bReplicateEndAbility=*/true, /*bWasCancelled=*/false);
}

Common built-in tasks: UAbilityTask_WaitDelay, UAbilityTask_PlayMontageAndWait, UAbilityTask_WaitGameplayEvent, UAbilityTask_WaitTargetData, UAbilityTask_WaitAttributeChange. All live under Abilities/Tasks/ in the plugin's Public folder.

See references/ability-tasks-and-cues.md for custom task authoring, NewAbilityTask, output delegate patterns, and Gameplay Cues.

Gameplay Cues

Cues are cosmetic effects (particles, sounds, decals) driven by GameplayCue.* tags. They do not need bespoke RPCs — the ASC handles replication automatically.

// Fire a one-shot cue (e.g. impact spark)
ASC->ExecuteGameplayCue(FGameplayTag::RequestGameplayTag("GameplayCue.Impact.Spark"), Ctx);

// Add a persistent cue (e.g. burning aura) and remove it later
ASC->AddGameplayCue(FGameplayTag::RequestGameplayTag("GameplayCue.Status.Burning"), Ctx);
ASC->RemoveGameplayCue(FGameplayTag::RequestGameplayTag("GameplayCue.Status.Burning"));

Cue handlers are UGameplayCueNotify_Static (one-shot, OnExecute) or UGameplayCueNotify_Actor (spawns an actor, OnActive/WhileActive/OnRemove). Must be tagged GameplayCue.* and registered with the GameplayCue manager (auto-scanned from configured paths).

Networking model

  • The server is authoritative. Grant/remove abilities server-only; effects apply server-side.
  • Set replication mode on the ASC: Full (single-player or small peer-to-peer), Mixed (player-owned ASC in multiplayer), Minimal (AI-owned ASC).
  • Abilities with LocalPredicted net execution policy run immediately on the predicting client and are confirmed or rolled back by the server. Use Gameplay Cues for cosmetic output — they replicate without blocking server authority.
  • Replicated attributes require ReplicatedUsing=OnRep_<Name> and DOREPLIFETIME_CONDITION in GetLifetimeReplicatedProps (handled automatically by the ASC for attribute sets it owns, but you must implement GetLifetimeReplicatedProps on the attribute set).

Gotchas

  • InitGlobalData() not called — montage and target-data prediction break silently.
  • Writing attribute fields directly at runtime — bypasses replication, prediction, and aggregation. Always go through Gameplay Effects.
  • ASC on Pawn for a respawning multiplayer player — state resets on death; put it on APlayerState.
  • Missing module deps (GameplayAbilities/GameplayTags/GameplayTasks) — link errors.
  • EndAbility not called — the ability stays "active" forever, blocking further uses.
  • NonInstanced removed in 5.5 — UE_DEPRECATED_FORGAME(5.5, ...) in 5.8; use InstancedPerActor as the default.
  • Wrong replication mode — Mixed required for player-owned ASCs in multiplayer; Minimal-mode ASCs won't replicate GE data to simulated proxies.
  • Cue tags not prefixed GameplayCue. — the manager won't find or route them.
  • Forgot to call CommitAbility — cost/cooldown not consumed; server may reject prediction.
  • FindOrAddComponent/AddComponent in a GE constructor — NewObject with an empty name fatal-asserts at CDO construction and kills the editor at module load. Use CreateDefaultSubobject + GEComponents.Add instead (see references/attributes-and-effects.md).
  • SetByCaller cost/cooldown with bare CommitAbility — the magnitude is never set, so the spec applies at 0 with a warning. Override ApplyCost/ApplyCooldown (see references/gameplay-abilities.md).
  • No GameplayCueNotifyPaths configured — the cue manager falls back to scanning all of /Game/ (startup warning + scan cost). Set [/Script/GameplayAbilities.AbilitySystemGlobals] +GameplayCueNotifyPaths=/Game/GameplayCues.

Version notes

  • NonInstanced policy deprecated in 5.5; InstancedPerActor is the recommended default.
  • AbilityTags deprecated in 5.5: read GetAssetTags(), set defaults with SetAssetTags(...) (constructor only).
  • UGameplayEffect became component-based in 5.3 (EGameplayEffectVersion::Modular53). Legacy monolithic GE data still works but new functionality is via UGameplayEffectComponent subclasses.
  • GetAbilitySystemComponentFromActorInfo_Checked() deprecated in 5.5; use GetAbilitySystemComponentFromActorInfo_Ensured().

References & source material

Engine source (UE 5.8, Engine/Plugins/Runtime/GameplayAbilities/Source/GameplayAbilities/Public/):

  • AbilitySystemComponent.h — UAbilitySystemComponent: GiveAbility:949, TryActivateAbility:1040, TryActivateAbilityByClass:1032, InitAbilityActorInfo:1523, MakeOutgoingSpec:372, MakeEffectContext:376, ApplyGameplayEffectSpecToTarget:339, SetReplicationMode:268, ExecuteGameplayCue:887, AddGameplayCue:891, RemoveGameplayCue:898, EGameplayEffectReplicationMode enum:81.
  • AttributeSet.h — UAttributeSet:185, FGameplayAttributeData:21, PreAttributeChange:220, PostGameplayEffectExecute:206, ATTRIBUTE_ACCESSORS pattern:419, GAMEPLAYATTRIBUTE_PROPERTY_GETTER:428, GAMEPLAYATTRIBUTE_VALUE_GETTER:435.
  • Abilities/GameplayAbility.h — UGameplayAbility:91, ActivateAbility:574, CommitAbility:336, EndAbility:604, CanActivateAbility:263, CancelAbility:299, EGameplayAbilityInstancingPolicy:37, EGameplayAbilityNetExecutionPolicy:59 (in Abilities/GameplayAbilityTypes.h).
  • GameplayAbilitySpec.h — FGameplayAbilitySpec:168 (class, level, InputID, handle).
  • GameplayEffect.h — UGameplayEffect, EGameplayEffectDurationType:686 (Instant/Infinite/HasDuration), EGameplayEffectVersion:95 (Modular53).
  • AbilitySystemInterface.h — IAbilitySystemInterface::GetAbilitySystemComponent():30.
  • AbilitySystemGlobals.h — UAbilitySystemGlobals::InitGlobalData():69.
  • Abilities/Tasks/AbilityTask.h — UAbilityTask:90, NewAbilityTask<T>:136.
  • GameplayCueNotify_Static.h — UGameplayCueNotify_Static:19.
  • GameplayCueNotify_Actor.h — AGameplayCueNotify_Actor (actor-spawning cue notify):20.

Related skills: gameplay-tags (GAS is tag-driven throughout), networking-and-replication, animation-system (montage tasks).

Official docs (UE 5.8):

Deep-dive references in this skill: