通过ArcInventory中的几个Task示例可以了解GAS中的
AbilityTask
、TargetData
、Predicting
之间的相互协作
一、UArcInvAbilityTask_SimpleTarget
1.1、属性
FGameplayAbilityTargetDataHandle ServerTargetData;
简洁,仅有一个TargetDataHandle
负责数据传输
1.2、方法
重写了AbilityTask的函数,同时提供虚函数调用给子类重写
virtual void Activate() override;
virtual void OnTargetDataCallback(const FGameplayAbilityTargetDataHandle& Data, FGameplayTag ActivationTag);
virtual void OnTargetDataCancelled();
virtual FGameplayAbilityTargetDataHandle GenerateTargetHandle();
virtual void HandleTargetData(const FGameplayAbilityTargetDataHandle& Data);
virtual void HandleCancelled();
1.2.1、Activate
主要作用是将客户端的内容传送到服务端并一起执行之后的内容
//创建一个网络同步点, 服务器必须等待预测的客户发送该事件的预测密钥。
FScopedPredictionWindow ScopedWindow(AbilitySystemComponent, IsPredictingClient());
//1、生成TargetData,交由子类实现生成不同的TargetData并放入TargetDataHandle中
FGameplayAbilityTargetDataHandle TargetDataHandle = GenerateTargetHandle();
//2、TargetDataHandle的发送与接收
//2.1、将客户端的TargetData同步出去
if (IsPredictingClient())
{
FGameplayTag ActivationTag;
AbilitySystemComponent->CallServerSetReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey(),
TargetDataHandle, ActivationTag, AbilitySystemComponent->ScopedPredictionKey);
//将TargetData接入到输出Pin
HandleTargetData(TargetDataHandle);
}
//2.2、服务器获取到TargetData并输出到对应的Pin
else
{
const bool bIsLocallyControlled = Ability->GetCurrentActorInfo()->IsLocallyControlled();
//保留服务器的目标数据,当客户的数据进来时,我们就可以将数据进行比较。
ServerTargetData = TargetDataHandle;
//服务器进行远端接收TargetData并绑定对应函数
if (!IsLocallyControlled())
{
AbilitySystemComponent->AbilityTargetDataSetDelegate(GetAbilitySpecHandle(), GetActivationPredictionKey())
.AddUObject(this, &UArcInvAbilityTask_SimpleTarget::OnTargetDataCallback);
AbilitySystemComponent->AbilityTargetDataCancelledDelegate(GetAbilitySpecHandle(), GetActivationPredictionKey())
.AddUObject(this, &UArcInvAbilityTask_SimpleTarget::OnTargetDataCancelled);
AbilitySystemComponent->CallReplicatedTargetDataDelegatesIfSet(GetAbilitySpecHandle(), GetActivationPredictionKey());
SetWaitingOnRemotePlayerData();
}
//如果我们不是预测客户端,但我们是本地控制的,这意味着我们是本地或者监听服务器主机,则直接Callback不需要网络传输
else
{
FGameplayTag ActivationTag;
OnTargetDataCallback(TargetDataHandle, ActivationTag);
EndTask();
}
}
实现:
1、让子类重写GenerateTargetHandle
()函数生成需要的TargetData
2、客户端将该TargetData
通过Prediction
发送给服务器同时执行本地HandleTargetData
()逻辑,该逻辑由子类重写
3、服务器绑定数据接收回调并最终执行与客户端的相同逻辑
1.2.1、OnTargetDataCallback与OnTargetDataCancelled
作为服务器获取到TargetData
的回调函数使用,实际上也只是执行一遍客户端执行的HandleTargetData
()
void UArcInvAbilityTask_SimpleTarget::OnTargetDataCallback(const FGameplayAbilityTargetDataHandle& Data, FGameplayTag ActivationTag)
{
//与客户端执行同一个函数
HandleTargetData(Data);
AbilitySystemComponent->ConsumeClientReplicatedTargetData(GetAbilitySpecHandle(), GetActivationPredictionKey());
EndTask();
}
void UArcInvAbilityTask_SimpleTarget::OnTargetDataCancelled()
{
//执行另一个取消的回调,空函数,交由子类重写
HandleCancelled();
EndTask();
}
1.3、总结
虽然实现简单,但同时使用了
Predicting
作为网络预测、AbilityTask
作为任务发起、TargetDataHandle
作为网络传输的数据,麻雀虽小五脏俱全。
解决的问题
本地操作的数据需要在GA中同步到服务器一起执行
保证服务器与客户端执行GA时使用的数据是一样的
大体方案:
1、首先让子类生成不同类型的TargetData
2、通过预测窗口将数据发送到服务器并让本地预测执行HandleTargetData
3、服务器接收到数据后也开始执行HandleTargetData
二、UArcAbilityTask_SwitchInventory
2.1、属性
public:
UPROPERTY(BlueprintAssignable)
FArcSwitchItemsRecieved OnSlotsRecieved;
UPROPERTY(BlueprintAssignable)
FArcItemSwitchCancelled OnSlotsCancelled;
protected:
FArcInventoryItemSlotReference FromSlot;
FArcInventoryItemSlotReference ToSlot;
2.1.2、OnSlotsRecieved与OnSlotsCancelled
作为Task的输出Pin,其反射定义为:
DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FArcSwitchItemsRecieved, const FArcInventoryItemSlotReference&, FromSlot, const FArcInventoryItemSlotReference&, ToSlot);
用于返回切换库存时的两个库存引用
2.1.2、FromSlot与ToSlot
作为外界传入Task的两个Slot,代表了SwitchSlot的两个参数。也是客户端需要发送给服务端的真实数据
2.2、接口
UFUNCTION(BlueprintCallable, meta = (HidePin = "OwningAbility", DefaultToSelf = "OwningAbility", BlueprintInternalUseOnly = "true", HideSpawnParms = "Instigator"), Category = "Ability|Tasks")
static UArcAbilityTask_SwitchInventory* SwitchInventorySlots(UGameplayAbility* OwningAbility, const FArcInventoryItemSlotReference& FromSlot, const FArcInventoryItemSlotReference& ToSlot);
virtual FGameplayAbilityTargetDataHandle GenerateTargetHandle() override;
virtual void HandleTargetData(const FGameplayAbilityTargetDataHandle& Data) override;
virtual void HandleCancelled() override;
2.2.1、SwitchInventorySlots()
作用:
蓝图调用Task的接口
UArcAbilityTask_SwitchInventory* Task = NewAbilityTask<UArcAbilityTask_SwitchInventory>(OwningAbility);
Task->FromSlot = FromSlot;
Task->ToSlot = ToSlot;
return Task;
实现:
将传入的两个ItemSlotReference
保存,为客户端发送给服务器做准备
2.2.2、GenerateTargetHandle()
作用:
重写了父类函数,来实现自己的TargetData
FGameplayAbilityTargetData_ItemSwitch* ItemSwitchData = new FGameplayAbilityTargetData_ItemSwitch();
ItemSwitchData->FromSlot = FromSlot;
ItemSwitchData->ToSlot = ToSlot;
return FGameplayAbilityTargetDataHandle(ItemSwitchData);
实现:
创建用于交换ItemSlot
的TargetData
并填入相关数据,等待发送给服务器
2.2.3、HandleTargetData()与
作用:
处理TargetData中的数据
void UArcAbilityTask_SwitchInventory::HandleTargetData(const FGameplayAbilityTargetDataHandle& Data)
{
const FGameplayAbilityTargetData_ItemSwitch* SwitchData = static_cast<const FGameplayAbilityTargetData_ItemSwitch*>(Data.Get(0));
if (SwitchData != nullptr){
OnSlotsRecieved.Broadcast(SwitchData->FromSlot, SwitchData->ToSlot);}
else{
OnSlotsCancelled.Broadcast();}
}
void UArcAbilityTask_SwitchInventory::HandleCancelled()
{
OnSlotsCancelled.Broadcast();
}
实现:
主要是通过两个Pin将TargetData
中的数据发送出去,当有数据的时候走OnSlotsRecieved
端口,没有数据时走OnSlotsCancelled
如果没有收到的话则通过OnSlotsCancelled
端口返回
2.2.4、FGameplayAbilityTargetData_ItemSwitch
一个存储FromSlot
和ToSlot
的数据结构
USTRUCT(BlueprintType)
struct FGameplayAbilityTargetData_ItemSwitch : public FGameplayAbilityTargetData
{
GENERATED_USTRUCT_BODY()
public:
UPROPERTY()
FArcInventoryItemSlotReference FromSlot;
UPROPERTY()
FArcInventoryItemSlotReference ToSlot;
// -------------------------------------
virtual UScriptStruct* GetScriptStruct() const override{
return FGameplayAbilityTargetData_ItemSwitch::StaticStruct();
}
virtual FString ToString() const override{
return TEXT("FGameplayAbilityTargetData_ItemSwitch");
}
bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess);
};
template<>
struct TStructOpsTypeTraits<FGameplayAbilityTargetData_ItemSwitch> : public TStructOpsTypeTraitsBase2<FGameplayAbilityTargetData_ItemSwitch>
{
enum{
WithNetSerializer = true
};
};
需要注意的点:
GetScriptStruct
():需要重写为自己类的ToString()
:与GetScriptStruct
()配套,用于DebugNetSerialize()
:用于手动序列化,需要同时配套TStructOpsTypeTraits<FGameplayAbilityTargetData_ItemSwitch>
TStructOpsTypeTraits<FGameplayAbilityTargetData_ItemSwitch>
:给结构体添加WithNetSerializer
宏,告知UBT该结构体实现了NetSerialize
()函数,可以调用
2.3、总结
目的:
实现从客户端发送FromSlot
和ToSlot
给服务器并一起执行之后的GA逻辑
方案:
在父类的客户端服务器发送方案的基础下:
添加了外部接口SwitchInventorySlots
()供外部调用该Task
重写GenerateTargetHandle
()以及HandleTargetData
()来进行数据的发送和处理