[GAS] Does anyone know how to wait for the ability to finish?

Altogether with teamtwentythree’s answer I’ve found a neat little way of implementing it using GAS events on the tags. (so no ticks, + it looks sexy af)
Here’s what the little BP brick looks like.
Mine takes an ability class, but you can easilly adapt the code from there for anything else.
image

LIMITATIONS:

  • Requires setting up pretty much unique ‘AbilityTags’ on the abilities since that’s what we’re using. One should be enough. ie.: Combat.Spell.Fireball.
  • IMPORTANT: Tags added in ‘AbilityTags’ need to also be in ‘ActivationOwnedTags’.
    Why: I noticed this after saddly, ‘AbilityTags’ arent subbed to the event that listent for tag changes.
    Debate: Tho, you can easilly override some functionality in UGameplayAbility to automatically add these tags and not have to write them at both places. I wouldnt consider just using ‘ActivationOwnedTags’ instead of ‘AbilityTags’, these could have other tags like ‘FreezeWhileCasting’ that could be general and thus be used by other abilities and be a big fu.

Here’s the code.
.h

#include "CoreMinimal.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "AbilitySystemComponent.h"
#include "AsyncTaskGameplayAbilityEnded.generated.h"

DECLARE_DYNAMIC_MULTICAST_DELEGATE(FAsyncTaskGameplayAbilityEndedEv);

UCLASS(BlueprintType, meta = (ExposedAsyncProxy = AsyncTask))
class TPSMPPG_API UAsyncTaskGameplayAbilityEnded : public UBlueprintAsyncActionBase
{
	GENERATED_BODY()

	UPROPERTY(BlueprintAssignable)
	FAsyncTaskGameplayAbilityEndedEv OnEnded;

	UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
		static UAsyncTaskGameplayAbilityEnded* ListenForGameplayAbilityEnd(UAbilitySystemComponent* abilitySystemComponent, TSubclassOf<UGameplayAbility> abilityClass);

	// You must call this function manually when you want the AsyncTask to end.
	// For UMG Widgets, you would call it in the Widget's Destruct event.
	UFUNCTION(BlueprintCallable)
		void EndTask();

protected:

	UAbilitySystemComponent* ASC;
	FGameplayTagContainer TagsStillApplied;
	TMap<FGameplayTag, FDelegateHandle> HandlesMap;

	UFUNCTION()
	virtual void OnCallback(const FGameplayTag CallbackTag, int32 NewCount);
};

.cpp

UAsyncTaskGameplayAbilityEnded* UAsyncTaskGameplayAbilityEnded::ListenForGameplayAbilityEnd(UAbilitySystemComponent* abilitySystemComponent,  TSubclassOf<UGameplayAbility> abilityClass)
{
	if (!IsValid(abilitySystemComponent))
	{
		Print::Say("Couldnt create Task, missing ASC");
		return nullptr;
	}
	
	const UGameplayAbility* const abilityDef = abilityClass.GetDefaultObject();
	if (abilityDef == nullptr)
	{
		Print::Say("Couldnt create Task, Ability " + abilityClass->GetName() + " CDO is invalid");
		return nullptr;
	}
	
	if (abilityDef->AbilityTags.IsValid() == false)
	{
		Print::Say("Couldnt create Task, Ability " + abilityClass->GetName() + " requires at least one AbilityTags");
		return nullptr;
	}

       // TODO: check that 'AbilityTags' are contained within  'ActivationOwnedTags'. This requires to make another class that has a public getter to this protected field.
	
	UAsyncTaskGameplayAbilityEnded* r = NewObject<UAsyncTaskGameplayAbilityEnded>();
	r->ASC = abilitySystemComponent;
	r->TagsStillApplied = abilityDef->AbilityTags;

	for (const auto tag : r->TagsStillApplied)
	{
		auto h = abilitySystemComponent->RegisterGameplayTagEvent(tag, EGameplayTagEventType::NewOrRemoved).AddUObject(r, &UAsyncTaskGameplayAbilityEnded::OnCallback);
		r->HandlesMap.Add(tag, h);
	}

	return r;
}

void UAsyncTaskGameplayAbilityEnded::EndTask()
{
	if (IsValid(ASC))
	{
		for (const auto tag : TagsStillApplied)
		{
			ASC->UnregisterGameplayTagEvent(*HandlesMap.Find(tag), tag, EGameplayTagEventType::NewOrRemoved);
		}
	}

	SetReadyToDestroy();
	MarkAsGarbage();
}

void UAsyncTaskGameplayAbilityEnded::OnCallback(const FGameplayTag CallbackTag, int32 NewCount)
{
	if (NewCount == 0) {
		if (TagsStillApplied.HasTagExact(CallbackTag))
		{
			TagsStillApplied.RemoveTag(CallbackTag);
			ASC->UnregisterGameplayTagEvent(*HandlesMap.Find(CallbackTag), CallbackTag, EGameplayTagEventType::NewOrRemoved);

			if (TagsStillApplied.IsEmpty())
			{
				OnEnded.Broadcast();
			}
		}
	}
}
1 Like