在游戏开发中除了单个属性的属性同步,我们还会遇到容器的属性同步。相应的UE4也提供的TArray的属性同步,不过UE4并未提供TMap的属性同步。
什么情况下我们会用到TArray的属性同步呢,就我所知的有2个系统就会用到。一个是任务系统,比如需要知道当前玩家接取了哪些任务,完成了哪些任务,在进行中的又有哪些任务。还有就是Buff系统,玩家身上有哪些Buff等。这些容器中的数据都有一个特点就是只允许服务端端对容器进行增删改,然后客户端需要进行相应的表现。下面我们以Buff系统为例进行说明。
Buff的属性同步
正常情况下我们是只允许服务端增删改Buff的,这样就需要UE4的属性同步来帮我们将这些变更的Buff复制到客户端,以便客户端做一些操作(比如显示Buff图标,播放Buff光效等)。在不熟悉FastTArray的情况下,我们通常会用TArray来实现类似的功能:
//属性复制,服务端GameplayEffects_Internal中元素变化时,客户端触发函数OnRep_ArrayChange
UPROPERTY(ReplicatedUsing=OnRep_ArrayChange)
TArray<FActiveGameplayEffect> GameplayEffects_Internal;
上面的方法有以下几个问题:
- GameplayEffects_Internal删除(持续时间到了,或者Buff被驱散了)和添加(释放了一个技能,该技能赋予目标某个buff)一个元素的时候,在OnRep_ArrayChange中我们无法知道触发此次属性同步被删除或者添加的是哪个元素。
- 如果我删除的刚好是GameplayEffects_Internal的0号索引位置的元素,连续存储容器的原理大家都知道,TArray会把0号索引后面所有的元素(第1到n-1)每个都往前移,而UE4的属性同步机制会认为第1到n-1所有的元素都已经发生改变,会把这些元素全部属性同步到客户端。
- 针对1问题,有一个可行的方案,那就是再增加一个数组缓存前一个属性同步的数据(该数组不做属性同步,而且只能客户端调用):
//属性复制,GameplayEffects_Internal元素变化时,客户端触发函数OnRep_ArrayChange
UPROPERTY(ReplicatedUsing=OnRep_ArrayChange)
TArray<FActiveGameplayEffect> GameplayEffects_Internal;
TArray<FActiveGameplayEffect> Old_GameplayEffects_Internal_InClient;
OnRep_ArrayChange函数中,先比对GameplayEffects_Internal和Old_GameplayEffects_Internal_InClient,多出来的元素就是服务器新增的元素,缺少的元素就是服务器remove的元素,最后再执行Old_GameplayEffects_Internal_InClient = GameplayEffects_Internal。
- 对于问题2,官方给的方案就是用FastTArray来替代TArray的属性同步了。FastTArray的使用方法见UE4引擎头文件源码:NetSerialization.h。代码编写者真的很良心的在头文件中给出了FastTArray的使用示例和注意事项。其中最需要使用者注意的是采用该方法无法保证客户端和服务容器中的元素顺序的一致,即服务器的第n个元素可能是客户端的第n+x个元素。
现在假设你已经看过NetSerialization.h中的使用示例和注释了,那么我们来看下GAS是如何使用FastTArray来实现Buff的同步的:
- GAS封装了一个FActiveGameplayEffectsContainer容器来操作服务端Buff的增删改。为了让该容器具备FastTArray的特性,我们需要将其继承FFastArraySerializer。
- 申明一个TArray数组GameplayEffects_Internal用于正真的存放Buff,并且用UPROPERTY()宏代替UPROPERTY(ReplicatedUsing=OnRep_ArrayChange)宏。
- GameplayEffects_Internal中的元素需要继承FFastArraySerializerItem,那样在删除添加元素时,会触发PreReplicatedRemove函数和PostReplicatedAdd函数,如果只是元素中的内容变更的话就会触发PostReplicatedChange(这三个函数的作用类似OnRep函数,只会客户端调用)。
为了更好的说明FastTArray的使用,下面给出GASDocumentation-4.25工程的关键代码(该工程可以在下面的地址中下载):
tranek/GASDocumentationgithub.com- 元素的删除: 使用TArray的RemoveAtSwap接口,这会导致数组乱序,但是效率高。
- PostReplicatedAdd重写函数:其中客户端的FActiveGameplayEffectsContainer容器在该函数中不可被变更(不解,该函数的参数不是已经const了吗,GAS为何还要const_cast<FActiveGameplayEffectsContainer&>(InArray))。