Gameplay Ability System setup

Commits 71fc60c, ebd87d3 · AWarriorBaseCharacter, UWarriorAbilitySystemComponent, UWarriorAttributeSet · 10 min

In one breath

GAS is Epic's framework for "things characters can do and have done to them". The Ability System Component (ASC) is its hub on each character. It holds granted abilities, active effects, current tags and attribute sets (health, rage…). You've built the skeleton: the ASC and an empty attribute set on the base character, plus the interface GAS uses to find them.

The vocabulary (learn these five)

TermIsGod of War example
Ability System ComponentThe hub component. Everything goes through it.One on Kratos, one on each draugr
Gameplay AbilityA thing you can do: activation rules, cost, cooldown, logicLight attack, axe throw, spartan rage
Gameplay EffectA data-only rule that changes attributes or tags (instant, for a duration, or permanent)Damage, a frost slow, rage building
Attribute SetThe numbers: FGameplayAttributeData fieldsHealth, Rage, AttackPower
Gameplay TagsCurrent state, and the rules about itStatus.Blocking; "can't attack while Status.Stunned"

Why use it instead of hand-rolled health variables and if (bIsAttacking) flags? Abilities, cooldowns, buffs and "can't do X while Y" rules get complicated fast. GAS gives one consistent model for them (and networking, if you ever need it). The cost: it's big, and the setup is boilerplate-heavy. You've just written that boilerplate.

Your code

class AWarriorBaseCharacter : public ACharacter, public IAbilitySystemInterface
{
    virtual UAbilitySystemComponent* GetAbilitySystemComponent() const;   // from the interface
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly) UWarriorAbilitySystemComponent* WarriorAbilitySystemComponent;
    UPROPERTY(VisibleAnywhere, BlueprintReadOnly) UWarriorAttributeSet* WarriorAttributeSet;
};

AWarriorBaseCharacter::AWarriorBaseCharacter() {
    PrimaryActorTick.bCanEverTick = false;
    GetMesh()->bReceivesDecals = false;
    WarriorAbilitySystemComponent = CreateDefaultSubobject<UWarriorAbilitySystemComponent>(TEXT("..."));
    WarriorAttributeSet = CreateDefaultSubobject<UWarriorAttributeSet>(TEXT("..."));
}

void AWarriorBaseCharacter::PossessedBy(AController* NewController) {
    Super::PossessedBy(NewController);
    if (WarriorAbilitySystemComponent) WarriorAbilitySystemComponent->InitAbilityActorInfo(this, this);
}

IAbilitySystemInterface

Multiple inheritance, but of an interface (the I prefix): a class with only a virtual function to implement. GAS code that has an arbitrary AActor* (say, whatever your axe just hit) asks "do you implement this interface? then give me your ASC", without casting to your character class. That's the same reason interfaces help with reference bloat: the caller doesn't depend on your concrete class.

Owner vs avatar: InitAbilityActorInfo(this, this)

Owner actorAvatar actor
MeansWho owns the ASC and its stateThe body in the world: animations, location, collision
HereThe characterThe character
In Lyra / multiplayer shootersThe PlayerStateThe pawn

Putting the ASC on the PlayerState makes buffs and attributes survive death and respawn (a new pawn is spawned; the PlayerState stays). Single-player hack-and-slash where enemies just die? Putting it on the character is simpler and fine, and it's what the course does.

Why PossessedBy?

Actor info has to be set after the character exists and has a controller. PossessedBy runs when a controller takes over the pawn. Caveat: it only runs on the server. In a networked game, clients would also need to initialise, usually in OnRep_PlayerState. Single-player, it doesn't matter, but it's a classic GAS interview question.

The attribute set is empty, and how the ASC finds it

Created as a default subobject of the same actor, the ASC picks it up automatically when it initialises. Next you'll fill it with FGameplayAttributeData Health etc. and the ATTRIBUTE_ACCESSORS macro boilerplate.

Small things in your code worth a question each

Quiz yourself

Name what the ASC holds (four things).

Granted abilities, active gameplay effects, owned gameplay tags, attribute sets.

Gameplay Ability vs Gameplay Effect, in one sentence each.

An ability is something you do (with logic, activation rules, costs, cooldowns). An effect is a data rule that changes attributes or tags: instant, for a duration, or permanent.

Why implement IAbilitySystemInterface?

So any GAS code holding an AActor* can get its ASC without casting to your specific class.

Owner vs avatar actor? Why would a shooter put the ASC on the PlayerState?

Owner = owns the ASC's state; avatar = the physical body. On the PlayerState, attributes and effects survive the pawn dying and respawning.

Why is InitAbilityActorInfo in PossessedBy, and what's the multiplayer catch?

It needs the pawn to be possessed. PossessedBy is server-only, so networked clients must also init elsewhere (typically OnRep_PlayerState).

What does the missing override on GetAbilitySystemComponent risk?

Nothing today. But if the signature drifted (e.g. you dropped const), you'd be declaring a new function instead of overriding. Here the base is pure virtual, so you'd get a confusing "abstract class" error somewhere else. With an ordinary virtual base it's worse: it compiles, and the base version runs silently. override turns both into a clear error on that line.