UE4网络同步

什么是网络同步

多人游戏里面需要把某个玩家操作的结果通知给其玩家,这个通知的过程就是同步,再放到局域网或者广域网中进行,就是网络同步了。

中介转发

中介转发就是客户端/服务器模式(C/S),添加一个服务器作为中介节点,每台客户端只与服务器建立连接,客户端与客户端彼此独立,服务器负责转发消息。这种架构的复杂度仅为O(N),维护起来也方便。

帧同步与状态同步

帧同步是服务器将某个玩家的输入直接转发给其他玩家,自己不做处理。理论上所有客户端都以相同的初始状态开始,只要收到的输入相同,那么每时每刻的状态都会是相同的。

状态同步是服务器只同步影响游戏功能的某些重要状态变量,并且这些重要变量是在服务器运算出来的或者至少校验过的,客户端拿到这些状态变量后自行做本地的表现。

一般来说帧同步实时性节省流量方面比较好,状态同步则在安全性角度来说更胜一筹。具体选用哪种方案由具体游戏类型来决定,UE是在射击游戏基础上发展而来的,它默认的网络同步方案是状态同步,把决策权放在服务器上做,可以有效减少外挂,对于中途加入/断线重连也能天然支持。

使用UDP还是TCP

在UDP的基础上融合了TCP的优点,例如加入了乱序处理,以及对reliable的包丢失重传。可谓是各取所优,既保证了连接速度,也保证了可靠性。

Reliable,可靠性

  1. Reliable,不会丢失,立刻发出,适合重要的事件
  2. Unreliable,可能会丢失,适合表现相关的和不重要的事件
  3. 全部的远程调用都使用Reliable,可能会造成网络拥堵
  4. 尽量避免在循环里面进行远程调用和勾选Reliable选项
  • 丢失重传:reliable的包在发端会保存一个备份,只有收到收端返回的Ack包确认后才会清掉,若收到的Ack包跳序则会触发重传。
  • Packet乱序整理:因网络链路复杂性,到达包顺序可能与发端不一致,这时会进行整理,对于分包导致的不完整的部分(Partial)包也会等待重组。

如何同步重要状态变量

需要同步的重要状态变量都存在于Actor这个容器里。

Game Modes

Game Modes 的任务是定义和实现规则。

AGameModeBase

 AGameModeBase 包含大量可覆盖的基础功能

函数/事件目的
PreLogin

接受或拒绝尝试加入服务器的玩家。如它将 ErrorMessage 设为一个非空字符串,会导致 Login 函数失败。PreLogin 在 Login 前调用,Login 调用前可能需要大量时间,加入的玩家需要下载游戏内容时尤其如此。

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 作为引用,用于了解游戏的一般状态。

服务器和客户端的连接过程如下

  1. 客户端发送连接请求。
  2. 如果服务器接受连接,则发送当前地图。
  3. 服务器等待客户端加载此地图。
  4. 加载之后,服务器将在本地调用 AGameModeBase::PreLogin。这样可以使 GameMode 有机会拒绝连接
  5. 如果接受连接,服务器将调用 AGameModeBase::Login。该函数的作用是创建一个 PlayerController,可用于在今后复制到新连接的客户端。成功接收后,这个 PlayerController 将替代客户端的临时 PlayerController (之前被用作连接过程中的占位符)。此时将调用 APlayerController::BeginPlay。应当注意的是,在此 actor 上调用 RPC 函数尚存在安全风险。您应当等待 AGameModeBase::PostLogin 被调用完成
  6. 如果一切顺利,AGameModeBase::PostLogin 将被调用。这时,可以放心的让服务器在此 PlayerController 上开始调用 RPC 函数。

越好的网络环境和网络模型,客户端的游戏状态会越接近服务器。

客户端之间是没有直接连接的,必须通过服务器来进行客户端之间的交互,即:如果没有服务器告知,客户端之间是不知道互相之间的存在的

游戏信息只准从服务器向客户端同步,客户端不能向服务器同步,就算客户端发信息给服务器,服务器也当成垃圾丢掉。

客户端向服务器发信息的方式只有调用RPC中的Server函数一种形式。

几种同步方式

复制是指在网络会话中的不同机器间复制游戏状态信息。若正确设置复制,将可同步不同机器的游戏实例。

1. Actor Replication

大多数 actor 复制操作都发生在 UNetDriver::ServerReplicateActors 内。在这里,服务器将收集所有与各个客户端相关的 actor,并对(已连接的)客户端发送自上次更新后出现变化的所有属性。

默认情况下,UE4不知道是否该对一个Actor执行复制操作,我们需要将Actor::bReplicates变量设置为true。


版权声明:本文为weixin_38051797原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。