什么是网络同步
多人游戏里面需要把某个玩家操作的结果通知给其玩家,这个通知的过程就是同步,再放到局域网或者广域网中进行,就是网络同步了。
中介转发
中介转发就是客户端/服务器模式(C/S),添加一个服务器作为中介节点,每台客户端只与服务器建立连接,客户端与客户端彼此独立,服务器负责转发消息。这种架构的复杂度仅为O(N),维护起来也方便。
帧同步与状态同步
帧同步是服务器将某个玩家的输入直接转发给其他玩家,自己不做处理。理论上所有客户端都以相同的初始状态开始,只要收到的输入相同,那么每时每刻的状态都会是相同的。
状态同步是服务器只同步影响游戏功能的某些重要状态变量,并且这些重要变量是在服务器运算出来的或者至少校验过的,客户端拿到这些状态变量后自行做本地的表现。
一般来说帧同步在实时性、节省流量方面比较好,状态同步则在安全性角度来说更胜一筹。具体选用哪种方案由具体游戏类型来决定,UE是在射击游戏基础上发展而来的,它默认的网络同步方案是状态同步,把决策权放在服务器上做,可以有效减少外挂,对于中途加入/断线重连也能天然支持。
使用UDP还是TCP
在UDP的基础上融合了TCP的优点,例如加入了乱序处理,以及对reliable的包丢失重传。可谓是各取所优,既保证了连接速度,也保证了可靠性。
Reliable,可靠性
- Reliable,不会丢失,立刻发出,适合重要的事件
- Unreliable,可能会丢失,适合表现相关的和不重要的事件
- 全部的远程调用都使用Reliable,可能会造成网络拥堵
- 尽量避免在循环里面进行远程调用和勾选Reliable选项
- 丢失重传:reliable的包在发端会保存一个备份,只有收到收端返回的Ack包确认后才会清掉,若收到的Ack包跳序则会触发重传。
- Packet乱序整理:因网络链路复杂性,到达包顺序可能与发端不一致,这时会进行整理,对于分包导致的不完整的部分(Partial)包也会等待重组。
如何同步重要状态变量
需要同步的重要状态变量都存在于Actor这个容器里。
Game Modes
Game Modes 的任务是定义和实现规则。
AGameModeBase
AGameModeBase 包含大量可覆盖的基础功能
| 函数/事件 | 目的 |
| PreLogin | 接受或拒绝尝试加入服务器的玩家。如它将 |
| PostLogin | 成功登录后调用。这是首个在 PlayerController 上安全调用复制函数之处。OnPostLogin 可在蓝图中实现,以添加额外的逻辑。 |
Pawn
Pawn类是一个代表你或者代表电脑的人工智能的游戏对象,它是可以在屏幕上控制的游戏对象。Pawn 是玩家或 AI 实体在游戏场景中的具化体现。如果它是被玩家控制的,我们通常称之为controller(控制器);如果它是被人工智能脚本控制的,我们通常称之为AI(Artificial Intelligence,人工智能),那些NPC(Non-player Characters,非玩家角色)就通常具有AI行为。
默认情况下,控制器(Controllers)和 Pawn 之间是一对一的关系;也就是说,每个控制器在某个时间点只能控制一个 Pawn。此外,在游戏期间生成的 Pawn 不会被控制器自动控制。
PlayerController(玩家控制器)
PlayerController(玩家控制器) 是Pawn和控制它的人类玩家间的接口。PlayerController本质上代表了人类玩家的意愿。
当设置PlayerController时,需要考虑的一个事情就是想在PlayerController中包含哪些功能及内容。可以在 Pawn 中处理所有输入, 尤其是不太复杂的情况下。但是,如果需求非常复杂,比如在一个游戏客户端上的多玩家、或实时地动态修改角色的功能,那么最好 PlayerController中处理输入。在这种情况中,PlayerController决定要干什么,然后将命令(比如"开始蹲伏"、"跳跃")发布给Pawn。
同时,某些情况下,则必须把输入处理或其他功能放到PlayerController中。PlayerController在整个游戏在过程中都是一直存在的,但是Pawn可能是临时存在的。 比如,在死亡竞技模式的游戏中,您可能死了又重生,所以您将获得一个新的Pawn,但是您的PlayerController都是一样的。在这个示例中,如果您将分数保存到您的Pawn上, 那么分数将会重置,但是如果您将分数保存到PlayerController上,它将不会重置。
RPC
RPC (远程过程调用)是在本地调用但在其他机器(不同于执行调用的机器)上远程执行的函数。
RPC 函数非常有用,可允许客户端或服务器通过网络连接相互发送消息。
这些功能的主要作用是执行那些不可靠的暂时性/修饰性游戏事件。这其中包括播放声音、生成粒子或产生其他临时效果 之类的事件,它们对于 Actor 的正常运作并不重要。在此之前,这些类型的事件往往要通过 Actor 属性进行复制。
服务器与客户端
UE4有两种服务器:
- Listen Server:方便进行局域网本地游戏,在本地机器上搭建服务器,此时本地机器既是服务器又是客户端。其接受远程客户端中的连接,且直接在服务器上拥有本地玩家。此模式通常用于临时合作和竞技多人游戏。
- Dedicated Server:独立服务器,在独立服务器上则不执行渲染任务,只承担服务器的相关职责。其接受远程客户端中的连接,但无本地玩家,因此为了高效运行,其将废弃图形、音效、输入和其他面向玩家的功能。此模式常用于需要更固定、安全和大型多人功能的游戏。此类游戏包括MMO、竞技MOBA,或快节奏网络射击游戏。
服务器是多人游戏实际发生的地方。客户端会远程控制其在服务器上各自拥有的 Pawn,发送过程调用以使其执行游戏操作。但服务器不会将视觉效果直接流送至客户端显示器。服务器会将游戏状态信息复制到各客户端,告知应存在的Actor、此类Actor的行为,以及不同变量应拥有的值。然后各客户端使用此信息,对服务器上正在发生的情况进行高度模拟。
服务器需要选择性地向各客户端发送信息,游戏编程时须指定要复制的信息和接收副本的机器。主要的难点在于选择应复制的信息及方式,以向所有玩家提供一致的游戏体验,同时需最小化信息复制量,尽可能减少网络带宽占用率。
游戏状态和流程一般是通过 GameMode 这一 actor 来驱动。只有服务器才包含此 actor 的有效复本(客户端不包含复本)。要向客户端传达该状态,可以使用 GameState actor 显示 GameMode actor 的重要状态。这个 GameState actor 被标记为复制到每个客户端。客户端将包含此 GameState actor 的一个近似复本,而且能使用这个 actor 作为引用,用于了解游戏的一般状态。
服务器和客户端的连接过程如下
- 客户端发送连接请求。
- 如果服务器接受连接,则发送当前地图。
- 服务器等待客户端加载此地图。
- 加载之后,服务器将在本地调用 AGameModeBase::PreLogin。这样可以使 GameMode 有机会拒绝连接
- 如果接受连接,服务器将调用 AGameModeBase::Login。该函数的作用是创建一个 PlayerController,可用于在今后复制到新连接的客户端。成功接收后,这个 PlayerController 将替代客户端的临时 PlayerController (之前被用作连接过程中的占位符)。此时将调用 APlayerController::BeginPlay。应当注意的是,在此 actor 上调用 RPC 函数尚存在安全风险。您应当等待 AGameModeBase::PostLogin 被调用完成
- 如果一切顺利,AGameModeBase::PostLogin 将被调用。这时,可以放心的让服务器在此 PlayerController 上开始调用 RPC 函数。
越好的网络环境和网络模型,客户端的游戏状态会越接近服务器。
客户端之间是没有直接连接的,必须通过服务器来进行客户端之间的交互,即:如果没有服务器告知,客户端之间是不知道互相之间的存在的。
游戏信息只准从服务器向客户端同步,客户端不能向服务器同步,就算客户端发信息给服务器,服务器也当成垃圾丢掉。
客户端向服务器发信息的方式只有调用RPC中的Server函数一种形式。
几种同步方式
复制是指在网络会话中的不同机器间复制游戏状态信息。若正确设置复制,将可同步不同机器的游戏实例。
1. Actor Replication
大多数 actor 复制操作都发生在 UNetDriver::ServerReplicateActors 内。在这里,服务器将收集所有与各个客户端相关的 actor,并对(已连接的)客户端发送自上次更新后出现变化的所有属性。
默认情况下,UE4不知道是否该对一个Actor执行复制操作,我们需要将Actor::bReplicates变量设置为true。