Choose your operating system:
Windows
macOS
Linux
虚幻引擎4提供了许多现成的多人功能,并且建立基于网络工作的基本蓝图游戏十分简单。 开始尝试 多人模式并不困难。成就基本多人功能的大多数逻辑得益于`Character`类中的联网支持以及第三人称模板项目所用的`CharacterMovementComponent`。
Gameplay框架评估
要向游戏添加多人功能,必须理解引擎提供的主要Gameplay类的角色,以及它们彼此的协作关系,尤其是它们在多人情境下如何工作:
-
GameInstance
-
GameMode
-
GameState
-
Pawn(以及从Pawn继承而来的Character)
-
PlayerController
-
PlayerState
请参阅 Gameplay框架 文档以了解更多信息,但在设计多人游戏模式时,至少需要记住以下提示:
-
GameInstance在引擎会话的持续时间内一直存在,意味着在引擎启动时创建,并在引擎关闭后才会销毁或更换。服务器和每个客户端上都存在一个独立的GameInstance,这些实例彼此不通信。由于GameInstance存在于游戏会话之外,并且是在关卡加载期间唯一存在的游戏结构体,因此非常适合于保存特定类型的持久数据,如终生玩家统计信息(如获胜总次数)、帐户信息(如特殊物品的锁定/解锁状态),甚至在像《虚幻竞技场》等竞技游戏中,用来保存用来切换的地图列表。
-
GameMode对象仅存在于服务器上。它通常存储客户端不需要明确知道的游戏信息。例如,如果游戏有"仅火箭发射装置"等特殊规则,客户端或许不需要知道这条规则,但在地图上随机产生武器时,服务器需要知道仅从"火箭发射装置"类别中选取。
-
GameState存在于服务器和客户端上,因此服务器可以在GameState上使用复制变量让所有客户端保持最新的游戏数据。与所有玩家和旁观者有关、而不是与任何一个特定玩家有关的信息最适合于GameState复制。例如,棒球游戏可以通过GameState复制每个团队的分数和当前局次。
-
每一台客户端上的每一个玩家存在一个PlayerController。它们在服务器和关联的客户机之间进行复制,但不会复制到其他客户端,因此在服务器上每个玩家都有PlayerController,但本地客户端只有本地玩家的PlayerController。客户端保持连接时存在PlayerController,PlayerController与Pawn关联,但不会像Pawn一样被销毁和重新产生。它们非常适用于在客户端和服务器之间传达信息,而不必将该信息复制到其他客户端,例如,服务器告知客户端对其迷你地图进行ping,以响应只有该玩家能检测到的游戏事件。
-
服务器和客户端上存在与游戏相连的每个玩家的PlayerState。这个类可以用于所有客户端感兴趣的复制属性,而不仅仅是所属客户端,如单个玩家在自由竞赛游戏中的当前分数。与PlayerController类似,它们与单个Pawn关联,但不会像Pawn那样被销毁和重新产生。
-
Pawn(包括Character)也存在于服务器和所有客户端上,可以包含复制变量和事件。决定对特定变量或事件使用PlayerController、PlayerState还是Pawn取决于具体情况,但务必要记住的是,只要所属玩家保持与游戏相连,且游戏没有加载新关卡,则PlayerController和PlayerState就保持不变,而`Pawn`则不然。例如,如果Pawn在游戏期间死亡,它通常会被销毁并替换为一个新Pawn,而PlayerController和PlayerState将持续存在,并在新Pawn产生后与新Pawn关联。因此,Pawn的生命值将存储在Pawn自身上,因为该信息特定于该Pawn的实际实例,应在该Pawn替换为新Pawn时重置。
Actor复制
UE4中的联网技术的核心是Actor复制。"复制"标志设置为true的Actor将自动从服务器同步到与该服务器相连的客户端。必须理解的一点是,Actor仅从服务器复制到客户端,而不能从客户端复制到服务器。当然,客户端仍需要能够向服务器发送数据,它们通过复制的"在服务上运行"事件来实现。
请参阅概 在蓝图中同步 Actor 指南以了解具体示例的逐步演示,以及 Actor 的复制 文档。
权限
对于场景中的每个Actor,会将其中一个连接的玩家视为拥有对该Actor的权限。对于服务器上存在的每个Actor,服务器拥有对该Actor的权限,包括所有复制的Actor。因此, 拥有权限 函数在客户端上运行,而目标是复制到客户端的Actor时,将返回false。你还可以使用 切换拥有权限 便捷宏,作为针对复制Actor中不同服务器和客户端行为进行分支的快捷方法。
变量
在Actor上的变量的细节面板中,有一个 复制(Replication) 下拉列表,让你可以控制如何复制变量。
选项 |
说明 |
---|---|
无(None) |
这是新变量的默认值,表示不通过网络向客户端发送值。 |
复制(Replicated) |
服务器复制该Actor时,它会将该变量发送到客户端。接收客户端上的变量值将自动更新,因此下一次访问该客户端时,它会反映服务器上的值。当然,在真实网络上游戏时,更新会有所延迟,具体取决于网络的延迟时间。请记住,复制的变量仅沿一个方向传递,从服务器到客户端!要从客户端向服务器发送数据,请参见"事件"部分。 |
复制通知(RepNotify) |
变量将按照"复制"(Replicated)选项进行复制,此外,还将在蓝图中创建`OnRep_
|
引擎内置类中的许多变量已经启用了复制,因此许多功能在多人情境下会自动工作。
请参阅概 在蓝图中同步变量 指南以了解具体变量复制示例的逐步演示,以及 属性复制 文档。
产生和销毁
在服务器上产生复制的Actor时,这个信息会传达给客户端,他们也会自动产生该Actor的副本。但总体而言,由于不会从客户端复制到服务器,因此如果在客户端上产生了复制的Actor,则该Actor只会存在于产生这个Actor的客户端上。服务器和任何其他客户端都不会收到该Actor的副本。但产生客户端拥有对该Actor的权限。这对于某些Actor还是十分有用的,比如对Gameplay没有实际影响的装饰Actor,但对于会影响Gameplay并应当复制的Actor,最好确保在服务器上产生这类Actor。
销毁复制的Actor也属于类似情况:如果服务器销毁一个复制的Actor,则所有客户端也会销毁相应的副本。客户端可以随意销毁他们拥有权限的Actor,即,客户端自己产生的Actor,因为这些Actor不会复制到其他玩家,也不会影响其他玩家。如果客户端尝试销毁没有权限的Actor,这个销毁请求会被忽略。关键在于,产生Actor亦是如此:如果你需要销毁复制的Actor,则需要在服务器上销毁。
事件复制
在蓝图中,除了复制Actor及其变量,还可以跨客户端和服务器运行事件。
请参阅 在蓝图中使用远程调用函数 指南以了解具体示例的逐步演示,以及 RPC 文档。
你可能还会看到RPC(远程过程调用)一词。如果看到,只需注意蓝图中的复制事件基本上编译到引擎中的RPC,这是C++中的常用说法。
所有权
在使用多人时需要理解的一个重要概念是(尤其是在使用复制事件时),哪个连接 被视为特定Actor或组件的所有者 。就我们的目的而言,需要知道"在服务器上运行"事件只会从客户端拥有的Actor(或其组件)调用。通常,这表示你只能从以下Actor或其中某个Actor的组件发送"在服务器上运行"事件:
-
客户端本身的PlayerController;
-
客户端PlayerController支配的Pawn;或者
-
客户端的PlayerState。
同样,对于发送"在所属客户端上运行"事件的服务器,这些事件也应当在上述某个Actor上调用。否则,服务器不知道要将事件发送到哪个客户端,这样就只会在服务器上运行!
事件
在自定义事件的细节面板中,你可以设置如何复制事件。
选项 |
说明 |
---|---|
不复制(Not Replicated) |
这是默认值,表示该事件不进行复制。如果在客户端上调用,它仅在该客户端上运行,如果在服务器上调用,则仅在服务器上运行。 |
组播(Multicast) |
如果在服务器上调用组播事件,则该事件将复制到所有连接的客户端,无论拥有目标对象的是哪个连接。如果客户端调用组播事件,该事件会被视为未复制,并仅在调用该事件的客户端上运行。 |
在服务器上运行(Run on Server) |
如果从服务器调用该事件,则仅在该服务器上运行。如果从客户端调用,并且目标归该客户端所有,则事件将复制到服务器并在服务器上运行。"在服务器上运行"事件是客户端向服务器发送数据的主要方法。 |
在所属客户端上运行(Run on Owning Client) |
如果从服务器调用,则该事件将在拥有目标Actor的客户端上运行。由于服务器可以拥有Actor本身,因此"在所属客户端上运行"事件实际上可以在服务器上运行,而无论名称为何。如果从客户端调用,该事件会被视为未复制,并仅在调用该事件的客户端上运行。 |
以下各表描述了不同复制模式根据调用方式对事件运行位置的影响。
如果事件从服务器调用,假设左列是目标,则运行位置将是...
未复制 |
组播 |
在服务器上运行 |
在所属客户端上运行 |
|
---|---|---|---|---|
客户端拥有目标 |
服务器 |
服务器和所有客户端 |
服务器 |
目标的所属客户端 |
服务器拥有目标 |
服务器 |
服务器和所有客户端 |
服务器 |
服务器 |
无主目标 |
服务器 |
服务器和所有客户端 |
服务器 |
服务器 |
如果事件从客户端调用,假设左列是目标,则运行位置将是...
未复制 |
组播 |
在服务器上运行 |
在所属客户端上运行 |
|
---|---|---|---|---|
目标归调用客户端所有 |
调用客户端 |
调用客户端 |
服务器 |
调用客户端 |
目标归其他客户端所有 |
调用客户端 |
调用客户端 |
丢弃 |
调用客户端 |
服务器拥有目标 |
调用客户端 |
调用客户端 |
丢弃 |
调用客户端 |
无主目标 |
调用客户端 |
调用客户端 |
丢弃 |
调用客户端 |
从上表可见,任何从一个客户端调用并且没有设为"在服务器上运行"的事件都被视为没有复制。
从客户端向服务器发送复制的事件是唯一一种从客户端向服务器传达信息的方法,因为一般的Actor复制设计为仅从服务器到客户端。
此外,还需注意组播事件只能从服务器发送。虚幻采用客户端-服务器模型,因此客户端不会直接连接任何其他客户端,它们只是连接到服务器。因此,客户端无法直接向其他客户端发送组播事件,只能与服务器通信。但你可以使用两个复制事件来模拟这种行为:一个"在服务器上运行"事件和一个"组播"事件。"在服务器上运行"事件实现可以在需要时执行验证,然后调用组播事件。"组播"事件实现将执行你想要对所有连接玩家运行的逻辑。下图代表了不执行任何验证的示例:
"进行中加入"注意事项
在使用复制事件以传达游戏状态更改时需要记住的一点是它们如何与支持"进行中加入"的游戏交互。如果玩家在游戏进行期间加入游戏,则加入前发生的任何复制事件都不会对新玩家执行。重点是,如果你希望游戏正确地支持"进行中加入",通常最好通过复制的变量同步重要的Gameplay数据。经常用的一种模式是客户端在场景中执行一些操作,通过"在服务器上运行"事件将操作通知给服务器,然后在该事件的实现中,服务器根据该操作更新一些复制的变量。然后没有执行该操作的其他客户端仍会通过复制的变量看到操作结果。此外,任何在操作发生后在进行期间加入的客户端仍会看到场景的正确状态,因为它们从服务器获得了最新的复制变量值。如果服务器仅发送事件,则进行中加入的玩家不会知道之前执行的操作!
可靠性
对于任何复制的事件,你可以选择是 可靠 还是 不可靠 。
可靠事件保证可到达目标(假设遵守上述所有权规则),但为了兑现这个承诺,它们会使用更多带宽,还可能会存在延迟。尽可能频繁地尝试避免发送可靠事件,例如每个tick事件,因为引擎的内部可靠事件缓冲区可能会溢出,如果溢出,则关联的玩家会断开连接!
不可靠事件顾名思义,它们不能到达目标,比如网络上丢包,或者引擎确定有许多更高优先级的流量需要发送。因此,不可靠事件使用的带宽低于可靠事件,你可以更频繁地安全调用此类事件。