Activating abilities when Granted to ASC (Passive Abilities)
Updated for UE 5.8: the engine now calls
UGameplayAbility::OnAvatarSetboth when an ability is granted (if an avatar already exists) and whenever the avatar changes, so one override replaces the oldOnGiveAbilityoverride plus theInitAbilityActorInfoloop. TheSpec.ActivationInfopredicting check was removed because that member is deprecated.
Here is a good way to activate abilities as soon as they are given to the ASC. It is useful for passive abilities, meaning abilities that run automatically without triggers or user activation (a passive regen, an aura, etc.).
First we add a bool to our custom Gameplay Ability class.
/** If true, the ability is activated automatically when it is granted or when the avatar actor is set. */
UPROPERTY(EditDefaultsOnly, Category = "Activation")
bool bPassiveAbility = false;
Then we override OnAvatarSet and add a function that does the activation.
virtual void OnAvatarSet(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) override;
/** Activates the ability if it is passive and this machine is the right one to run it, given the net execution policy. */
void TryActivatePassiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) const;
Why OnAvatarSet? UAbilitySystemComponent::OnGiveAbility ends up calling it if the ASC already has an avatar, and InitAbilityActorInfo calls it for every granted ability when the avatar changes. That covers both cases we care about: abilities granted after the avatar exists, and abilities granted before the avatar was set (very common when the ASC lives on the PlayerState). The base implementation is empty, but call Super anyway.
void UKaosGameplayAbility::OnAvatarSet(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec)
{
Super::OnAvatarSet(ActorInfo, Spec);
TryActivatePassiveAbility(ActorInfo, Spec);
}
And now the actual activation logic.
void UKaosGameplayAbility::TryActivatePassiveAbility(const FGameplayAbilityActorInfo* ActorInfo, const FGameplayAbilitySpec& Spec) const
{
if (!bPassiveAbility || !ActorInfo || Spec.IsActive())
{
return;
}
UAbilitySystemComponent* ASC = ActorInfo->AbilitySystemComponent.Get();
const AActor* AvatarActor = ActorInfo->AvatarActor.Get();
// If avatar actor is torn off or about to die, don't try to activate it.
if (!ASC || !AvatarActor || AvatarActor->GetTearOff() || AvatarActor->GetLifeSpan() > 0.0f)
{
return;
}
const EGameplayAbilityNetExecutionPolicy::Type Policy = GetNetExecutionPolicy();
const bool bIsLocalExecution = (Policy == EGameplayAbilityNetExecutionPolicy::LocalPredicted) || (Policy == EGameplayAbilityNetExecutionPolicy::LocalOnly);
const bool bIsServerExecution = (Policy == EGameplayAbilityNetExecutionPolicy::ServerOnly) || (Policy == EGameplayAbilityNetExecutionPolicy::ServerInitiated);
const bool bClientShouldActivate = ActorInfo->IsLocallyControlled() && bIsLocalExecution;
const bool bServerShouldActivate = ActorInfo->IsNetAuthority() && bIsServerExecution;
if (bClientShouldActivate || bServerShouldActivate)
{
ASC->TryActivateAbility(Spec.Handle);
}
}
This checks that the ability is passive and not already running, then checks the avatar actor to make sure it is sane to activate (not torn off, and not already in its dying lifespan countdown). Finally we make sure we are the correct network client or authority for the ability’s net execution policy. For example, we are the server for Server Only and Server Initiated, or we are locally controlled for Local Predicted and Local Only.
Notes and gotchas:
OnAvatarSetcan fire more than once (avatar changes, respawns). TheSpec.IsActive()check stops an already running passive from being activated twice, but if you want the passive to end when the avatar goes away, you need to handle that yourself.- If the ability is not instanced per actor,
OnAvatarSetis invoked on the CDO, which is whyTryActivatePassiveAbilityonly reads state and does not modify the ability. Non-instanced abilities are deprecated in 5.5+, so preferInstancedPerActor. - Nothing needs to change in your ASC. The old
InitAbilityActorInfooverride that loopedActivatableAbilitiesis no longer needed.
Hope this helps people. You can always find me on www.unrealslackers.org.