角色移动组件

角色移动组件的详细说明

Windows
MacOS
Linux

使用“CharacterMovementComponent”的角色自动内置了客户端-服务器网络。下面是网络游戏中玩家移动预测、复制和修正如何通过 “CharacterMovementComponent”为各个帧工作的原理。

  • 调用“TickComponent”函数

  • 计算了帧的加速度和旋转变化

  • 调用“PerformMovement”(针对本地控制的角色)或“ReplicateMoveToServer”(针对网络客户端)

“ReplicateMoveToServer”保存移动(到待定的移动列表中),在本地调用“PerformMovement”,然后通过调用复制的函数“ServerMove” 将移动复制到服务器。“ServerMove”接收移动参数,包括客户端的最终位置和时间戳。它在服务器上执行,在服务器上解码移动参数并 导致适当的移动发生。然后它查看结果位置,并计算自最后一次客户端响应以来的时间, 以及客户端声明的位置与服务器确定的位置之间的差异。如果差异足够大,服务器调用“ClientAdjustPosition”, 它将复制到客户端并传递校正的位置。

当客户端再次调用“TickComponent”时,如果收到来自服务器的校正,客户端将在调用“PerformMovement”之前调用“ClientUpdatePosition” 。此过程将重演在服务器调整移动的时间戳之后发生的待定移动列表中的所有移动。

角色移动和模拟代理

到目前为止,描述的“CharacterMovementComponent”中的联网方法只处理连接到权威服务器的单个客户端详细信息。远程计算机上人工智能控制的 角色和玩家被视为“模拟代理”,它们的代码路径略有不同。

远程控制角色的移动通常使用普通的“PerformMovement”代码在服务器(在这方面的权威)上更新。Actor状态数据 (如位置、旋转和速度)和其他特定于角色的信息(如跳跃、蹲伏)通过正常的复制机制复制到其他机器, 这意味着它们通常不会像本地控制的角色那样逐帧接收更新。为了提供这些角色的更流畅的视图,客户端机器将 为模拟代理运行每一帧的模拟更新,直到服务器发来的新的、权威的数据到达。虽然人工智能控制的角色通常直接在服务器上运行, 但远程玩家将向服务器发送自己的本地更新,服务器随后对该玩家进行完整的移动更新,并定期将该数据复制到所有其他玩家。

根据复制的状态模拟预期移动结果,用合理的预测“填补空白”,直到下一个服务器权威更新到来。一旦 下一次更新到来,它将有效地重置本地模拟,并基于最新信息启动新的模拟。

模拟代理的大部分移动更新是在“UCharacterMovementComponent::SimulateMovement”和“MoveSmooth”中执行的。“MoveSmooth”是 完整移动模式更新的精简版,适用于各种移动模式(步行、飞行等),运行起来更便宜,也更简单。

模拟代理平滑

在角色只是向前移动的情况下,模拟更新很可能与下一个复制更新非常匹配, 因为沿直线移动非常容易预测。即使在撞到世界场景中的静态墙壁的情况下,偏转和后续更新也可能有很高的模拟精度。

然而,在某些情况下,基于先前复制的状态快照的本地模拟肯定会偏离实际的正确位置,特别是在使用人工控制角色的情况下。 考虑一个复制的状态,它表示一个角色以一定的速度移动。在等待下一次更新时,模拟代理将继续以这个速度移动。 但是,远程角色可能在发送速度更新后立即停止移动。本地模拟无法知道这一点, 并且会做出错误的预测,直到下一次服务器更新到来。

为了避免在接收到服务器更新时在模拟代理的位置出现可视化的“弹出窗口(pop)”,我们使用函数“UCharacterMovementComponent::SmoothClientPosition”使角色的可视化表示的位置变得平滑 。默认情况下,这将应用一个简单的平滑函数以在特定的时间内到达目标目的地(由客户端网络数据 上的“SmoothNetUpdateTime”设置)。

调试CharacterMovementComponent网络

有一些有用的工具可以调试和分析角色网络。通常,您要做的第一件事就是在行为不正常的客户端控制台中输入“p.NetShowCorrections 1” (这只适用于非发布版本)。在服务器上启用此功能也可能很有用。这使得在客户端上接收(或在服务器上发送)网络更正时, 通过登录到输出控制台并在“正确”位置(绿色)绘制一个碰撞形状和 “不正确”位置(红色),可以查看网络更正。在客户端上,“正确”的位置是服务器作为更正发送下来的位置,而“错误”的位置是服务器上被判断为 超出误差容忍度的本地位置。在服务器上也是类似理念 -“正确”的服务器位置用绿色表示,而“错误”的接收到的客户端位置用红色 表示。变量“p.NetCorrectionLifetime”控制调试可视化在消失之前在世界场景中存在的时间(以秒为单位)。

诊断问题的另一种有用方法是打开由CharacterMovement网络移动函数发送数据的一些日志记录。控制台命令 “log LogNetPlayerMovement Verbose”允许记录角色移动数据的每一次发送和接收,包括位置、旋转和加速度。这可能有助于解释 为什么会发生错误更正,比如位置只在客户端上更新,因为位置更改没有复制到服务器。

移动速度破解防护(网络游戏)

网络游戏往往是用户试图通过漏洞获取不公平优势的目标。一个常见的、游戏通用的漏洞使用作弊软件来加速 游戏客户端上的时间流逝。使用未经修改的CharacterMovementComponent功能的游戏易受此漏洞影响,导致作弊者能够以更高的速度 移动。为了防止这种漏洞利用,我们在“CharacterMovementComponent”中添加了检测和解析时间差异的功能。

检测

时间差异检测的工作原理是让服务器比较来自客户端的“ServerMove”RPC(远程过程调用)与 服务器上已传递的已知(和可信)时间之间的报告时间差。这个差异保留为一个运行的总数,一旦总数超过用户可配置的阈值, 就会采取解决措施。

解决

所采用的解决方法是覆盖来自客户端的未来“ServerMove”RPC的时间戳,以匹配服务器的时间。角色的移动数据 (位置、旋转、加速度等)在RPC之间的部分差异可以被减去,以“偿还”时间差异,直到客户端重新 与服务器同步。

配置

默认情况下,时间差异检测和解决是禁用的。可以配置“GameNetworkManager”中的以下变量,它们的默认值可以在BaseGame.ini中 找到。可以在特定于项目的游戏配置文件中覆盖这些值。

变量名称(Variable Name)

效果(Effect)

bMovementTimeDiscrepancyDetection

启用检测。检测到后,将触发警告日志。如果启用解决,也将应用检测。

bMovementTimeDiscrepancyResolution

启用解决。如果检测到足够的差异,客户端将被更正并“偿还”时间。

MovementTimeDiscrepancyMaxTimeMargin

在触发检测/解决之前,客户端能够提前于服务器预期游戏时间的最长时间。

MovementTimeDiscrepancyMinTimeMargin

在触发检测/解决之前,客户端能够落后于服务器预期游戏时间的最长时间。

MovementTimeDiscrepancyResolutionRate

在解决期间“偿还”时间的比率。默认为100%,这意味着客户端在偿还债务之前不能移动。

MovementTimeDiscrepancyDriftAllowance

客户端和服务器之间允许的时钟偏离(每秒允许的百分比)。这有助于防止突发数据包丢失或性能故障触发误报。

bMovementTimeDiscrepancyForceCorrectionsDuringResolution

在解决期间是否强制客户端更新。这对于具有宽松的移动误差容忍度或启用了ClientAuthorativePosition的项目是必需的。

这些设置可能需要调整以适应您的特定项目。基本测试可以通过在客户端上使用“slomo”作弊来完成。变量“p.DebugTimeDiscrepancy”将激活 服务器上的时间差异日志记录。

高级主题:为角色移动添加新的移动能力

有几种方法可以给角色添加新的移动能力。举个例子,我们可以赋予一个角色传送的能力。让我们假设只要目的地没有障碍物, 这个能力可以让角色在按下T键时向前移动10米。让我们进一步假设这种能力需要在网络游戏中 发挥作用。

方法1:仅在客户端上执行

一种方法可能是检查障碍物,然后在目的地通畅无阻的情况下将角色直接向前移动10米。这并不考虑联网, 只是在客户端上执行。

结果:在网络游戏中失效。本地客户端将向前传送,但随后将快速回传到其起始位置。

分析:这只是一个基线,用来确定我们的代码正在运行。它可以用于非网络游戏。然而,由于它没有考虑到联网, 它将在网络游戏中失效。服务器不了解这种能力,因此它使用客户端角色的位置、旋转和加速度来确定客户端 应该在哪里结束。就服务器而言,角色的预期位置和客户端给出的位置之间的10米差异是 客户端上发生的错误。服务器更正错误,客户端在本地应用服务器的更正,取消传送。

方法2:仅服务器RPC

网络游戏最简单的工作方法是让服务器处理该能力,然后将结果通知客户端。为此,我们将设置一个标记为“Reliable”和“Server”的“UFUNCTION”, 当从客户端触发时,它将执行传送代码。

结果:在网络游戏中可发挥作用。然而,它的工作方式存在一些问题。对于使用该能力的客户端来说,将会有明显的延迟, 而且移动将看起来是平滑的,而不是瞬间的位置变化。

首先,对于使用该能力的玩家来说,会有明显的延迟, 因为来自客户端的命令需要从服务器发送,在那里执行,然后在客户端知道它已实际发生之前发送回来。第二,平滑代码将 使传送感觉像是一个平滑的移动,它应该像是一个瞬间的位置变化。第三,服务器上的玩家移动会被视为是一种更正, 但事实并非如此,在尝试调试游戏的联网运行时,服务器的玩家移动会掩盖真实的更正。

分析:这是可行的,但远非理想状态。当玩家按T键传送时,命令被发送到服务器,但本地没有发生任何事情。这可能会让玩家 感觉控制反映迟钝,玩家不会立即知道传送命令是否有效。当服务器接收到命令后, 它移动角色,但不告诉客户端。在下一次客户端发送更新时,服务器将看到客户端报告的位置在10米之外, 并将发送一个更正。该更正将导致客户端(平滑地)移动到服务器的预期位置,有效地导致传送成功,尽管看起来不太正确。如果我们将“p.NetShowCorrections”设置为1, 我们将收到通知这是一个网络更正。另外,如果我们想用粒子或者声音效果来完善这个能力, 这些效果将不会正确显示,因为实际的能力只在服务器上运行。尽管客户端知道何时尝试运行该能力(并将命令发送到服务器), 但是没有关于该功能成功或失败的报告,只有稍后显示的更正。

方法3:服务器RPC和本地触发器

用这种方法,客户端在本地执行传送,然后还调用服务器的传送RPC。

结果:在网络游戏中可行,存在极少出现但可能较为严重的问题。

分析:这种方法试图弥补前一次尝试的缺陷,即本地移动的延迟以及移动是由于服务器的纠正, 而不是能力造成的事实。我们也得到了传送的全部功能,比如声音和粒子效果,因为这个能力现在正在客户端上运行。这比之前的方法运行地更好, 但有一些重要的注意事项,并且仍然可能在真实的网络环境中中断。主要的问题是,如果客户端的时间被更正到传送触发之前, 该客户端在重复其待定的移动列表时将不知道重新触发传送,并且传送在客户端上似乎已经丢失。这 将使游戏在某些情况下令人感觉反应迟钝,尤其是在糟糕的网络条件下。

方法4:CharacterMovementComponent能力实现

用这种方法,我们将传送能力的知识添加到“CharacterMovementComponent”的子类中。这个版本在网络上很稳定。

结果:在网络游戏中可行,实施时有一些注意事项。

分析:这需要更多的背景知识:如前所述,使用“CharacterMovementComponent”的角色在一个待定的移动列表中排列输入的结果。列表中的每个移动 都记录了帧移动开始时的状态,比如位置、旋转、加速度(通常是玩家输入的结果)、跳跃状态,等等。当 移动从客户端发送到服务器时,我们保留这些移动,并在服务器确认它们已经完成时删除旧的移动。在服务器更正的情况下, 客户端知道何时发生了更正,并可以“重播”在该时间点之后所做的所有操作。这很有帮助,因为 由于已经过更正时间点的角色移动会在更正时间点上被重新应用,玩家可能甚至没有注意到有更正,这也意味着更正后触发的能力 将被重新应用。特殊效果可以通过选中“bReplayingMoves”参数来管理,这样它们在这种情况下就不会重复播放, 就像在“UCharacterMovementComponent::DoJump”函数中看到的那样。

传送能力本身将以类似于“CharacterMovementComponent”中的跳跃能力的方式被添加。我们需要指出何时触发了此能力, 并在服务器上正确地处理它。我们仍然会在本地执行它,这样游戏才会快速响应。在客户端和服务器之间发送和接收数据是作为现有网络的一部分自动完成的, 我们只需要打包和解包数据。一旦我们从“UCharacterMovementComponent”中创建了子类,我们就可以覆盖“AllocateNewMove” 来创建我们自己的“FSavedMove_Character”版本,这就是我们将要存储在待定移动列表中的内容。我们的“FSavedMove_Character”版本将会有一些被覆盖方法。 “GetCompressedFlags”将被覆盖,并使用一个新的标记指示传送能力的触发。“UpdateFromCompressedFlags”也将被更新,以解包我们新的传送标记, 并在服务器端触发该能力。这种方法将允许在网络游戏中创建稳定的移动能力。

欢迎来到全新虚幻引擎4文档站!

我们正在努力开发新功能,包括反馈系统,以便您能对我们的工作作出评价。但它目前还未正式上线。如果您对此页面有任何意见与在使用中遭遇任何问题,请前往文档反馈论坛告知我们。

新系统上线运行后,我们会及时通知您的。

发表反馈意见