3

In Unreal GameplayAbilitySystem, e.g. Cancel Abilities With Tag is documented with

Cancels any already-executing Ability [..]

and the source code says (in GameplayAbility.h)

Abilities with these tags are cancelled when this ability is executed

To know at design time, which abilities will be affected when setting those tags, it is required to be sure about:

When a GameplayAbility is considered as executed and when as executing?


According to the documentation linked above, the GA is executed with CallActivateAbility(). However, activation can be interrupted, if cooldown/costs conditions are not fulfilled (e.g. "not enough mana") in CommitAbility().

CommitAbility() checks if the ability can be activated via CommitCheck() (cooldown/costs) and only if this succeeds, CommitExecute() is called (which applies cooldown and costs).

Before CommitAbility(), PreActivate() is called, which

1) sets (for instanced GameplayAbilities):

// ...
bIsActive = true;
bIsBlockingOtherAbilities = true;
bIsCancelable = true;
// ...

2) and blocks/cancels other GameplayAbilities with given tags using UAbilitySystemComponent::ApplyAbilityBlockAndCancelTags


Detailed questions which might help to answer the question above:

  1. Is executing a GameplayAbility the same like activating a GameplayAbility or have these terms different meanings? (both terms are used in documentation and function names/variables)
  2. Is a GameplayAbility always considered as executing as soon as PreActivate() is called? (since that function blocks/cancels other abilities via ApplyAbilityBlockAndCancelTags and therefore - according to the second quote above - is considered as executing)

    This would imply, that a GameplayAbility is considered as executing

    • independently from the outcome of CommitAbility()?
    • as long as UGameplayAbility::ActivateAbility() is executing?
  3. How is a GameplayAbility detected as executing?
    • Is it enough to check for UGameplayAbility::bIsActive == true (for instanced GameplayAbilities)?
    • Or are executing abilities registered somewhere in UGameplayAbilitySystem?
    • ...
Roi Danton
  • 7,933
  • 6
  • 68
  • 80

1 Answers1

4

Hypothesis

The terms activate/execute a GameplayAbility (GA) and active/executing GameplayAbility are used when the abilities influence each other with GameplayTags. So when designing, these terms are important for understanding the GameplayAbility interaction via tags. As a consequence, an:

  1. activated/executed GA may block/cancel other GAs (via adding tags)
  2. deactivated GA may unblock other GAs (via removing tags)
  3. active/executing GA must be able to receive a block/cancel via tag changes, when another GA is executed

Conclusion

To make best usage of tags for controlling the interaction between GameplayAbilities, keep the following in mind: A GameplayAbility is

  • activated/executed when CallActivateAbility() is called (in blueprint, the Event ActivateAbility and Event ActivateAbilityFromEvent are triggered by that function via ActivateAbility()).
  • deactivated when EndAbility() is called (which is also called by CancelAbility()).
  • active/executing between the calls of UAbilitySystemComponent::TryActivateAbility() until EndAbility().

    In case that EndAbility() is not called, the GA is executing for the lifetime of ActivateAbility() (again, in blueprints this is almost similar to the lifetime of the implemented Event ActivateAbility and Event ActivateAbilityFromEvent).

Explanation

When a GA is considered activated/executed and deactivated (1, 2)

UAbilitySystemComponent::ApplyAbilityBlockAndCancelTags() triggers GAs to be cancelled or blocked/unblocked. Therefore calls to it define the moment of activate/execute and deactivate.

  • Block/cancel are called by PreActivate() which is called by CallActivateAbility()
  • Unblock is called by EndAbility() (which is also called by CancelAbility())

When a GA is considered active/executing (3)

When it can be blocked

A GA can be blocked only during its activation. CanActivateAbility() checks (via DoesAbilitySatisfyTagRequirements()) the blocked tag list of the GameplayAbilitySystemComponent (ASC) (UAbilitySystemComponent::BlockedAbilityTags). If one of the tags is present there, the ability activation is aborted.

and when it can be cancelled (unregarded the fact if the GA allows it)

A GA can be cancelled by other GAs via tags as long as it is registered in the GameplayAbilitySystemComponent (ASC) in UAbilitySystemComponent::ActivatableAbilities and as long as that stored GA spec returns FGameplayAbilitySpec::IsActive() = true.

  • It gets added to that container via UAbilitySystemComponent::GiveAbility() and removed via UAbilitySystemComponent::ClearAbility()
  • FGameplayAbilitySpec::IsActive() will return true, as long as FGameplayAbilitySpec::ActiveCount > 0. This variable will be incremented upon GA activation (in UAbilitySystemComponent::InternalTryActivateAbility()) and decremented upon GA deactivation (in UAbilitySystemComponent::NotifyAbilityEnded).

UASC::InternalTryActivateAbility() is called in UASC::TryActivateAbility() and UASC::NotifyAbilityEnded() is called in EndAbility(). These two conditions are the reason that I considered a GA only as active/executing between those calls instead of between UASC::GiveAbility() and UASC::ClearAbility().

How those tags are triggered

When a GA is activated/deactivated, it triggers functions in the GameplayAbilitySystemComponent (ASC):

For un-/blocking
  1. UAbilitySystemComponent::ApplyAbilityBlockAndCancelTags calls ...
  2. UAbilitySystemComponent::BlockAbilitiesWithTags() respectively UAbilitySystemComponent::UnBlockAbilitiesWithTags(), which (finally) call for each of the tags in its list UAbilitySystemComponent::BlockedAbilityTags ...
  3. FGameplayTagCountContainer::UpdateTagMap_Internal(), which adds respectively removes the tag
For cancelling
  • UAbilitySystemComponent::CancelAbilities() which compares the tags provided by the cancelling GA with the tags of all activateable abilities stored in the Ability System Component (in UAbilitySystemComponent::ActivatableAbilities()), which are currently active.
  • If there is a match, all of those GA instances are cancelled.

What about CommitExecute()

That function is independent from interacting with other GAs via tags, so the Execute part of the name can be misleading.

CommitExecute() is called only if the costs of the GA can be afforded and if the GA is not on cooldown (all of that happens in CommitAbility()). But blocking/cancelling other GAs already happened before and unblocking GAs also happens independently from that function.

So what about CommitAbility()

For an executing GA, it doesn't matter, if this is being called.

If CommitAbility() fails, the GA might be deactivated immediately after being activated, depending on the implementation of the GA. So it can have an influence on the execution duration, which however is unimportant for the design process of the GAs interaction via tags.

What about SetShouldBlockOtherAbilities()

This public function can be called to un-/block other GAs via tags from an arbitrary place in C++/blueprints.

E.g. it could be called several times during the lifetime of ActivateAbility() to un-/block other GAs. Furthermore, it is not able to cancel other GAs. It is not clear what are the best practices for using or avoiding this function. It seems to lower the transparency of GA interaction via tags.


(if no parent namespace is present, all functions are members of UGameplayAbility)

Other ways to block/unblock GAs (e.g. via inputID) or cancelling GAs (e.g. via UAbilitySystemComponent::CancelAllAbilities()) are not considered. This is about GA interaction via tags. Also triggering GAs via tags (or GameplayEvents) is another topic.

It would be nice if a designer of the Unreal GAS could clarify things, correct incomplete conclusions or leave some words about best practices.

Roi Danton
  • 7,933
  • 6
  • 68
  • 80