0. 개요

비동기 태스크(Async Task) 노드를 활용하여 스펠 글로브에 쿨다운을 표시한다.

 

1. 쿨다운 표시하기 - 비동기 태스크 노드 생성

성능을 위해 Tick이 아닌 비동기 태스크를 활용한다.

비동기 태스크 노드는 모든 이벤트 그래프에서 사용할 수 있다.

 

(1) WaitCooldownChange C++ 클래스 생성

#pragma once

#include "CoreMinimal.h"
#include "Kismet/BlueprintAsyncActionBase.h"
#include "GameplayTagContainer.h"
#include "ActiveGameplayEffectHandle.h"
#include "WaitCooldownChange.generated.h"

class UAbilitySystemComponent;
struct FGameplayEffectSpec;

// 델리게이트 선언
DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FCooldownChangeSignature, float, TimeRemaining);

UCLASS()
class AURA_API UWaitCooldownChange : public UBlueprintAsyncActionBase
{
	GENERATED_BODY()

public:
	UPROPERTY(BlueprintAssignable)
	FCooldownChangeSignature CooldownStart;

	UPROPERTY(BlueprintAssignable)
	FCooldownChangeSignature CooldownEnd;

	// Cooldown 비동기 작업 시작 함수
	UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true"))
	static UWaitCooldownChange* WaitForCooldownChange(UAbilitySystemComponent* AbilitySystemComponent, const FGameplayTag& InCooldownTag);

	// 작업 종료
	UFUNCTION(BlueprintCallable)
	void EndTask();

protected:
	UPROPERTY()
	TObjectPtr<UAbilitySystemComponent> ASC;

	FGameplayTag CooldownTag;

	void CooldownTagChanged(const FGameplayTag InCooldownTag, int32 NewCount);
	void OnActiveEffectAdded(UAbilitySystemComponent* TargetASC, const FGameplayEffectSpec& SpecApplied, FActiveGameplayEffectHandle ActiveEffectHandle);
};

쿨다운 시간 변화를 추적하는 델리게이트를 생성한다.

CooldownStart는 남은 쿨다운 시간, CooldownEnd는 쿨다운 끝이다.

 

쿨다운 변화를 계산하는 내부 함수를 만든다.

또한 태스크 종료시 델리게이트의 함수 바인딩을 제거하고 스스로를 제거하도록 한다.

 

 

(2) 쿨다운의 변화를 기다리는 WaitForCooldownChange 함수

UWaitCooldownChange* UWaitCooldownChange::WaitForCooldownChange(UAbilitySystemComponent* AbilitySystemComponent, const FGameplayTag& InCooldownTag)
{
	UWaitCooldownChange* WaitCooldownChange = NewObject<UWaitCooldownChange>();
	WaitCooldownChange->ASC = AbilitySystemComponent;
	WaitCooldownChange->CooldownTag = InCooldownTag;

	// 유효하지 않으면 null 반환
	if (IsValid(AbilitySystemComponent) == false || InCooldownTag.IsValid() == false)
	{
		WaitCooldownChange->EndTask();
		return nullptr;
	}

	// 쿨다운이 언제 끝나는가(태그가 언제 사라지는가)
	AbilitySystemComponent->RegisterGameplayTagEvent(
		InCooldownTag,	EGameplayTagEventType::NewOrRemoved).AddUObject(
			WaitCooldownChange,
			&UWaitCooldownChange::CooldownTagChanged);

	// 쿨다운이 언제 적용되었는가
	AbilitySystemComponent->OnActiveGameplayEffectAddedDelegateToSelf.AddUObject(WaitCooldownChange, &UWaitCooldownChange::OnActiveEffectAdded);

	return WaitCooldownChange;
}

- 정적 함수이므로 스스로의 오브젝트를 생성한다.

- ASC와 태그를 설정한다. 둘 중 하나라도 유효하지 않으면 본인을 파괴하고 가비지 컬렉터에 등록한다.

- 쿨다운 태그를 가져와 해당 태그가 생기거나 제거되는 순간에 CooldownTagChange 콜백 함수를 실행한다.

- 쿨다운 게임플레이 이펙트가 컴포넌트에 적용되는 순간에 OnActiveEffectAdded 콜백 함수를 실행한다. 

 

 

(3) 쿨다운 종료를 기다리는 CooldownTagChanged 함수

void UWaitCooldownChange::CooldownTagChanged(const FGameplayTag InCooldownTag, int32 NewCount)
{
	// 쿨다운 종료
	if (NewCount == 0)
	{
		CooldownEnd.Broadcast(0.f);
	}
}

쿨다운 태그의 갯수를 세고, 쿨다운이 끝났음(TimeRemaining = 0.f)을 전달한다.

 

 

(4) 쿨다운 시작을 기다리는 OnActiveEffectAdded 함수

void UWaitCooldownChange::OnActiveEffectAdded(UAbilitySystemComponent* TargetASC, const FGameplayEffectSpec& SpecApplied, FActiveGameplayEffectHandle ActiveEffectHandle)
{
	FGameplayTagContainer AssetTags;
	SpecApplied.GetAllAssetTags(AssetTags);

	FGameplayTagContainer GrantedTags;
	SpecApplied.GetAllGrantedTags(GrantedTags);

	// 태그를 찾아 쿨다운 태그가 부여되어 있다면
	if (AssetTags.HasTagExact(CooldownTag) || GrantedTags.HasTagExact(CooldownTag))
	{
		// 해당하는 쿨다운 태그를 가지는 이펙트 찾기
		FGameplayEffectQuery GameplayEffectQuery = FGameplayEffectQuery::MakeQuery_MatchAnyOwningTags(CooldownTag.GetSingleTagContainer());
		
		// 다수의 쿨다운을 가질 수 있음
		TArray<float> TimesRemaining = ASC->GetActiveEffectsTimeRemaining(GameplayEffectQuery);
		if (TimesRemaining.Num() > 0)
		{
			// 쿨다운 중 최댓값(가장 오래걸리는 시간) 찾기
			float TimeRemaining = TimesRemaining[0];
			for (int32 i = 0; i < TimesRemaining.Num(); i++)
			{
				if (TimeRemaining < TimesRemaining[i])
				{
					TimeRemaining = TimesRemaining[i];
				}
			}

			// 쿨다운 시간 브로드캐스트
			CooldownStart.Broadcast(TimeRemaining);
		}
	}
}

한 어빌리티에 쿨다운 태그가 여러 종류가 걸려있을 수 있다.

그 중에 가장 오래 지속되는 쿨다운을 가져와 넘기는 로직이다.

 

ASC에 접근해서 활성화된 이펙트의 시간 가져오기


- 단 하나의 태그를 넘기는 것은 새로운 TagContainer를 생성하는 것보다, GetSingleTagContainer 함수를 사용하는 것이 좋다.


 

 

(5) 태스크 종료

void UWaitCooldownChange::EndTask()
{
	if (IsValid(ASC) == false)
		return;
	
	// 콜백 함수 바인드 제거
	ASC->RegisterGameplayTagEvent(CooldownTag, EGameplayTagEventType::NewOrRemoved).RemoveAll(this);
	
	SetReadyToDestroy();

	// 가비지로 표기
	MarkAsGarbage();
}

태스크가 종료되면 태스크를 폐기한다.

(정적으로 생성된 인스턴스 제거)

 

 

2. WBP_SpellGlobe에서 쿨다운 추적 비동기 노드 실행

이제 쿨다운 변화를 나타내기 위해서 해당 함수 노드를 사용한다.

스펠 글로브마다 설정된 어빌리티에 대해서 쿨다운 태그를 가져와야 하지만 먼저 하드코딩을 통해 기능을 테스트한다.

 

(1) 테스트

쿨다운으로 설정된 0.5초 간격으로 Start 이후 End가 발생하는 것을 볼 수 있다.

 

 

(2) 비동기 태스크 핀 추가

이제 AbilityInfo로부터 각 스펠 글로브에 쿨다운 태그를 가져와 저장할 것이다.

그 전에 비동기 태스크가 유일한지 확인하기 위해 비동기 태스크 핀을 추가한다.

UCLASS(BlueprintType, meta = (ExposedAsyncProxy = "ASyncTask"))
class AURA_API UWaitCooldownChange : public UBlueprintAsyncActionBase
{

비동기 태스크 오브젝트 레퍼런스를 변수로 승격한다.

시퀀스에 핀을 추가해 Wait for Cooldown Change 비동기 노드를 실행하기 전에, 제거되지 않은 Wait for Cooldown Change Task가 있다면 End Task로 종료하고 제거한다.

 

 

(3) AbilityInfo에 쿨다운 태그 추가

	// 쿨다운 태그
	UPROPERTY(EditDefaultsOnly, BlueprintReadOnly)
	FGameplayTag CooldownTag = FGameplayTag();

 

 

(4) WBP_SpellGlobe의 Receive Ability Info 함수 수정

새로 추가한 쿨다운 태그를 입력 태그와 정확히 일치하는 어빌리티 정보에만 쿨다운 태그를 설정하도록 노드를 연결한다.

 

 

(5) 시퀀스 수정

이제 쿨다운 태그를 AbilityInfo로 부터 Set 하므로 AbilityInfo를 수신한 이후에 쿨다운을 추적하도록 시퀀스를 변경한다.

 

 

(6) DA_AbilityInfo 수정

파이어볼트 쿨다운 태그에 적절한 태그를 연결한다.