UnrealEngine4 Net System Overview
说到联机,就是几台终端与服务器之间的互相连接。
一般来说,如果是各个终端全部连接到一台服务器,这台服务器(或者服务器组)负责计算,则是中心服务器结构。
如果是各个客户端互相之间连接,则是P2P结构。
一般具体业务中,可能是多种模式之间的合并。
Server NetMode
虚幻内置的连接是典型的中心结构,就是所有其他机器均往主机上连,消息全都给主机,主机执行完毕后回信给其他机器。
这样,每台终端就有某种区分了,有些是客户端,有些是服务器,还有可能是客户端和服务器的混血。
虚幻称这种区分为 NetMode ,有四种可能:纯单机(Standalone)、客户端(Client)、Host主机(Listen Server) 和独立服务器(Dedicated Server)。
- 纯单机不具备网络通信功能。
- 独立服务器相当于一个专门的应用程序,跟游戏本体一般是分离开的。可以架设在集体宿舍或者公司的某个始终不关的主机、或者某台云主机上。所有玩家均开启客户端连接到这个独立服务器并进行游乐。目前的许多游戏都是这种模式。
- Host主机模式,相当于一个客户端建主,其它玩家作为客户端连接,这在RTS游戏中很常见。
NetRole, RPC/Replicate
Entity sync
对于联机游戏而言,同一个实体概念(比如某个具体的NPC),在不同的终端上均需要有所体现,一般来说,我们认为在服务器的那个是真身,其他客户端的是代理、假身、虚像。这主要是因为,服务器许多情况下有很多行为的权限,客户端没有,客户端一般只是把用户操作请求发送给服务器,在服务器执行完毕后,通知回客户端。
这里我们有两种可能的做法,一种是服务器逻辑和客户端逻辑完全分离,服务器自有一套自己的类派生体系,跟客户端可以完全不一致,只要概念上能对的上就行了,比如ID能够对上。客户端的实体可能是 ClientNPC 类,服务器是 ServerNPC 类,但是,客户端这个 NPC 的ID是 10000 ,服务器这个 NPC 的ID也是10000,两者之间通过ID交流,就能对上了。比如客户端发上去的消息是: ClientNPC , 10000 号,向左走。服务器收到消息后,找到自己的 10000 号 ServerNPC ,试图对其进行移动。这种情况下,客户端可能甚至都没有移动相关的处理,而服务器则没有客户端动作、特效相关的处理。
虚幻采取的是另一种,Actor 类体系是一套,通过对象的Role和RPC机制来区分究竟这个 Actor 是在客户端还是服务器,哪些流程和方法只在服务器具备权限,哪些没有服务器权限。引擎自动帮你把通信做好,你只需要知道,你当前手里的这个 Actor ,就是这个 NPC 就行了。诚然它们在客户端和服务器,还是两个不同的对象,但是你手里拿到的就是这个 Actor 。
Role
Role 是一个枚举,一般有下面三种可能:
- ROLE_SimulatedProxy :这个对象是运行在客户端的,不具备任何操作权限,只能通过 RPC 发送 Request 。
- ROLE_AutonomousProxy :这个对象是运行在客户端的,具备一定的权限,但是会被服务器修正。这个权限比较特殊,一般用于 Pawn 。
- ROLE_Authority :这个对象可能运行在服务器、客户端、 Standalone ,本终端对其享有控制权。
Simulated 很好理解, Autonomous 和 Authority 其实有很多需要注意的地方。
先说 Authority , Authority 对象并不必然只在服务器出现、客户端代理。只有服务器 Authority 的 Actor ,开启了 Replicate ,并且通过了 Relevant 的验证(简单理解为视野,不在视野里的不告诉你),才会自动注册到其他客户端里。
Authority 简单点说,一个 Actor ,在哪个终端 Spawn ,就在哪个终端有 Authority 。
- 比如客户端临时创建了一个纯表现的 Actor ,那么这个 Actor 在这个客户端上就拥有 Authority 权限。
- 但是,当然,这种 Actor ,其他客户端都不知道。
- 服务器创建一个 Actor ,不 Replicate ,这个 Actor 就只有在服务器有 Authority ,但是在任何客户端都没有代理。
当然这些特殊的 Actor 对于联机而言,都没有太大意义。但是对于网络部份的一些具体环节和流程,可能会有一些影响,在写扩展的时候,需要小心。
RPC
RPC 是 Remote Procedure Call 的缩写,远程过程调用。感觉上就是个语法糖,不用再自己封装消息结构、分析消息ID了。表面上就是调用一个方法(比如: Foo ),消息Biu地就发给远端,远端收到这个包后,才真正执行这个方法的真正代码段(在虚幻里,就是后缀 Implementation 的那个方法: Foo_Implementation )。
RPC 机制需要注意的一点是,按理说,它应该是跟 Role 有关的,但是实际上它跟 NetMode 的关系更大。举个例子,正常情况下,在服务器 Spawn 一个 Actor 并 Replicate ,那么它在客户端有代理,这种情况下,客户端调用标记 UFUNCTION(server) 的方法, RPC 会自动通知把这个封装为一个消息,发送给服务器的那个真身,并执行。同样的道理,服务器调用标记 UFUNCTION(client) 的方法, RPC 也会自动把消息发给客户端的代理,并执行。
但是,在客户端 Spawn 一个 Actor ,会怎样呢?如前所述,这样的一个 Actor ,在客户端是具备 Authority 的,在服务器则是根本不存在的。这样我们预期可能是,将其视为某种单机性质的 Actor ,客户端调用 UFUNCTION(server) 的时候,事实上执行的还是客户端这个对象的这个方法,不再走打包等过程。
然而实际上不是,还是会打包、发送,然而发现服务器不存在对等对象,于是报 warning。这实际上是 Actor::GetFunctionCallspace 的策略决定的。在我们列举的这种情况下,它发现自身的 NetMode 是客户端,而期望运行的方法标记了 Server ,于是就直接认定这个方法在Server上执行了,而没有判断当前这个 Actor 是不是在客户端具备 Authority !……
Replicate
Replicate 相对就简单了,这个是单向的,只可能从服务器的 Authority 对象,把数据刷新给各个客户端的对等对象。 RPC 方法的调用时机之类的,需要自己去管理, Replicate 则有许多可以设定的项,可以决定更新频率、何时更新、何时取消更新等等。更新后,还可以顺带发一个 RepNotify 给你,方便做一些数据获取后的操作。做数据刷新时很方便。
如果没有这套机制,就得自己在 Tick 里各种 RPC ,自己做各种频率、权限验证等策略,有 Replicate 则方便很多。
Keywords
UnrealEngine
Net
RPC
NetRole
NetMode