0. 개요

이전 포스트에서 커스텀 어빌리티 태스크로 마우스 커서 아래의 데이터를 얻을 수 있었다.

그러나 서버에서는 데이터를 전달받지 못하고 단순히 델리게이트를 발동시키기만 했기 때문에 디버그 구체가 (0, 0, 0)에 그려지는 모습이 있었다.

이번 포스트에서 적절한 타이밍에 데이터를 전송하는 방법(서버 RPC)에 대해서 알아본다.

 

8-9. 코드 리펙토링 - 메세지 팝업 디버그, RPC

0. 개요이전에 클릭으로 이동하기에서 클릭으로 목적지를 캐싱하는 부분을 기존의 함수인 CursorTrace로 대체할 수 있다.코드를 리펙토링하여 재사용성, 효율성, 가독성을 개선한다.  1. 목적지 캐

crat.tistory.com

 

 

1. 서버로 데이터 전송하기

(1) 문제 확인

어빌리티 태스크가 클라이언트에서 실행된 이후 서버에서 실행되는 시간을 입실론(e), 해당 데이터를 클라이언트에서 서버로 전송하는 시간을 델타(d)라고 하자.

서버로 데이터가 전송되기 전에, 서버에서 태스크가 실행되면(e<d) 이전과 같이 디버그 구체가 원점에 그려지는 버그가 발생한다.

유효한 조건은 서버로 데이터가 전송된 후에 태스크가 실행되는 것(e>d)이다.

 

 

(2) 문제 해결

FGameplayAbilityTargetData 구조체의 ServerSetReplicatedTargetData 함수를 실행하면 서버가 TargetSet을 가져와 델리게이트를 브로드캐스트한다.

서버에서는 맵에 어빌리티 스펙과 타겟 데이터를 매핑한다.

서버가 데이터를 받기 전에 Activate 함수를 실행해도 CallReplicatedTargetDataDelegateIfSet 함수를 사용하면 델리게이트에 데이터를 넘겨줄 수 있다.

 

 

(3) Activate 함수 리펙토링

void UTargetDataUnderMouse::Activate()
{
	// 클라이언트인지 확인
	const bool bIsLocallyControlled = Ability->GetCurrentActorInfo()->IsLocallyControlled();
	if (bIsLocallyControlled)
	{
		SendMouseCursorData();
	}
	else
	{
		// TODO: 서버에 있으면 타겟 데이터를 기다림
	}


}

컨트롤되는 액터가 서버에 있는지 클라이언트에 있는지 파악하고 함수를 실행한다.

 

 

(4) 마우스 커서 데이터 전송하기 - SendMouseCursorData 함수

FGameplayAbilityTargetData 구조체가 정의된 헤더 파일을 보면 FGameplayAbilityTargetData_SingleTargetHit 구조체가 있는 것을 볼 수 있다.

해당 구조체를 이용해 하나의 Hit Result 정보를 패킹할 수 있다.

DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMouseTargetDataSignature, const FGameplayAbilityTargetDataHandle&, DataHandle);

전송할 데이터는 FVector가 아닌 어빌리티 타겟 데이터 핸들이므로 델리게이트를 수정한다.

void UTargetDataUnderMouse::SendMouseCursorData()
{
	// 예측
	FScopedPredictionWindow ScopedPrediction(AbilitySystemComponent.Get());

	APlayerController* PC = Ability->GetCurrentActorInfo()->PlayerController.Get();
	
	// 마우스 커서 데이터
	FHitResult CursorHit;
	PC->GetHitResultUnderCursor(ECC_Visibility, false, CursorHit);

	// ServerSetReplicatedTargetData의 매개 변수
	FGameplayAbilityTargetDataHandle DataHandle;
	FGameplayAbilityTargetData_SingleTargetHit* Data = new FGameplayAbilityTargetData_SingleTargetHit();
	Data->HitResult = CursorHit;
	DataHandle.Add(Data);

	// 서버로 데이터 전송
	AbilitySystemComponent->ServerSetReplicatedTargetData(
		GetAbilitySpecHandle(),
		GetActivationPredictionKey(),
		DataHandle,
		FGameplayTag(),
		AbilitySystemComponent->ScopedPredictionKey);

	if (ShouldBroadcastAbilityTaskDelegates())
	{
		// 어빌리티가 아직 활성화 중인가
		ValidData.Broadcast(DataHandle);
	}
}

ServerSetReplicatedTargetData 함수를 사용해 패킹된 데이터를 서버로 전송한다.

ShouldBroadcastAbilityTaskDelegates 함수를 사용해 어빌리티가 아직 활성화 중이면 데이터를 브로드캐스팅 한다.

ScopedPrediction를 통해 작업을 서버에서 예측하여 문제가 없으면 실행하게 된다.

 

 

2. 서버에서 데이터 받기

클라이언트에서 전송한 데이터를 받는다.

 

(1) Activate 함수

void UTargetDataUnderMouse::Activate()
{
	// 클라이언트인지 확인
	const bool bIsLocallyControlled = Ability->GetCurrentActorInfo()->IsLocallyControlled();
	if (bIsLocallyControlled)
	{
		SendMouseCursorData();
	}
	else
	{
		// 서버
		FGameplayAbilitySpecHandle SpecHandle = GetAbilitySpecHandle();
		FPredictionKey ActivationPredictionKey = GetActivationPredictionKey();
		
		// 콜백 함수 바인딩
		AbilitySystemComponent.Get()->AbilityTargetDataSetDelegate(SpecHandle, ActivationPredictionKey).AddUObject(this, &UTargetDataUnderMouse::OnTargetDataReplicatedCallback);
		
		// 클라이언트로부터 타겟 데이터를 획득했는가
		const bool bCalledDelegate = AbilitySystemComponent->CallReplicatedTargetDataDelegatesIfSet(SpecHandle, ActivationPredictionKey);

		if (bCalledDelegate == false)
		{
			// 데이터가 도착할 때까지 대기
			SetWaitingOnRemotePlayerData();
		}
	}
}

서버라면 콜백 함수를 바인딩하고 타겟 데이터를 받았는지 확인한다.

데이터 획득 여부와 관계 없이 타겟 데이터 복제 콜백 함수를 바인딩한다.

데이터를 아직 획득하지 않았다면 SetWaitingOnRemotePlayerData 함수를 이용하여 대기한다.

 

 

(2) 타겟 데이터 복제 콜백 함수

void UTargetDataUnderMouse::OnTargetDataReplicatedCallback(const FGameplayAbilityTargetDataHandle& DataHandle, FGameplayTag ActivationTag)
{
	// 데이터가 수
	AbilitySystemComponent->ConsumeClientReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey());
	if (ShouldBroadcastAbilityTaskDelegates())
	{
		// 어빌리티가 아직 활성화 중인가
		ValidData.Broadcast(DataHandle);
	}
}

타겟 데이터를 받았다면 타겟 데이터를 소비(정보를 정리하고 수신했다는 불리언을 변경)하고 추가작업이 발생하지 않도록 보장한 후 어빌리티가 아직 활성화 중이라면, DataHandle을 브로드캐스트한다.

 

 

(3) 타겟 데이터 초기화 - AuraAssetManager

타겟 데이터를 사용하기 위해서 반드시 에셋 매니저에서 초기화를 진행해야한다.

void UAuraAssetManager::StartInitialLoading()
{
	Super::StartInitialLoading();

	FAuraGameplayTags::InitailizeNativeGameplayTags();

	UAbilitySystemGlobals::Get().InitGlobalData();

}

 

 

(4) GA_FireBolt 블루프린트 수정

이제 출력 핀이 Target Data Handle로 변경되었으므로 노드를 새로고침한다.

타겟 데이터로부터 Hit Result를 가져오고 분할하려 Location을 획득해 디버그 구체로 넘겨준다.

이제 클라이언트가 서버에게 정상적으로 데이터를 전달하는 것을 확인할 수 있다.