【UE·网络篇】ReplicationGraph入门教程
ReplicationGraph公司项目里用了很久了,一直没看过。最近抽点时间看了下,发现网上教程很少(大抵都是源码解读,原理详解之类)。UE官方的ShooterGame项目虽然是个不错的例子但是还是有点复杂了,对于从来没接触过ReplicationGraph的人来说也容易看得一头雾水。所以想了想自己还是写一个吧。本文适合网络方面了解不多的新手,比如说像我之前就只会用Replicate,Client,Server之类的宏实现RPC和同步,底层从来没研究过。
默认同步策略的缺陷
在介绍ReplicationGraph之前,先来探讨下UE默认的网络同步架构有什么问题。
先介绍几个概念:
- NetDriver,是DS服务器侧控制连接收包发包的总控制类,放在World上,一个World一个NetDriver。
- NetConnection,管理玩家和服务器的连接。每个玩家的Controller放一个Connection。Owner不属于玩家的Actor没有Connectiion,比如Level,GameState之类,虽然会从服务器上同步到客户端,但是不属于玩家,所以调用他们身上的GetNetConnection会为空。
接下来以下图为例,假设现在有4个玩家,有5个Actor,Actor0所有玩家都要同步,Actor1只给玩家1,Actor2只给玩家2,Actor3只给玩家3,Actor4只给玩家4同步。
针对这种某个Actor应该同步某个指定玩家的情况,Actor有一个IsNetRelevantFor函数可以重写。
那么在NetDriver里处理Actor是否应该同步给玩家的逻辑的简化版伪代码为:
Actor0的IsNetRelevantFor(玩家)
{return true
}
Actor1的IsNetRelevantFor(玩家)
{if(玩家 == 玩家1){return true}else{return false}
}Actor2的IsNetRelevantFor(玩家)
{if(玩家 == 玩家2){return true}else{return false}
}
//Actor3,Actor4同理
.....
for(int i =0 ; i < 玩家数 ; i++)
{for(int j = 0 ; j < Actor数; j ++){Actor[j].IsNetRelevantFor(玩家[i])}
}
在这个案例中,需要遍历的次数为20次。看着好像不多,但是如果是大型多人项目里,比如场景里有1000个Actor和100个玩家,那么每次判断都需要遍历10w次对cpu就是不小的消耗了。虽然说UE提供了一些手段去优化遍历的次数,比如设置剔除距离,降低更新频率,设置更新优先级。但是无法从根本上解决问题,比如说可能从10w次的遍历优化成5w次。但是如果想要更进一步优化,比如优化成1k次以内,那用默认的同步框架就做不了了。
总结起来,就是UE默认的同步策略缺陷是遍历次数太多,对cpu消耗太大。
ReplicationGraph同步策略
UE自己开发的ReplicationGraph插件就是用来解决这种问题。ReplicationGraph优化的原理是把Actor分到各种节点Node里,由节点去管理要给哪个玩家同步哪些Actor。
还是用上面的案例讲一下它的基本原理。
先介绍几个概念
- ReplicationGraph,管理节点,会和一个NetDriver关联。
- NetReplicationGraphConnection,扩展并且关联了NetConnection。
相当于原先的NetDriver和NetConnection当处理到关于同步复制策略的逻辑时,把这些逻辑委派给这两个类,自己只负责传入数据。这是一种访问者设计模式的应用。
那ReplicationGraph是怎么优化的呢?
首先我们把Actor分组,对于Actor0,对所有人同步,所以单独一个节点。对于Actor1到Actor4,我们把它们分到另外一个节点。
然后我们在ReplicationGraph存一个Map列表:
TMap<玩家,Actor*> 玩家Actor查找表
那么两个节点的返回哪些Actor可以同步的逻辑为:
永远同步节点::GatherActorListsForConnection(玩家)
{return TArray{Actor0};
}
条件同步节点::GatherActorListsForConnection(玩家)
{return TArray{ReplicationGraph->玩家Actor查找表[玩家]}
}
这样的话,遍历的次数为4个玩家 x 2个节点 = 8次。是原先遍历次数的40%,看着好像不多。这是因为我现在讲的案例是一种最简单级别的应用,对于大型多人项目这种节点式的同步策略优化效果会特别好。
ReplicationGraph实战
上面说了这么多,还是得实战巩固一下。接下来手把手做一个最简单的需求:
- 将玩家分为两个队,每个玩家只能看到自己队友,看不到另一个队的玩家
首先编辑器下开启ReplicationGraph插件,Build.cs增加ReplicationGraph模块:
新建自定义ReplicationGraph类,创建蓝图,增加DefaultEngine.ini关于ReplicationGraph类的配置:
然后我们需要三种节点: - 对所有玩家都可见的Actor的节点,PlayerState、GameState放到这个节点(GameState好理解,为什么PlayerState也需要?首先PlayerState就算不用ReplicationGraph默认也是会同步给所有玩家,这是因为多人游戏中经常需要拿其他玩家的状态,比如我需要知道其他玩家的血量,击杀死亡数等战场信息。所以PlayerState需要同步给所有玩家。)
- 只对当前玩家可见的Actor的节点,PlayerController放到这个节点。(这个很好理解,每个玩家肯定不需要同步其他玩家的Controller。)
- 处理队伍相关的Actor的节点,Pawn放到这个节点。
第一个节点:
第二个节点,使用自带的UReplicationGraphNode_AlwaysRelevant_ForConnection。
第三个节点:
对于第一个节点,因为对于所有玩家可见。所以必须放在ReplicationGraph上。后面两个节点,因为和玩家相关,所以放在自定义的NetReplicationGraphConnection。这里叫作ConnectionManager是因为UE的官方案例也是这么叫的,这里就入乡随俗。
建好这几个类以后,我们需要捋一下目前的结构,到底有几个Connection,有几个Node?
- 放在Graph上的节点不管有几个玩家,都只有一个。
- ReplicationGraphConnection,每个玩家一个。
- ForConnection和ForTeam节点因为放在Connection上,所以每多一个玩家,就多1个节点。
接下来在Graph上重载两个函数创建节点:
然后我们需要个通用函数,根据Actor获得自定义的ReplicationGraphConnection:
如果能拿到,说明是PlayerController或者以PlayerController为Owner的类,比如Pawn。
接着重写RouteAddNetworkActorToNodes将Actor分到各个Node里。PendingConnectionActors列表的作用是,调这个函数的时候玩家的Connection没有初始化好,所以把Actor缓存放在另外一个地方处理。
然后我们需要一个数据结构,去存队伍Id和Connection的对应查找表,以方便后面Team节点去拿到哪些玩家是同队的。
然后是设置Team函数,这个会在PlayerState里面调用,还是一样,要考虑到Connection没有初始化好的情况:
PlayerState处调用:
注意我们现在有两个地方,都是可能存在Connection没初始化好我就要用的情况。这种要怎么办呢?这种问题的处理方式就是把数据收集起来,放在别的地方调用。
在哪个地方调用?在Node的PrepareForReplication,这个重载函数会一直执行。这样就能保证我们的设置队伍和添加Actor到节点一定能执行到:
最后是最关键的重写Team节点的GatherActorListsForConnection函数:
首先如果直接返回父类的结果,会发生什么事情?
每个玩家只能看到自己。为什么?
回到我们上面这张图:
4个玩家其实有4个Team节点,每个Team节点存了这个节点所在的Connection的玩家。所以默认只返回当前玩家。
如果想要返回同队玩家应该怎么做?还记得我们之前Graph上存了Team和Connection的查找表吗?只要拿到Graph,再拿到当前Team节点所在的Connection,然后我们Connection也存了Team,就能拿到同队玩家的所有Connection。有了同队所有Connection,调用Team节点的父类的GatherActorListsForConnection。就可以做到只显示同队玩家。
GatherActorListsForConnectionDefault就是把父类的调用存起来而已。
最后实现效果:
最后是项目Git工程,使用UE5.6版本:
https://github.com/756915370/LearnReplicationGraph/tree/master
参考资料
- UE官方文档Replication Graph
- UE官方工程ShooterGame。
- Replication Graph 从入门到入土
- Replication Graph – How To Reduce Network Bandwidth In Unreal
- 详解 UE ReplicationGraph 方案