개요
최근 프로젝트를 네트워크(멀티플레이) 지원할 수 있도록 리팩토링하면서 기존 구현했던
어빌리티 테스크를 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))
TargetClass가 nullptr일때 실행되는걸 볼 수 있다
- RegisterTargetDataCallbacks
- 클라이언트에서 서버로 HitData를 전송했을때
실행할 콜백함수들을 바인드하는 함수 - 네트워크 콜백 함수
- 클라이언트에서 서버로 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();
- ASC의 LocalInputConfirm, 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를 초기화 한다 - 필요한 데이터는 델리게이트로 전달받는다
- PredictionKey를 활용해 클라이언트에서 보낸 데이터를
타겟 데이터가 존재하면
- 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)
- ShouldProduceTargetDataOnServer 가 true 이고 UserConfirmed 일때 실행된다
- ServerSetReplicatedEvent 호출하여 GenericConfirm 이벤트를 서버에서 발생시킨다
ShouldProduceTargetDataOnServer = true, UserConfirmed 일때 시나리오
- 타겟액터 생성 및 SpawnedActor->BindToConfirmCancelInputs();
함수로 ASC->LocalInputConfirm 이벤트 대기 - 클라이언트 ASC->LocalInputConfirm 호출
- ConfirmTargeting, ConfirmTargetingAndContinue 순서로 호출
- ConfirmTargetingAndContinue 함수에서 데이터 생성하여
TargetDataReadyDelegate Broadcast 실행 - OnTargetDataReadyCallback 함수가 실행되며
ServerSetReplicatedEvent 함수로 서버에 이벤트 발생
추가로 클라이언트에서 생성된 데이터는 버려짐 - 서버에서 ConfirmTargeting 이 실행되며 서버에서 판정 실행
마지막으로 ValidData 델리게이트를 실행하여 클라이언트에서도
테스크 결과 콜백함수가 실행되게 된다
'Unreal Engine > Unreal GAS' 카테고리의 다른 글
| Unreal Engine 캐릭터의 광역 스킬 구현(10) 최종 대미지의 수동 계산 (0) | 2024.06.12 |
|---|---|
| Unreal Engine 캐릭터의 광역 스킬 구현(9) 스킬 마나 및 쿨타임 구현 (0) | 2024.06.10 |
| Unreal Engine 캐릭터의 광역 스킬 구현(8) 스킬어트리뷰트 활용 (0) | 2024.06.04 |
| Unreal Engine 캐릭터의 광역 스킬 구현(7) 스킬 어트리뷰트 추가 (0) | 2024.06.04 |
| Unreal Engine 캐릭터의 광역 스킬 구현(6) UABGA_AttackHitCheck 수정 (0) | 2024.05.27 |