Gameplay Ability System setup
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)
| Term | Is | God of War example |
|---|---|---|
| Ability System Component | The hub component. Everything goes through it. | One on Kratos, one on each draugr |
| Gameplay Ability | A thing you can do: activation rules, cost, cooldown, logic | Light attack, axe throw, spartan rage |
| Gameplay Effect | A data-only rule that changes attributes or tags (instant, for a duration, or permanent) | Damage, a frost slow, rage building |
| Attribute Set | The numbers: FGameplayAttributeData fields | Health, Rage, AttackPower |
| Gameplay Tags | Current state, and the rules about it | Status.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 actor | Avatar actor | |
|---|---|---|
| Means | Who owns the ASC and its state | The body in the world: animations, location, collision |
| Here | The character | The character |
| In Lyra / multiplayer shooters | The PlayerState | The 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.
GetAbilitySystemComponent() const;has nooverride. It compiles, butoverridemakes the compiler check the signature really matches the base. Add it: it costs nothing and turns a silent mistake into an error.bCanEverTick = falseon the base character: good. Nothing in it needs per-frame work (see Tick).bReceivesDecals = false: blood and scorch decals projected onto the world shouldn't paint the moving character.- Raw pointers in
UPROPERTYwork. UE5 style isTObjectPtr<>. What keeps them alive is theUPROPERTY, not the pointer type (your mock Q3).
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.