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:
- activated/executed GA may block/cancel other GAs (via adding tags)
- deactivated GA may unblock other GAs (via removing tags)
- 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
UAbilitySystemComponent::ApplyAbilityBlockAndCancelTags
calls ...
UAbilitySystemComponent::BlockAbilitiesWithTags()
respectively UAbilitySystemComponent::UnBlockAbilitiesWithTags()
, which (finally) call for each of the tags in its list UAbilitySystemComponent::BlockedAbilityTags
...
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.