본문 바로가기
Unreal Engine/Unreal GAS

Unreal Engine UAbilityTask_WaitTargetData 살펴보기

by k99812 2025. 12. 8.

개요

 

최근 프로젝트를 네트워크(멀티플레이) 지원할 수 있도록 리팩토링하면서 기존 구현했던
어빌리티 테스크를 UAbilityTask_WaitTargetData 로 바꾸는 작업을 하였다

 

기존 구현한 어빌리티 테스크는 네트워크를 고려하지않고 싱글플레이에서만 동작하도록 코드를 구성했다

그래서 구글과 gemini 를 통해 언리얼 엔진에서 이미 네트워크 지원하도록 설계된
UAbilityTask_WaitTargetData 가 존재하는걸 알았다

 

그래서 리팩토링 하면서 해당 테스크에 대해 알게된 내용을 정리한다

 

UAbilityTask_WaitTargetData

 

어빌리티 테스크 생성

UAbilityTask_WaitTargetData* UAbilityTask_WaitTargetData::WaitTargetData(생략, 
TEnumAsByte<EGameplayTargetingConfirmation::Type> ConfirmationType, 
TSubclassOf<AGameplayAbilityTargetActor> InTargetClass)
{
	UAbilityTask_WaitTargetData* MyObj = 
    NewAbilityTask<UAbilityTask_WaitTargetData>(OwningAbility, TaskInstanceName);
	
    MyObj->TargetClass = InTargetClass;
	MyObj->TargetActor = nullptr;
	MyObj->ConfirmationType = ConfirmationType;
	return MyObj;
}

UAbilityTask_WaitTargetData* UAbilityTask_WaitTargetData::WaitTargetDataUsingActor(생략, 
TEnumAsByte<EGameplayTargetingConfirmation::Type> ConfirmationType,
AGameplayAbilityTargetActor* InTargetActor)
{
	UAbilityTask_WaitTargetData* MyObj = 
    NewAbilityTask<UAbilityTask_WaitTargetData>(OwningAbility, TaskInstanceName);
	
    MyObj->TargetClass = nullptr;
	MyObj->TargetActor = InTargetActor;
	MyObj->ConfirmationType = ConfirmationType;
	return MyObj;
}

 

어빌리티 테스크 생성은 WaitTargetData, WaitTargetDataUsingActor 함수로 한다

 

WaitTargetData

  • 인자로 타겟 액터 클래스를 받는다
  • 타겟 액터 생성은 BeginSpawningActor, FinishSpawningActor 함수로 생성한다
  • FinishSpawningActor 함수가 실행되면 타겟액터가 실행된다
  • FinishSpawningActor 함수 호출전 타겟액터 관련 초기화를 해야된다

WaitTargetDataUsingActor 

  • 인자로 타겟 액터 인스턴스를 받는다
  • ReadyForActivation 함수로 Activate 함수가 실행되면
    타겟액터가 실행된다
  • ReadyForActivation 호출전 타겟액터 관련 초기화를 해야된다

위와 같은 차이점이 있다

 

예시 코드

AGameplayAbilityTargetActor* SpawnedActor = nullptr;
UAbilityTask_WaitTargetData* WaitTargetData = 
UAbilityTask_WaitTargetData::WaitTargetData(this, FName("WaitTargetData"), 
EGameplayTargetingConfirmation::Instant, APPTA_Trace::StaticClass());

WaitTargetData->ValidData.AddDynamic(this, &UPPGA_AttackHitCheck::TraceResultCallback);
TargetActor = SpawnedActor;

if (WaitTargetData->BeginSpawningActor(this, APPTA_Trace::StaticClass(), SpawnedActor))
{
	APPTA_Trace* MyTrace = Cast<APPTA_Trace>(SpawnedActor);
	if (MyTrace)
	{
		MyTrace->SetShowDebug(true);
		MyTrace->ShouldProduceTargetDataOnServer = bIsMonster;
	}

	WaitTargetData->FinishSpawningActor(this, SpawnedActor);
}

WaitTargetData->ReadyForActivation();

 

WaitTargetData를 사용하여 타겟액터를 생성한 코드이다

  • WaitTargetData->ValidData 타겟액터 결과 콜백함수 바인드
  • BeginSpawningActor 로 타겟액터를 지연생성
  • FinishSpawningActor 실행시 타겟액터 생성마무리 후 실행
  • FinishSpawningActor 호출전 타겟액터 관련 초기화

WaitTargetData를 이용해 생성하면 반드시 FinishSpawningActor 함수 호출전

타겟액터 설정을 마무리해야 된다

 

클라이언트 실행
리슨서버 클라이언트 실행

 

게임 어빌리티를 LocalPredicted로 설정한 경우 클라이언트에서 테스크 결과가 먼저 실행된 다음

서버로 결과를 전송하여 콜백함수가 실행된다

 

결과적으로 타겟데이터 콜백함수(TraceResultCallback)는 클라이언트, 서버 두 곳에서 실행된다

 

코드 살펴보기

Activate

void UAbilityTask_WaitTargetData::Activate()
{
	if (Ability && (TargetClass == nullptr))
	{
		if (TargetActor)
		{
			AGameplayAbilityTargetActor* SpawnedActor = TargetActor;
			TargetClass = SpawnedActor->GetClass();

			RegisterTargetDataCallbacks();


			if (!IsValid(this))
			{
				return;
			}

			if (ShouldSpawnTargetActor())
			{
				InitializeTargetActor(SpawnedActor);
				FinalizeTargetActor(SpawnedActor);
			}
			else
			{
				TargetActor = nullptr;

				SpawnedActor->Destroy();
				SpawnedActor = nullptr;
			}
		}
		else
		{
			EndTask();
		}
	}
}

 

Activate 함수는 테스크가 활성화 될 때 호출되지만 WaitTargetData를 사용하여 

수동으로 타겟액터 스폰을 마친 경우에는 내부 로직을 건너뛴다

  • if (Ability && (TargetClass == nullptr))

TargetClassnullptr일때 실행되는걸 볼 수 있다

  • RegisterTargetDataCallbacks
    • 클라이언트에서 서버로 HitData를 전송했을때
      실행할 콜백함수들을 바인드하는 함수
    • 네트워크 콜백 함수
  • InitializeTargetActor
    • 타겟액터의 HitData를 받을 콜백함수를 바인드하는 함수
  • FinalizeTargetActor
    • 타겟액터 생성을 마무리 후 실행

 

BeginSpawningActor

bool UAbilityTask_WaitTargetData::BeginSpawningActor(생략)
{
	SpawnedActor = nullptr;

	if (Ability)
	{
		if (ShouldSpawnTargetActor())
		{
			UClass* Class = *InTargetClass;
			if (Class != nullptr)
			{
				if (UWorld* World = GEngine->GetWorldFromContextObject(OwningAbility, EGetWorldErrorMode::LogAndReturnNull))
				{
					SpawnedActor = World->SpawnActorDeferred<AGameplayAbilityTargetActor>(생략);
				}
			}

			if (SpawnedActor)
			{
				TargetActor = SpawnedActor;
				InitializeTargetActor(SpawnedActor);
			}
		}

		RegisterTargetDataCallbacks();
	}

	return (SpawnedActor != nullptr);
}

 

WaitTargetData 로 테스크 생성시 타겟액터 생성을 위해 실행해야 하는 함수

타겟액터를 생성해주고 델리게이트 연결을 수행한다

  • SpawnedActor = World->SpawnActorDeferred
    • 타겟액터 지연생성
  • InitializeTargetActor, RegisterTargetDataCallbacks 함수에서 콜백함수 바인드 진행

 

ShouldSpawnTargetActor

bool UAbilityTask_WaitTargetData::ShouldSpawnTargetActor() const
{
	//생략

	return (bReplicates || bIsLocallyControlled || bShouldProduceTargetDataOnServer);
}

 

타겟 액터를 생성하는 조건문을 살펴보면 아래와 같다

  • 리플리케이트 되야될때, 로컬컨트롤러일때, 서버에서 생성될때

따라서 서버에서만 생성(ShouldProduceTargetDataOnServer = true)로 설정해도

로컬컨트롤인 클라이언트에서도 타겟액터를 생성하고 관련 로직을 진행하게 된다

결국 클라이언트는 타겟액터를 생성하지만 해당 타겟액터는 버려지게 된다

 

또한 서버에서 클라이언트 타겟액터 결과를 기다리는것이 아닌 직접 타겟액터를

생성하여 판정을 진행하게 된다

 

FinishSpawningActor

void UAbilityTask_WaitTargetData::FinishSpawningActor(UGameplayAbility* OwningAbility, AGameplayAbilityTargetActor* SpawnedActor)
{
	UAbilitySystemComponent* ASC = AbilitySystemComponent.Get();
	if (ASC && IsValid(SpawnedActor))
	{
		check(TargetActor == SpawnedActor);

		const FTransform SpawnTransform = ASC->GetOwner()->GetTransform();

		SpawnedActor->FinishSpawning(SpawnTransform);

		FinalizeTargetActor(SpawnedActor);
	}
}

 

WaitTargetData로 테스크를 생성시 타겟액터 생성 마무리로 실행해야 하는 함수

해당함수 실행시 타겟액터 생성을 마무리하고 타겟액터 로직을 실행하는

FinalizeTargetActor함수를 실행한다

  • SpawnedActor->FinishSpawning(SpawnTransform);
    • 타겟액터 생성 마무리
  • FinalizeTargetActor(SpawnedActor);
    • 타겟액터 로직 실행

 

InitializeTargetActor

void UAbilityTask_WaitTargetData::InitializeTargetActor(AGameplayAbilityTargetActor* SpawnedActor) const
{
	check(SpawnedActor);
	check(Ability);

	SpawnedActor->PrimaryPC = Ability->GetCurrentActorInfo()->PlayerController.Get();

	SpawnedActor->TargetDataReadyDelegate.AddUObject(const_cast<UAbilityTask_WaitTargetData*>(this), 
    &UAbilityTask_WaitTargetData::OnTargetDataReadyCallback);
	
	SpawnedActor->CanceledDelegate.AddUObject(const_cast<UAbilityTask_WaitTargetData*>(this), 
    &UAbilityTask_WaitTargetData::OnTargetDataCancelledCallback);
}

 

타겟액터 결과를 받을 콜백함수를 바인딩하는 함수

추가로 GA에 어빌리티 테스크 결과를 알려주는 델리게이트는
ValidData로 클라이언트, 서버 두 곳에서 한번씩 Broadcast를 실행한다

  • SpawnedActor->TargetDataReadyDelegate
    • 타겟액터 실행결과를 받을 콜백함수 바인드

 

FinalizeTargetActor

void UAbilityTask_WaitTargetData::FinalizeTargetActor(AGameplayAbilityTargetActor* SpawnedActor) const
{
	//생략

	SpawnedActor->StartTargeting(Ability);

	if (SpawnedActor->ShouldProduceTargetData())
	{
		if (ConfirmationType == EGameplayTargetingConfirmation::Instant)
		{
			SpawnedActor->ConfirmTargeting();
		}
		else if (ConfirmationType == EGameplayTargetingConfirmation::UserConfirmed)
		{
			SpawnedActor->BindToConfirmCancelInputs();
		}
	}
}

 

타겟액터 로직을 실행하는 함수

테스크실행시 지정하는 ConfirmationType에 따라 로직이 변경된다

  • EGameplayTargetingConfirmation::Instant
    • SpawnedActor->ConfirmTargeting();
    • 별도의 추가로직없이 바로 타겟액터로직이 실행된다
  • GameplayTargetingConfirmation::UserConfirmed
    • SpawnedActor->BindToConfirmCancelInputs();
    • ASCLocalInputConfirm, LocalInputCancle 이벤트에 콜백함수를 연결한다
      LocalInputConfirm 실행시 ConfirmTargeting이 실행된다
    • 해당 타입으로 설정할 경우 ASC->LocalInputConfirm 로 이벤트를 호출해야된다

 

RegisterTargetDataCallbacks

void UAbilityTask_WaitTargetData::RegisterTargetDataCallbacks()
{
	//생략
    
    const bool bShouldProduceTargetDataOnServer = CDO->ShouldProduceTargetDataOnServer;
    
	if (!bIsLocallyControlled)
	{
		if (!bShouldProduceTargetDataOnServer)
		{
			FGameplayAbilitySpecHandle	SpecHandle = GetAbilitySpecHandle();
			FPredictionKey ActivationPredictionKey = GetActivationPredictionKey();

			ASC->AbilityTargetDataSetDelegate(SpecHandle, ActivationPredictionKey).AddUObject(
		this, &UAbilityTask_WaitTargetData::OnTargetDataReplicatedCallback);
			
			ASC->AbilityTargetDataCancelledDelegate(SpecHandle, ActivationPredictionKey).AddUObject(
		this, &UAbilityTask_WaitTargetData::OnTargetDataReplicatedCancelledCallback);

			ASC->CallReplicatedTargetDataDelegatesIfSet(SpecHandle, ActivationPredictionKey );

			SetWaitingOnRemotePlayerData();
		}
	}
}

 

해당 함수는 서버에서 조종되는 remote 클라이언트일때 로직이 실행된다(로컬 클라이언트가 아닐때)

따라서 서버는 로컬에서 생성된 타겟 데이터를 전달 받는다.

 

즉 로컬예측으로 GA가 실행된 경우 클라이언트에서 타겟 데이터를 전달 받으면 실행되는
델리게이트에 콜백함수를 바인드하는 함수이다

 

함수에서 진행되는 로직은 (서버, 리모트 클라이언트)가 만족하면 클라이언트에서 타겟데이터가

수신될때 실행되는 델리게이트에 콜백함수를 바인드한다

  • ShouldProduceTargetDataOnServer 
    • 타겟액터에서 설정하는 변수
    • true이면 타겟액터를 서버에서만 생성
      false이면 클라이언트에서 생성하여 서버로 결과를 전송
  • ASC->AbilityTargetDataSetDelegate
    • 클라이언트에서 타겟데이터가 서버로 전송되면 실행되는 델리게이트

 

OnTargetDataReplicatedCallback

void UAbilityTask_WaitTargetData::OnTargetDataReplicatedCallback(const FGameplayAbilityTargetDataHandle& Data, FGameplayTag ActivationTag)
{
	FGameplayAbilityTargetDataHandle MutableData = Data;

	if (UAbilitySystemComponent* ASC = AbilitySystemComponent.Get())
	{
		ASC->ConsumeClientReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey());
	}
    
	if (TargetActor && !TargetActor->OnReplicatedTargetDataReceived(MutableData))
	{
		if (ShouldBroadcastAbilityTaskDelegates())
		{
			Cancelled.Broadcast(MutableData);
		}
	}
	else
	{
		if (ShouldBroadcastAbilityTaskDelegates())
		{
			ValidData.Broadcast(MutableData);
		}
	}

	//생략
}

 

서버에서 클라이언트가 보낸 타겟데이터가 수신되면 실행되는 델리게이트의 콜백함수

즉 서버에서 실행되는 함수이다

  • ConsumeClientReplicatedTargetData
    • PredictionKey를 활용해 클라이언트에서 보낸 데이터를 
      TSharedPtr<FAbilityReplicatedDataCache> CachedData 로 저장 후
      CachedData초기화 한다
    • 필요한 데이터는 델리게이트로 전달받는다

타겟 데이터가 존재하면 

  • ValidData.Broadcast(MutableData);

Broadcast를 실행하여 서버에서 ValidData의 콜백함수를 실행한다

 

OnTargetDataReadyCallback

void UAbilityTask_WaitTargetData::OnTargetDataReadyCallback(const FGameplayAbilityTargetDataHandle& Data)
{
	//생략
    
	if (IsPredictingClient())
	{
		if (!TargetActor->ShouldProduceTargetDataOnServer)
		{
			FGameplayTag ApplicationTag;
			ASC->CallServerSetReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey(), Data, ApplicationTag, ASC->ScopedPredictionKey);
		}
		else if (ConfirmationType == EGameplayTargetingConfirmation::UserConfirmed)
		{
			ASC->ServerSetReplicatedEvent(EAbilityGenericReplicatedEvent::GenericConfirm, GetAbilitySpecHandle(), GetActivationPredictionKey(), ASC->ScopedPredictionKey);
		}
	}

	if (ShouldBroadcastAbilityTaskDelegates())
	{
		ValidData.Broadcast(Data);
	}

	if (ConfirmationType != EGameplayTargetingConfirmation::CustomMulti)
	{
		EndTask();
	}
}

 

해당 함수는 타겟액터가 결과를 Broadcast하면 호출되는 함수이다

 

클라이언트에서 타겟액터를 생성하여 데이터를 받으면 조건에따라

서버에 타겟 데이터를 전송하거나 이벤트를 발생시킨다

  • if (!TargetActor->ShouldProduceTargetDataOnServer)
    • true일 경우 서버에서 타겟액터를 생성한다는 뜻이다
    • false일 경우 클라이언트에서 생성된 타겟데이터를 서버에 전송한다
      CallServerSetReplicatedTargetData 함수를 사용한다
  • else if (ConfirmationType == EGameplayTargetingConfirmation::UserConfirmed)
    • ShouldProduceTargetDataOnServertrue 이고 UserConfirmed 일때 실행된다
    • ServerSetReplicatedEvent 호출하여 GenericConfirm 이벤트를 서버에서 발생시킨다

ShouldProduceTargetDataOnServer = true, UserConfirmed 일때 시나리오

  1. 타겟액터 생성 및 SpawnedActor->BindToConfirmCancelInputs();
    함수로 ASC->LocalInputConfirm 이벤트 대기
  2. 클라이언트 ASC->LocalInputConfirm 호출
  3. ConfirmTargeting, ConfirmTargetingAndContinue 순서로 호출
  4. ConfirmTargetingAndContinue 함수에서 데이터 생성하여 
    TargetDataReadyDelegate Broadcast 실행
  5. OnTargetDataReadyCallback 함수가 실행되며
    ServerSetReplicatedEvent 함수로 서버에 이벤트 발생
    추가로 클라이언트에서 생성된 데이터는 버려짐
  6. 서버에서 ConfirmTargeting 이 실행되며 서버에서 판정 실행

마지막으로 ValidData 델리게이트를 실행하여 클라이언트에서도

테스크 결과 콜백함수가 실행되게 된다