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

I need to use GAS with a behavior tree. I want to activate an ability in a behavior tree task and don’t finish the task until the ability ends.

1 Like

What seems to be working for me (Just working on this now). Add a tag to your ability that will be put on ability owner, in my case via Activation Owned Tags on the GameplayAbility. Then check for that tag in the Receive Tick AI in your BT task. Only call FinishExecute when that tag is no longer present.

1 Like

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

Well. Wont you guess: theres much simpler without having to do with the GameplayTags!

Make a sublass “UGameplayAbility_Crow”
Add an event “EventOn_AbilityEnded” that executes when virtual override function ‘EndAbility’ is ran.

Above comment method ‘ListenForGameplayAbilityEnd’ changes to:

UAsyncTaskGameplayAbilityEnded* UAsyncTaskGameplayAbilityEnded::ListenForGameplayAbilityEnd(UAbilitySystemComponent* abilitySystemComponent,  TSubclassOf<UGameplayAbility> abilityClass)
{
	if (!IsValid(abilitySystemComponent))
	{
		Print::Say("Couldn't create Task, missing ASC");
		return nullptr;
	}

	auto abilitySpec = abilitySystemComponent->FindAbilitySpecFromClass(abilityClass);
	if (abilitySpec == nullptr)
	{
		Print::Say("Couldn't create Task, Ability " + abilityClass->GetName() + " not found in ASC");
		return nullptr;
	}
	
	auto abilityInstance = abilitySpec->GetPrimaryInstance();
	if (abilityInstance == nullptr)
	{
		Print::Say("Couldn't create Task, Ability " + abilityClass->GetName() + " couldn't find an instance. Instance Policy shouldn't be 'instance per execution'");
		return nullptr;
	}
	
	UGameplayAbility_Crow* abilityCrow = Cast<UGameplayAbility_Crow>(abilityInstance);
	if (abilityCrow == nullptr)
	{
		Print::Say("Couldn't create Task, Ability " + abilityClass->GetName() + " needs to inherit from UGameplayAbility_Crow");
		return nullptr;
	}

	UAsyncTaskGameplayAbilityEnded* r = NewObject<UAsyncTaskGameplayAbilityEnded>();
	abilityCrow->EventOn_AbilityEnded.AddDynamic(r, &UAsyncTaskGameplayAbilityEnded::OnCallback);
	r->AbilityListeningTo = abilityCrow;

	return r;
}

Remember to do:

		AbilityListeningTo->EventOn_AbilityEnded.RemoveDynamic(this, &UAsyncTaskGameplayAbilityEnded::OnCallback);

In ‘OnCallback’ and ‘EndTask’

1 Like