The Games DevProgramming and Game Development. Tips, Tricks and Tutorials.

Activating abilities when Granted to ASC (Passive Abilities)

· Updated Gameplay Ability System

Updated for UE 5.8: the engine now calls UGameplayAbility::OnAvatarSet both when an ability is granted (if an avatar already exists) and whenever the avatar changes, so one override replaces the old OnGiveAbility override plus the InitAbilityActorInfo loop. The Spec.ActivationInfo predicting 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:

  • OnAvatarSet can fire more than once (avatar changes, respawns). The Spec.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, OnAvatarSet is invoked on the CDO, which is why TryActivatePassiveAbility only reads state and does not modify the ability. Non-instanced abilities are deprecated in 5.5+, so prefer InstancedPerActor.
  • Nothing needs to change in your ASC. The old InitAbilityActorInfo override that looped ActivatableAbilities is no longer needed.

Hope this helps people. You can always find me on www.unrealslackers.org.