Activation policy and the weapon
All your abilities derive from one base class that adds an activation policy. OnTriggered
abilities wait for input or an event. OnGiven abilities run as soon as they're granted, then remove
themselves. That's a neat way to run one-off setup through GAS, like "spawn the axe and put it on his back".
The policy
UENUM(BlueprintType)
enum class EWarriorAbilityActivationPolicy : uint8 { OnTriggered, OnGiven };
void UWarriorGameplayAbility::OnGiveAbility(const FGameplayAbilityActorInfo* ActorInfo,
const FGameplayAbilitySpec& Spec) {
Super::OnGiveAbility(ActorInfo, Spec);
if (AbilityActivationPolicy == EWarriorAbilityActivationPolicy::OnGiven)
if (ActorInfo && !Spec.IsActive())
ActorInfo->AbilitySystemComponent->TryActivateAbility(Spec.Handle);
}
void UWarriorGameplayAbility::EndAbility(...) {
Super::EndAbility(...);
if (AbilityActivationPolicy == EWarriorAbilityActivationPolicy::OnGiven)
if (ActorInfo) ActorInfo->AbilitySystemComponent->ClearAbility(Handle);
}
enum class ... : uint8: a scoped enum (you must writeEWarriorAbilityActivationPolicy::OnGiven, and it doesn't silently convert to int).uint8is required forUENUM(BlueprintType).- Spec and Handle. Granting an ability creates an
FGameplayAbilitySpec: "this ability, at this level, on this ASC". TheHandleis its ID. You activate and clear specs by handle, not ability classes. OnGiveAbilityis a GAS hook that runs when the spec is granted. Checking!Spec.IsActive()avoids activating twice.EndAbility→ClearAbility: once a one-shot ability finishes, it's removed from the ASC. It did its job, so it shouldn't stay granted.- Always call
Super::first in these overrides. GAS does real bookkeeping in the base versions.
Why spawn a weapon through an ability?
- It's consistent: everything the character does goes through the ASC, including setup.
- Enemies can reuse it.
GA_Shared_SpawnWeaponis the shared Blueprint;GA_Hero_SpawnAxeis the hero's version that picksBP_HeroAxe. Same logic, different data. - Later, abilities are granted from a list (a start-up data asset is the usual next step), so "which weapon does this character start with" becomes data, not code.
The weapon actor
AWarriorWeaponBase::AWarriorWeaponBase() {
PrimaryActorTick.bCanEverTick = true; // ← see below
WeaponMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("WeaponMesh"));
SetRootComponent(WeaponMesh);
WeaponCollisionBox = CreateDefaultSubobject<UBoxComponent>(TEXT("WeaponsCollisionBox"));
WeaponCollisionBox->SetupAttachment(GetRootComponent());
WeaponCollisionBox->SetBoxExtent(FVector(20.f));
WeaponCollisionBox->SetCollisionEnabled(ECollisionEnabled::NoCollision);
}
- Collision starts off. The axe mustn't hit anything while it's on his back or mid-idle. The usual next step is to switch it on only during the swing's "active frames", from an anim notify state in the attack montage, and off again after. A hit in that window = damage.
- A separate box, not the mesh's collision. You tune the hit area for game feel, independent of the art.
FORCEINLINE GetWeaponCollisionBox(): a getter, so other classes read it but can't reassign it.- Base → hero subclass (
AWarriorHeroWeapon) is the same idea as the characters: shared parent, hero specifics later (e.g. the hero's weapon anim layer and input).
AWarriorWeaponBase has bCanEverTick = true but no Tick function. That's
template boilerplate. Every spawned weapon registers a tick that does nothing. Set it to false, as the base
character already does. It's a small thing, but it's exactly the point your mock Q5/Q6 answers were about.
Quiz yourself
OnTriggered vs OnGiven: what's the difference in behaviour?
OnTriggered waits for something (input, an event) to activate it. OnGiven activates itself the moment it's granted and, in this project, clears itself from the ASC when it ends.
What is an FGameplayAbilitySpec, and what's its handle for?
A granted instance of an ability on an ASC (class, level, input and so on). The handle is its ID, used to activate, cancel or clear that specific grant.
Why clear an OnGiven ability in EndAbility?
It's a one-shot (spawn the weapon). Leaving it granted is clutter, and it could be activated again by accident.
Why does the weapon's collision box start at NoCollision?
It should only register hits during the attack's active frames. It gets toggled on and off from the attack animation (anim notify state), so carrying or idling doesn't deal damage.
Why must a UENUM(BlueprintType) be : uint8?
Blueprint only supports byte-sized enums.
What's wasteful in AWarriorWeaponBase's constructor?
bCanEverTick = true with no Tick. Each weapon registers an empty tick function every frame. Set it false.