一行代码省流:
SystemAPI.GetSingleton<GhostRelevancy>()
当你需要按照区域、距离或者场景对Ghost进行筛选的时候,Netcode for Entities里并没有类似FishNet那样方便的过滤方式,需要获取一个过滤专用的组件:
GhostRelevancy
。
这个结构的内容不多,但功能很强大,但用起来很累,但概念其实挺简单的:设置Ghost和客户端的关联性。有关联就发送,或者有关联就不发送。
在1.2.3版本,它里面只有三个field:
-
GhostRelevancyMode
:设置关联性的模式,默认是Disabled,既无视关联性,永远向每一个客户端发送每一个Ghost
当设置成SetIsRelevant的时候,在后面的GhostRelevancySet里面设置的GhostId会被认为是“和客户端有关联的”,则这些Ghost会向客户端发送,未设置的则不会发送
当设置成SetIsIrrelevant的时候,规则就会反过来。 -
GhostRelevancySet
:是一个HashMap,其中Value值的那个int项是没有使用到的。也就是说是当个HashSet在用的。我不知道为啥这玩意儿没有做成NativeParallelHashSet,可能是写代码的时候Unity.Collection还没有吧。 -
DefaultRelevancyQuery
:一个EntityQuery,根据前面GhostRelevancyMode设置的关联性模式,通过Query的结果判断是否有关联。不过根据Changelog,这个东西应该是1.3.0才有,然而在1.2.3就已经出现在代码里了。目前可以先不管他,鬼知道里面有没有什么BUG。
启用筛选的方式也很简单,下面是我用的方案。
首先做一个一次性执行的ISystem,设置好
GhostRelevancyMode
。比如设置成“有关联”模式:
SystemAPI.GetSingletonRW<GhostRelevancy>().ValueRW.GhostRelevancyMode = GhostRelevancyMode.SetIsRelevant;
接下来主要就是操作
GhostRelevancySet
了。方法是往里面填充大量的
RelevantGhostForConnection
结构。这东西就是一个
NetworkId
和一个
GhostId
。把你想让某个客户端看到的每一个Ghost都做一个
RelevantGhostForConnection
,一股脑全扔到
GhostRelevancySet
里,剩下的交给系统。
至于为什么NetCode没有选择用MultiHashMap?猛一看,每一个NetworkId对应多个GhostId,好像MultiMap是更适合的数据结构。其实是因为NetCode内部实现上是在遍历每一个Ghost所在的Chunk的时候,对着
GhostRelevancySet
用
ContainsKey
来判断这个Ghost要不要写到Snapshot里面去的。这种方式的话不用MultiHashMap反而是更高效的方法。具体的代码在
GhostChunkSerializer.cs
文件里,
UpdateGhostRelevancy
方法内。
因为我想做的是按区域过滤,比如玩家位于区域X的时候,那么就只将区域X和这个区域附近的一圈区域内的Ghost发送给他。而玩家会在不同的区域里晃来晃去,因此我操作
GhostRelevancySet
方法是做了一个Singleton Entity,称为SectorOperationCommands,上面有四个
IBufferElementData
,每一个都代表一个指令:
- Ghost在区域X内生成
- Ghost在区域X内删除
- 玩家进入区域X
- 玩家离开区域X
使用的时候就只需要往上面添加命令,然后写了一个ProcesssceneRelevancyCommanDSSystem来统一处理。接下来调整它的执行顺序,确保它在所有Ghost/玩家的处理之后,
GhostSendSystem
之前执行即可。而
GhostSendSystem
是在
SimulationSystemGroup
里,
EndSimulationEntityCommandBufferSystem
后面执行的,所以只要不把处理GhostRelevancySet的ISystem丢到
PresentationSystemGroup
去,一般就不会有问题。
我又做了一个Singleton Entity,称为SectorInfoCollection,里面保存了所有区域的数据,比如某个区域里有哪些GhostId,有哪些NetworkId什么的。这样当玩家新进入一个特定区域的时候,要把哪些GhostId和他关联起来就很好处理了。同样的当这个区域里生成了一个Ghost,要把它和哪些NetworkId关联起来也一目了然。当然你也可以选择直接把这堆数据就放在ISystem里。因为我还要在别处用这些数据,所以做成了一个Entity。
对了,获取GhostId的方法是对着Ghost Entity来一发
GetComPONEnt<GhostInstance>()
。
代码就不贴了,我不喜欢在文章里贴大段大段的代码。重要的是传递概念,而不是写一堆不好CV还得人肉编译的英文符号。