```cpp
void UTargetDataUnderCursor::Activate()
{
if (Ability == nullptr) return;
const bool bIsLocallyControlled = Ability->GetCurrentActorInfo()->IsLocallyControlled();
if (bIsLocallyControlled)
{
// 클라이언트에서 서버 측으로 정보를 전송하기 위한 함수
SendCursorData();
}
else
{
// On Server, listen for TargetData
}
}
``` // TargetDataUnderCursor.cpp
void UTargetDataUnderCursor::SendCursorData()
{
// FScopedPredictionWindow 생성
FScopedPredictionWindow ScopedPrediction(AbilitySystemComponent.Get());
// 기존에 PlayerController의 GetHitResultUnderCursor()를 호출하던 부분
FHitResult HitResult;
APlayerController* PC = Ability->GetCurrentActorInfo()->PlayerController.Get();
PC->GetHitResultUnderCursor(ECC_Visibility, false, HitResult);
// FGameplayAbilityTargetTypes.h를 참조
FGameplayAbilityTargetData_SingleTargetHit* Data = new FGameplayAbilityTargetData_SingleTargetHit();
Data->HitResult = HitResult;
// FGameplayAbilityTargetData 상속 구조체들을 래핑하는 핸들을 생성하여 추가
FGameplayAbilityTargetDataHandle DataHandle;
DataHandle.Add(Data);
AbilitySystemComponent->ServerSetReplicatedTargetData(
GetAbilitySpecHandle(),
GetActivationPredictionKey(),
DataHandle,
FGameplayTag(),
AbilitySystemComponent->ScopedPredictionKey);
if (ShouldBroadcastAbilityTaskDelegates())
{
OnCursorLocation.Broadcast(DataHandle);
}
}
```
/** Current prediction key, set with FScopedPredictionWindow */
FPredictionKey ScopedPredictionKey;void UTargetDataUnderCursor::Activate()
{
if (Ability == nullptr) return;
const bool bIsLocallyControlled = Ability->GetCurrentActorInfo()->IsLocallyControlled();
if (bIsLocallyControlled)
{
SendCursorData();
}
else
{
// On Server, listen for TargetData
const FGameplayAbilitySpecHandle SpecHandle = GetAbilitySpecHandle();
const FPredictionKey ActivationPredictionKey = GetActivationPredictionKey();
AbilitySystemComponent->AbilityTargetDataSetDelegate(SpecHandle, ActivationPredictionKey)
.AddUObject(this, &UTargetDataUnderCursor::OnTargetDataReplicatedCallback);
const bool bCalledDelegate = AbilitySystemComponent->CallReplicatedTargetDataDelegatesIfSet(SpecHandle, ActivationPredictionKey);
if (!bCalledDelegate)
{
SetWaitingOnRemotePlayerData();
}
}
}// AbilitySystemComponent_Abilities.cpp
FAbilityTargetDataSetDelegate& UAbilitySystemComponent::AbilityTargetDataSetDelegate(FGameplayAbilitySpecHandle AbilityHandle, FPredictionKey AbilityOriginalPredictionKey)
{
return AbilityTargetDataMap.FindOrAdd(FGameplayAbilitySpecHandleAndPredictionKey(AbilityHandle, AbilityOriginalPredictionKey))->TargetSetDelegate;
}// GameplayAbilityTargetTypes.h
/** Generic callback for returning when target data is available */
DECLARE_MULTICAST_DELEGATE_TwoParams(FAbilityTargetDataSetDelegate, const FGameplayAbilityTargetDataHandle&, FGameplayTag);/**
* Associative container of GameplayAbilitySpecs + PredictionKeys --> FAbilityReplicatedDataCache. Basically, it holds replicated data on the ability system component that abilities access in their scripting.
* This was refactored from a normal TMap. This mainly servers to:
* 1. Return shared ptrs to the cached data so that callsites are not vulnerable to the underlying map shifting around (E.g invoking a replicated event ends the ability or activates a new one and causes memory to move, invalidating the pointer).
* 2. Data is cleared on ability end via ::Remove.
* 3. The FAbilityReplicatedDataCache instances are recycled rather than allocated each time via ::FreeData.
*
**/
struct FGameplayAbilityReplicatedDataContainer
{
GAMEPLAYABILITIES_API TSharedPtr<FAbilityReplicatedDataCache> Find(const FGameplayAbilitySpecHandleAndPredictionKey& Key) const;
GAMEPLAYABILITIES_API TSharedRef<FAbilityReplicatedDataCache> FindOrAdd(const FGameplayAbilitySpecHandleAndPredictionKey& Key);
void Remove(const FGameplayAbilitySpecHandleAndPredictionKey& Key);
void PrintDebug();
private:
typedef TPair<FGameplayAbilitySpecHandleAndPredictionKey, TSharedRef<FAbilityReplicatedDataCache>> FKeyDataPair;
TArray<FKeyDataPair> InUseData;
TArray<TSharedRef<FAbilityReplicatedDataCache>> FreeData;
};이 구조체의 FAbilityTargetDataSetDelegate 변수인 TargetSetDelegate를 얻어와 거기에 바인딩하기 위해서 AbilityTargetDataSetDelegate()를 호출했던 것이다.
/** Struct defining the cached data for a specific gameplay ability. This data is generally synchronized client->server in a network game. */
struct GAMEPLAYABILITIES_API FAbilityReplicatedDataCache
{
/** What elements this activation is targeting */
FGameplayAbilityTargetDataHandle TargetData;
/** What tag to pass through when doing an application */
FGameplayTag ApplicationTag;
/** True if we've been positively confirmed our targeting, false if we don't know */
bool bTargetConfirmed;
/** True if we've been positively cancelled our targeting, false if we don't know */
bool bTargetCancelled;
/** Delegate to call whenever this is modified */
FAbilityTargetDataSetDelegate TargetSetDelegate;
/** Delegate to call whenever this is confirmed (without target data) */
FSimpleMulticastDelegate TargetCancelledDelegate;
/** Generic events that contain no payload data */
FAbilityReplicatedData GenericEvents[EAbilityGenericReplicatedEvent::MAX];
/** Prediction Key when this data was set */
FPredictionKey PredictionKey;
FAbilityReplicatedDataCache() : bTargetConfirmed(false), bTargetCancelled(false) {}
virtual ~FAbilityReplicatedDataCache() { }
/** Resets any cached data, leaves delegates up */
void Reset()
{
bTargetConfirmed = bTargetCancelled = false;
TargetData = FGameplayAbilityTargetDataHandle();
ApplicationTag = FGameplayTag();
PredictionKey = FPredictionKey();
for (int32 i=0; i < (int32) EAbilityGenericReplicatedEvent::MAX; ++i)
{
GenericEvents[i].bTriggered = false;
GenericEvents[i].VectorPayload = FVector::ZeroVector;
}
}
/** Resets cached data and clears delegates. */
void ResetAll()
{
Reset();
TargetSetDelegate.Clear();
TargetCancelledDelegate.Clear();
for (int32 i=0; i < (int32) EAbilityGenericReplicatedEvent::MAX; ++i)
{
GenericEvents[i].Delegate.Clear();
}
}
};
```cpp
void UTargetDataUnderCursor::OnTargetDataReplicatedCallback(const FGameplayAbilityTargetDataHandle& DataHandle, FGameplayTag ActivationTag)
{
AbilitySystemComponent->ConsumeClientReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey());
if (ShouldBroadcastAbilityTaskDelegates())
{
OnCursorLocation.Broadcast(DataHandle);
}
}
```
- 서버에서 제때 Activate()가 실행되고 FAbilityTargetDataSetDelegate에 함수를 바인딩하면, 서버에서 TargetData를 제때 활용할 수 있고, 이 TargetData를 활용했다고 처리해주어야 한다. 이를 위한 함수가 AbilitySystemComponent의 ConsumeClientReplicatedTargetData()이다.
- 그 후 SendCursorData()에서 한 것처럼, ShouldBroadcastAbilityTaskDelegates()를 검사하고 델리게이트 Broadcast()를 실시함. void UAuraAssetManager::StartInitialLoading()
{
Super::StartInitialLoading();
FAuraGameplayTags::InitializeNativeGameplayTags();
UAbilitySystemGlobals::Get().InitGlobalData();
}