Choose your operating system:
Windows
macOS
Linux
角色移动组件 是一种 Actor 组件,提供封装的移动系统和类人 角色 的常见移动模式,包括行走、跌倒、游泳和飞行。角色移动组件还具备强大的网络gameplay整合。它的默认移动模式全都默认用于复制,并提供框架来帮助开发者创建自定义的联网移动。
角色移动基础知识
UCharacterMovementComponent
前附于 ACharacter
Actor类及其派生的所有 蓝图。
在其 TickComponent
函数期间,UCharacterMovementComponent
将调用 PerformMovement
,基于当前所用 移动模式 以及玩家的输入变量来计算世界场景中所需的加速度,通常在APlayerController
中以 control input 变量表示。一旦完成移动计算,UCharacterMovementComponent
将把最终移动应用于拥有的角色。
虽然 ACharacter
派生自APawn
,但角色并不只是增加了角色移动组件的Pawn。UCharacterMovementComponent
和 ACharacter
需要一同使用,因为 ACharacter
覆盖数个复制的变量和函数,专为在 UCharacterMovementComponent
中进行复制。
PerformMovement和移动物理效果
PerformMovement
函数负责游戏世界场景中的角色物理移动。在非联网游戏中,UCharacterMovementComponent
每次tick将直接调用一次 PerformMovement
。在联网游戏中,由专用函数为服务器和客户端调用 PerformMovement
,在玩家的本地机器上执行初始移动,或在远程机器上再现移动。
PerformMovement
处理以下状况:
应用外部物理效果,例如脉冲、力和重力。
根据动画根运动和 根运动源 计算移动。
调用
StartNewPhysics
,它基于角色使用的移动模式选择Phys*
函数。
每个移动模式都有各自的 Phys*
函数,负责计算速度和加速度。举例而言,PhysWalking
决定角色在地面上移动时的移动物理效果,而 PhysFalling
决定在空中移动时的移动物理效果。若要调试这些行为的具体细节,需深入探究这些函数。
若移动模式在一个tick内发生变化(例如角色开始跌倒或撞到某个对象),Phys*
函数会再次调用 StartNewPhysics
,在新移动模式中继续角色的运动。StartNewPhysics
和 Phys*
函数各自通过已发生的 StartNewPhysics
迭代的次数。参数 MaxSimulationIterations
是此递归所允许的最大次数。
移动复制摘要
UCharacterMovementComponent
使用其所有者的 网络角色 来确定移动的复制方式。三种网络角色如下:
网络角色 |
说明 |
---|---|
自主代理(Autonomous Proxy) |
角色在其 所属客户端 机器上,由玩家本地控制。 |
权威(Authority) |
角色存在于建立游戏的服务器上。 |
模拟代理(Simulated Proxy) |
角色存在于可查看远程控制角色的其他客户端上,无论角色受服务器AI控制,还是由不同客户端上的自主代理控制。 |
复制进程遵循 TickComponent
函数中的循环,每tick重复一次。角色执行移动时,为同步移动信息,网络游戏中所有不同机器上的副本会相互进行 远程进程调用 (RPC),不同网络角色使用相应的不同执行路径。
下表逐步概述此进程中 UCharacterMovementComponent
在各个机器上所执行的操作:
步骤 |
说明 |
|
---|---|---|
自主代理(所属玩家的客户端) |
||
1 |
所属客户端本地控制自主代理。 |
|
2 |
代理构建 |
|
3 |
类似的 |
|
权威Actor(服务器) |
||
4 |
服务器接收ServerMove并使用 |
|
5 |
服务器检查其在ServerMove后的位置是否与客户端报告的最终位置匹配。 |
|
6 |
若服务器和客户端的最终位置匹配,则向客户端发回信号,表示移动有效。否则将使用 ClientAdjustPosition RPC 发送矫正。 |
|
7 |
服务器复制 |
|
自主代理(所属玩家的客户端) |
||
8 |
若客户端收到ClientAdjustPosition,则复制服务器的移动,并使用它的 |
|
模拟代理(所有其他客户端) |
||
9 |
模拟代理直接应用复制的移动信息。网络平滑 提供最终运动的可视化清理。 |
此进程在网络游戏中的所有三类机器之间同步移动。控制给定角色的用户应极少受服务器干扰,并保持在本地控制角色的错觉,还应看到其他用户的角色执行近似于他们在自己机器上执行的动作。
此进程之所以复杂,是因为要在自主代理及其在服务器上的对应代理之间进行中介预测和修正,让玩家在控制角色时拥有尽可能流畅的体验。相比之下,模拟代理只需在服务器认为的适当位置保持最新状态即可。
复制角色移动详解
以下各节提供上述概述进程的详细逐步介绍。尽管大部分项目都不会覆盖 UCharacterMovementComponent
的行为,但若需要开发类似功能或需要找到执行修改之处,便可将此用作参考。
本节介绍如何复制角色的普通移动模式。但是根运动和基于另一个Actor的移动各自有不同的执行路径,遵循的步骤类似于本节中列出的步骤。
所属客户端上的本地移动
自主代理在 TickComponent
中本地处理移动,予以记录,然后发送到服务器以授权方式再现和应用。本节将详细介绍自主代理在每次tick中经历的进程。
编译客户端预测数据
自主代理编译名为 ClientPredictionData
的 FNetworkPredictionData_Client_Character
对象,其部分流程负责记录移动情况和处理来自服务器的矫正。其参数包括:
客户端与服务器通信时的时间戳
已保存或待定移动情况的列表
来自服务器矫正的已保存信息
指示如何应用矫正的标记
决定平滑行为的参数
ClientPredictionData
还包括与这些参数交互的效用函数。可在FNetworkPredictionData_Client_Character
的API参考中找到此对象信息和函数的完整列表。客户端执行本地移动、准备要发送到服务器的移动并处理矫正时,它的参数将被频繁引用和更改。
复制服务器矫正
处理玩家的输入或世界场景中的力之前,自主代理将调用 ClientUpdatePositionAfterServerUpdate
。这将检查服务器是否已向所属玩家发送矫正。若是,ClientPredictionData
中的变量 bUpdatePosition
将为true,且该角色将再现服务器通过客户端矫正进程发送的移动。欲了解服务器矫正的详情,参见"处理客户端错误和矫正"中的部分。
执行和记录移动
自主代理角色在 TickComponent
期间调用 ReplicateMoveToServer
,而非直接调用 PerformMovement
。此函数围绕 PerformMovement
提供必要的逻辑,以便在角色执行它时记录移动,然后将移动提交到服务器。FSavedMove_Character
结构记录自主代理在每个tick中如何开始和结束移动,之后通过ServerMove RPC将其数据的最小子集发送到服务器。其参数包括:
角色最终位置和旋转的相关信息
已采集哪些移动输入
角色拥有的速度和加速度
从 AnimMontages 采集的根运动信息
可在FSavedMove_Character
的API参考中查看此结构的完整参数列表。此信息使服务器能够再现玩家执行的移动,然后检查客户端的最终位置。
处理 PerformMovement
之后,ReplicateMoveToServer
函数利用名为 NewMove
的 FSavedMove_Character
结构在客户端预测数据中记录角色移动的结果,然后将其添加到名为 SavedMoves
的缓冲。此缓冲将保存的移动按最旧到最新的顺序排列,并用作队列,直至保存的移动可提交到服务器为止。提交之前,为缓解带宽压力,缓冲中的所有类似移动将合并成单个 FSavedMove_Character
。若移动正等着与即将到来的移动相结合,那么参数 PendingMove
将用于存储移动。
当这些移动被确认或 ACK 后,会从缓冲中移除。服务器可通过确认客户端位置有效来直接确认移动,客户端也可以在处理来自服务器的矫正时确认移动。最新确认的移动保存在 LastAckedMove
中,以供处理未来的矫正时使用。
向服务器提交移动
ReplicateMoveToServer
通过运行函数 CallServerMove
来完成,该函数接受队列中未获得服务器确认的最新和最旧移动。这将执行向服务器提交移动的最后准备工作,先尝试提交旧移动(若适用),然后调用正确的ServerMove函数来提交新移动的最终移动。最终ServerMove直接提交给 UCharacterMovementComponent
的所属角色,作为 不可靠的 服务器RPC。
ServerMove函数不可靠的原因有二:
正常游戏期间经常会调用ServerMove函数,如果它们被视为可靠,大量调用可靠函数可能会导致缓冲溢出,迫使所属玩家断开连接。
用于缓冲已保存移动的系统可确保传输进程中丢失的移动信息会被重新提交和计算。这为可靠函数提供了类似的安全保障,但却没有可靠RPC缓冲溢出的风险,并增加了一些规定来确保丢弃太旧的移动数据。
计算服务器上的移动
服务器不会定期tick移动来与游戏的tick循环同步,而是等待从自主代理处接收ServerMove调用,ServerMove_Implementation
处理服务器端的移动,重新构造客户端的移动并检查细微差异。本节将详细介绍ServerMove执行的流程。
本文档主要指的是 ServerMove
和 ServerMove_Implementation
,但实际存在多种类型的ServerMove调用,具体取决于队列中的信息类型。
编译服务器预测数据
角色移动组件的权威版本创建名为 ServerPredictionData
的 FNetworkPredictionData_Server_Character
对象,该对象在角色生命周期内存在。在 ServerMove_Implementation
期间,此对象将存储信息,供以后的进程再现所属客户端的移动。服务器接收数据时,此对象在后台被不断修改,参数包括:
用于计算服务器差量时间的时间戳
待定的客户端调整
与解决时间差异相关的标记
指示服务器确认还是矫正移动的标记
可在FNetworkPredictionData_Server_Character
的API参考指南中查看它的参数和函数的完整列表。
验证客户端时间戳
与ServerMove RPC一同发送的信息包括移动发生时的时间戳。若服务器时间戳和客户端时间戳差异太大,客户端时间戳将视为已过期,移动将被丢弃。否则该差异将标记为已解决,且 UCharacterMovementComponent
使用 ProcessClientTimeStampForTimeDiscrepancy
在下一步中创建差量时间的覆盖。
计算差量时间
虽然差量时间通常是通过追踪当前tick与上一个tick之间经过的时间来获取,但服务器上的角色并不使用 TickComponent
来计算移动。ServerMove_Implementation
调用 GetServerMoveDeltaTime
并在收到ServerMove后计算移动。若服务器预测数据被标记为尝试解决时间戳差异,它将使用 TimeDiscrepancyResolutionMoveDeltaOverride
。若无时间差异,则根据当前ServerMove RPC时间戳和最后一个ServerMove RPC时间戳之间的差异,使用服务器预测数据来创建差量时间。为多一层安全,执行这些计算时多半使用服务器时间戳而非客户端时间戳,以防止客户端加快本地游戏时间进行速度作弊。
计算移动
接下来,服务器使用来自ServerMove RPC的数据重新构造所属玩家控制器的控制旋转,然后调用函数 MoveAutonomous
来处理角色的加速、旋转和跳跃输入。
MoveAutonomous
使用 PerformMovement
函数,根据之前重新构造的数据和上一步中提供的差量时间来模拟角色的移动物理效果。服务器从获取ServerMove调用时自身角色副本所在的位置模拟运动,而非从客户端开始位置模拟。
若角色正在计算来自动画的根运动,MoveAutonomous还会使用提供的差量时间tick角色的动画姿势。所有动画事件都将相应触发。否则动画将正常tick。
处理客户端错误和矫正
服务器移动的工作原理是假设服务器和所属客户端在相同位置开始移动,且若服务器执行的移动与客户端报告的相同,则移动结束的位置也将相同。但若客户端的移动由于连接问题而丢失,或者客户端提交了错误数据,则两者的移动将在不同的位置结束,因此需要矫正。函数 ServerMoveHandleClientError
负责这些操作。
确定是否需要调整
频繁发送矫正会导致带宽紧张,并导致客户端过于频繁地重新模拟大量已保存的移动,因此要首先检查从 WithinUpdateDelayBounds
返回的值,查看移动间歇是否已超过最小时间量。若返回 false
,则不发出矫正。若返回true,则可运行其余进程。
接下来使用 ServerCheckClientError
查看服务器与客户端之间的误差是否大到需要矫正。若返回true,或 bForceClientUpdate
因故被设为true而强制矫正,则 ServerMoveHandleClientError
将继续执行余下进程。
可在 BaseGame.ini
中找到用于调整这两个操作的参数,并可在 DefaultGame.ini
中提供项目特定的覆盖。ClientErrorUpdateRateLimit
值决定着服务器向客户端发送错误矫正的最小延迟(单位为秒)MAXPOSITIONERRORSQUARED
值是网络游戏中可接受未矫正最大位置误差的平方。两者都可在配置文件的 [/Script/Engine.GameNetworkManager]
部分中找到。
若需要调整,服务器预测数据用从角色的服务器副本中采集的当前移动变量样本填充名为 PendingAdjustment
的 FClientAdjustment
,包括位置、旋转、速度和可能充当角色移动基础的对象。否则将 PendingAdjustment
的 bAckGoodMove
值设为 true
,将客户端的移动标记为有效。
发送客户端调整或确认移动
确认到客户端的移动的最后一步由 SendClientAdjustment
完成。此函数不作为 ServerMove_Implementation
的一部分,而是作为 UNetDriver::ServerReplicateActors
的一部分,在服务器端的tick结束时调用,并同样负责调用其他客户端调整RPC。调用 SendClientAdjustment
时,操作方式将取决于之前步骤中编译的预测数据的标记方式。
若服务器预测数据的 PendingAdjustment
的 bAckGoodMove
标记为 true
,则将调用 ClientAckGoodMove
RPC来确认移动,告知所属客户端机器上的自主代理该移动为有效。这将从所属客户端的 SavedMoves
缓冲中移除原始移动,并将其记录为 LastAckedMove
,用于编译未来的预测数据。
若 PendingAdjustment
的 bAckGoodMove
标记为false,则将调用客户端调整函数,将最终矫正发送到客户端。
在自主代理上接收客户端调整
客户端调整RPC包括 ClientAdjustPosition
、ClientAdjustRotation
、这两者在速度为零时发生的缩略版、以及这两者专用于根运动移动的版本。作为 SendClientAdjustment
的一部分,服务器可调用多个此类函数,具体取决于待矫正内容的性质和严重程度。每个此类函数都可告知 ClientPredictionData
在应用必要矫正后确认移动,且都会将 bUpdatePosition
标记为true。
然后在客户端的下一个 TickComponent
开始时使用 ClientUpdatePosition
应用最终矫正。
将移动复制到模拟代理
客户端机器上的角色(其所有者除外)为模拟代理,而非自主代理。由于模拟代理的唯一工作就是响应服务器,因此将移动从服务器复制到模拟代理的进程为高度简化。从服务器接收移动更新时不会模拟运动物理效果,而是将其位置、旋转和速度设为服务器想要的值,并使用一些额外的进程让移动更流畅、更可信。
存储复制的移动信息
Actor复制移动时,其不会直接复制自身的变换。所有Actor都维持名为 ReplicatedMovement
的复制变量,该变量使用结构FRepMovement
。
布尔 bReplicateMovement
由蓝图中的 Replicate Movement 变量表示,标记Actor将移动信息存储在此结构中,并将其复制到客户端。客户端收到 ReplicatedMovement
的更新时,RepNotify 函数 OnRep_ReplicatedMovement
将解压缩存储的移动数据,并相应地更新Actor的位置和速度。
在蓝图中无法访问 ReplicatedMovement
或其 OnRep,但可在C++中覆盖 OnRep_ReplicatedMovement
,还可在 GetLifetimeReplicatedProps
中覆盖 ReplicatedMovement
的复制条件。用户因此能够自定义移动复制在基于C++的Actor类中的行为。
在 ACharacter
中,仅为模拟代理复制 ReplicatedMovement
结构。自主代理上会忽略此结构,而使用服务器移动和客户端调整RPC来处理移动。
若角色用另一个Actor作为基础,则将使用 ReplicatedBasedMovement
,从而应用额外的逻辑来确保客户端是依据服务器而正确建立。若角色使用根运动系统,则忽略所有这些进程,而倾向于使用 RepRootMotion
。
在模拟代理上tick移动
当 UCharacterMovementComponent
在模拟代理上运行 TickComponent
时,其将调用 SimulatedTick
来处理模拟移动的逻辑。这不会执行上述复制移动。相反,SimulatedTick
会根据最近提供的复制移动数据继续移动。执行标准移动物理效果时,调用 SimulateMovement
函数,然后使用 SmoothClientPosition
执行最终的验证和网络平滑。
执行模拟移动
SimulateMovement
函数负责移动模拟代理角色。除了会被 SimulatedTick
调用,其还会被 OnRep_ReplicateMovement
调用。此函数执行以下进程:
调用所属角色的
GetReplicatedMovement
函数,以获取对ReplicatedMovement
的引用。执行安全检查,确保复制的移动数据有效,且客户端的基础被解析。
检查是否已收到网络更新。
从服务器应用通过
GetReplicatedMovementMode
获取的角色移动模式。重置有关网络更新的所有标记。
基于当前
MovementMode
和角色的当前状态信息执行模拟移动的逻辑。
与标准运动物理效果相比,模拟运动的逻辑高度简化,因此主要包含在 SimulateMovement
函数本身中,不会分解成更小的函数。但是,此函数仍负责更新角色的本地移动状态,包括应转换为哪个移动模式,角色是否已经降落至地面,以及应拥有的速度。这些信息可确保角色正确更新动画,让动作看起来十分准确。
网络平滑
若单纯通过复制角色的位置和旋转来复制移动,则角色看起来每隔数分钟就会瞬移一次。这是因为本地机器的渲染速率比网络发送数据速率更快。举例而言,客户端可能以240 Hz刷新率渲染显示器,而复制的移动可能仅以30 Hz发送。
网络平滑是使此运动平滑的进程,将角色从源位置逐渐向目标位置插入,而非立即将其对齐到目标位置。源位置由角色当前位置提供,而目标位置由客户端预测数据提供。内插本身在 SmoothClientPosition
中处理,使用NetworkSmoothingMode
确定应使用的内插类型。
特殊移动情况
以下章节包含常见特殊移动情况的信息,包括传送、自定义移动,以及可能在特殊技能中看到的代码驱动移动。
在多人游戏中传送角色
可调用 SetLocation 函数或 Teleport 蓝图节点在网络游戏中传送角色:
必须在服务器上调用。
若使用
SetLocation
函数,将bTeleport
变量设为true,使其将该移动视为传送。
若满足这些条件,移动会作为传送记录到服务器的预测数据和复制移动中,且所有客户端会将角色对齐到所需位置(而非应用平滑)来做出相应的响应。
使用自定义移动模式
移动模式 MOVE_Custom
会中止所有其他移动物理效果,可实现自定义移动逻辑,不受 UCharacterMovementComponent
正常进程的干扰。
UCharacterMovementComponent
通常不可蓝图化,因此蓝图中的自定义移动通常是使用 UpdateCustomMovement 事件直接在角色内实现。可使用 Custom Movement Mode 字节变量通过整数开关或自定义列举转换提供子模式。
UpdateCustomMovement
由 UCharacterMovementComponent
中的 PhysCustom
函数调用。函数 StartNewPhysics
、PhysCustom
和所有其他移动物理效果函数都是虚拟函数,因此若要在C++中创建自定义 UCharacterMovementComponent
,可直接将其覆盖。
通过根运动复制特殊移动情况
有时需要在短时间内直接控制角色的移动,例如在使用 Gameplay Ability System 创建的技能发动期间、或在动画驱动操作发生期间。虽然这在纯本地游戏中很容易做到,但复制的特殊移动情况需要使用根运动(通常指应用动画中的移动)。根运动系统也已调整为允许代码驱动的特殊移动情况。
无论 UCharacterMovementComponent
使用哪种运动模式,根运动始终优先于标准移动物理状态。完成根运动后将继续正常移动。
来自动画蒙太奇
根运动的大部分应用来自动画蒙太奇,而动画蒙太奇用于代码触发的一次性动画。根运动的这种用法会中止角色正在执行的其他移动,直至动画结束。角色从其骨架的根骨骼使用移动,并将其转换为场景空间的移动,使动画能够控制角色的移动方式。完成这一步后,角色恢复使用普通物理效果。
若角色处于下落移动模式,即使角色正在执行根运动,重力仍将作用于角色的Z轴移动。
在上述复制进程中,根运动信息由 FSavedMove_Character
结构采集,包括作为其来源的动画蒙太奇、角色在蒙太奇中的轨迹位置,以及角色移动本身的参数。
服务器和所属客户端上的自主代理不会检查播放的动画是否相同,因为这通常被视为是一种装饰功能。因此必须对游戏逻辑进行编程,确保所有动画蒙太奇都能在所有连接游戏的机器上正确触发。但是模拟代理拥有上述进程的并行进程,用于同步基于根运动的移动。
Gameplay Ability System插件将复制触发动画蒙太奇和根运动的技能,以此同步这些动画蒙太奇和根运动。
来自根运动源
有时需要手动控制角色的位置来处理特殊情况。举例而言,可能需要创造一种特殊技能,使角色跳到空中特定的高度,然后落到移动目标上。
在standalone游戏中,可以用 SetLocation
和 SetRotation
手动控制角色,但在网络游戏中,上述复制进程无法捕捉此运动,因此服务器将把客户端的最终位置视为错误并发出矫正。同时,来自动画蒙太奇的根运动仅遵循动画中预计算的运动。这意味着根运动通常无法从游戏世界获取实时信息,比如其他角色的位置,而且也不能使用游戏变量进行轻松微调。
根运动源 为程序员提供了一种手动控制角色根运动的方法。因此可以通过编程来控制角色的移动,同时还可以利用上述系统在联网期间处理根运动。
根运动源应应用于所属客户端上的自主代理。
要使用此系统,必须创建新的FRootMotionSource结构。不同类型的移动对应不同的FRootMotionSource变体。举例而言,FRootMotionSource_MoveToForce用于从开始位置到目标位置的直线移动,而FRootMotionSource_JumpForce遵循的是弹跳式弧线移动。创建适当的根运动源后,可使用所需源位置、目标位置和移动行为相关参数将其属性初始化。
函数 UCharacterMovementComponent::ApplyRootMotionSource
将把根运动源应用于角色,并返回一个可供之后引用的柄。根运动源本身不处理移动,而是由角色移动组件执行与所提供 FRootMotionSource
中参数一致的移动,以代替动画。最终这将添加到 FSavedMove_Character
结构中的 SavedRootMotion
,并在复制周期中采集,前提是 FRootMotionSource
应用于自主代理。
完成移动后,必须调用 UCharacterMovementComponent::RemoveRootMotionSource
,使用从 ApplyRootMotionSource
返回的柄将其移除。
Gameplay Ability System插件包含几个利用根运动源的技能任务,使技能可以执行复杂的程序化移动序列。基础范例请参见 AbilityTask_ApplyRootMotionMoveToForce
。