前言
UE4 中的网格分块同步是基于 UReplicationGraphNode_GridSpatialization2D 同步节点的,本文尝试分析 UReplicationGraphNode_GridSpatialization2D 节点的同步流程
UReplicationGraphNode_GridSpatialization2D 下文简称 “分块节点”
相关配置
CellSize:网格的大小
SpatialBias:网格的边界,超过边界会重建网格
ConnectionMaxZ:大于这个高度的玩家不会参与到收集同步 Actor 的过程中
添加和移除同步对象
分块节点本身不保存和移除任何同步对象,对象的记录是通过
1 | void AddActor_Static(const FNewReplicatedActorInfo& ActorInfo, FGlobalActorReplicationInfo& ActorRepInfo) { AddActorInternal_Static(ActorInfo, ActorRepInfo, false); } |
来处理的,其中 AddActor_Dynamic 是会移动的 Actor,Static 则是静态不会移动的, AddActor_Dormancy 则是休眠的时候看做静态的,非休眠的时候变为可移动的 Actor
其中 Internal 的几个函数都是 virtual ,可继承后在子类改变其行为
这里只讨论默认的行为
Add Dynamic
如果 Actor 的属性为 bAlwaysRelevant ,会什么也不干,并触发 Warning
否则加入
1 | TMap<FActorRepListType, FCachedDynamicActorInfo> DynamicSpatializedActors; |
中
Add Static
如果 Actor 还未初始化,会加入一个
1 | TArray<FPendingStaticActors> PendingStaticSpatializedActors; |
中
否则会把它的 GlobalActorReplicationInfo (下文称Actor全局同步Info)
的位置属性赋值为当前 Actor 位置
如果分块节点有范围限制,则在此 Actor 可撑大范围的前提下(通过 Class 过滤)
重新按此 Actor 位置来设定范围
设定规则为当前 Actor 2D 位置减去单个网格大小的二分之一(即是扩展到 Actor 外侧多二分之一网格大小)
然后把对象加入到
1 | TMap<FActorRepListType, FCachedStaticActorInfo> StaticSpatializedActors; |
并根据其位置,获取到对应的 UReplicationGraphNode_GridCell 结点(代表单个网格的节点,下文称 __网格节点__)
并调用其 UReplicationGraphNode_GridCell::AddStaticActor 函数加入到网格节点中
网格节点在分块节点下保存在
1 | TArray< TArray<UReplicationGraphNode_GridCell*> > Grid; |
中,为一个二维数组,按 X 和 Y 划分
UReplicationGraphNode_GridCell::AddStaticActor
网格节点继承自 UReplicationGraphNode_ActorList
自带一个同步对象列表
当 Actor全局同步Info 的 bWantsToBeDormant 为 true 的时候
会把这个 Actor 加入到网格节点的 UReplicationGraphNode_DormancyNode 子节点中
否则会直接加入网格节点
Remove Static
Add Static 的反操作,没啥可说的
Remove Dynamic
先从 DynamicSpatializedActors 中找到 Actor 的 FCachedDynamicActorInfo
然后根据其中储存的网格信息找到相关网格,然后调用网格的
1 | UReplicationGraphNode_GridCell::RemoveDynamicActor(const FNewReplicatedActorInfo& ActorInfo) |
移除 Actor,
然后再移除 DynamicSpatializedActors 中相应的 Actor
UReplicationGraphNode_GridCell::RemoveDynamicActor
找到网格节点的 DynamicNode ,并调用其 NotifyRemoveNetworkActor
DynamicNode 的默认实现为 UReplicationGraphNode_ActorListFrequencyBuckets
但是可以自定义其类型
最终获取同步对象的时候,Dynamic 类型的 Actor 都是从 DynamicNode 上获取的
,而从上面 Static Add 的部分我们可以看出来 Static 类型的 Actor 是直接从网格节点上获取的
关于 UReplicationGraphNode_ActorListFrequencyBuckets
这个节点用于限制每次同步的时候从节点上获取的 Actor 数量,来处理避免扎堆同步的情况,细节上先不表
AddDormancy
如果是 ActorRepInfo.bWantsToBeDormant 为 true
直接调用 AddActorInternal_Static,只是休眠参数传入 true
否则直接调用 AddActorInternal_Dynamic
简单明了
休眠状态的变化,则是通过 Add 的时候添加休眠事件的回调来处理的
RemoveDormancy
和上面那个相反的操作,没啥可说的
Actor 信息存放位置总结
Static 对象引用是放在网格节点上的
Dynamic 对象引用是放在网格节点的 Dynamic 节点上的
Dormancy 对象在休眠的时候,放在网格节点的 Dormancy 子节点,非休眠的时候,放在 Dynamic 子节点上
同步前:PrepareForReplication
我们来查看函数
1 | UReplicationGraphNode_GridSpatialization2D::PrepareForReplication() |
首先,遍历 DynamicSpatializedActors,做以下操作:
获取其 Actor 位置,并查看是否需要扩大同步的范围(和 AddStatic 中的操作一样)
无需重新创建网格的时候,执行以下操作
a. 获取当前 Actor 所在网格信息
b. 检测当前所在网格和上次所在网格的关系,如发生改变了,移除不在的网格节点中的 Actor 信息,新增新网格节点中的 Actor 信息
c. 如果有更新网格信息,则重设 Actor 同步信息中的网格信息
然后,遍历 PendingStaticSpatializedActors,检查是否初始化完毕了,并加入 Static 列表中
如果需要重建网格,执行以下操作
- 移除当前的所有网格节点
- 遍历 DynamicSpatializedActors 和 StaticSpatializedActors,根据现有 Actor 重建网格节点并加入 Actor 信息
同步前处理内容总结
同步之前,主要做的是重新设定 Actor 的网格的工作,如有需要,会重建网格节点列表
搜集需要同步的 Actor 列表:GatherActorListsForConnection
首先,遍历每个玩家角色
获得当前玩家所在的网格信息
获得玩家上次的位置
合并相同玩家的网格信息,在合并后的网格中,收集需要同步的 Actor
a. 和上面的 位置 一样,找到相应的网格节点的子节点,收集相关内容
把玩家所在的网格标记为 Active 网格
如果需要销毁 Dynamic 的休眠 Actor,则遍历 Active 网格,如果 Active 网格发生改变了,则
- 找到网格的 DormancyNode 并获取休眠 Actor 列表
- 找到 Active 网格的上次的玩家网格信息的 DormancyNode 并获取休眠 Actor 列表
如果 Active 网格发生改变了,并且上次的玩家网格中有休眠 Actor,则遍历休眠 Actor 列表
- 通知客户端 Destroy 这个 Actor
- 修改这个 Actor 的网格节点上的休眠节点的这个 Actor 的同步状态
收集同步列表总结
先将玩家所在网格标记为 Active 网格,合并重复网格后,收集网格中需要同步的 Actor,然后检查并通知切换了网格的客户端销毁上个网格中的休眠的动态 Actor