关于 ue-mover
面向 UE 5.8 实验性 Mover 移动系统的开发技能,介绍配置、架构、移动模式、分层移动和模拟流程,适用于构建模块化 Actor 移动的开发者。
主要功能
- 01Mover 插件配置与模块依赖
- 02组件、后端和状态架构
- 03自定义移动模式与切换
- 04分层移动和瞬时效果
- 05模拟流程与网络后端
适用场景
- 01为非人形 Pawn 设计模块化移动
- 02添加攀爬等自定义移动模式
- 03实现冲刺、击退或瞬时移动
- 04排查 Mover 的模拟与状态流程
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ quodsoler/ unreal-engine-skills --skill ue-mover安装后在 Claude Code 中输入/ue-mover确认已加载。
附带文件references/mover-recipes.mdreferences/mover-reference.md
详细介绍
UE Mover
目标引擎:UE 5.8。以下 API 已根据 5.8 头文件验证;较旧的形式列在“已弃用 — 请勿使用”下。
Mover 是 Epic 推出的模块化移动系统,以 Mover 插件形式提供(在 5.8 中为实验性功能 — API 和数据格式可能会发生变化)。移动由数据驱动对象构成 — UBaseMovementMode、FLayeredMoveBase、FMovementModifierBase、FInstantMovementEffect、UBaseMovementModeTransition — 这些对象由 UMoverComponent 承载,并由外部 后端协调器 驱动,而不是由 TickComponent 驱动。Build.cs 模块名称:"Mover"(物理驱动移动还需 "ChaosMover",回滚后端还需 "NetworkPrediction",Mass 还需 "MoverMassIntegration")。公共头文件位于 Plugins/Experimental/Mover/Source/Mover/Public 下。日志类别为 LogMover;控制台命令前缀为 Mover. 和 mover.。
Context
如果 .agents/ue-project-context.md 存在,请阅读它(模块名称、约定、已启用插件、GAS/网络设置)。如果该文件缺失,请继续操作。
根据请求和代码库确定所属领域。只有当两种合理的理解会导致不同代码时才提问。
| 请求涉及…… | 查看 |
|---|---|
| 在 CMC 和 Mover 之间进行选择 | CMC 与 Mover |
| 启用插件、Build.cs、项目设置 | 设置 |
| 组件、模式映射、共享设置、各项的所属关系 | 架构 |
| 模拟 tick 的去向、子步进、混合 | Tick 流程 |
| 行走/攀爬/滑翔/滑索 — 新的移动类型 | 移动模式 |
| 冲刺、发射、击退、寻的、根运动 | 分层移动 |
| 蹲伏、姿态、设置切换 | 移动修饰器 |
| 瞬移、一次性冲量、强制切换模式 | 即时移动效果 |
| 自动模式切换规则 | 转换 |
| 将 Enhanced Input 转换为 Mover 输入命令 | 输入生成 |
| 自定义逐帧数据、位置、速度、持久化 | 输入和同步状态 |
| 后端、回滚、重模拟、必须复制的内容 | 后端和网络 |
| 读取速度/模式、绑定委托、游戏性标签 | 读取状态和事件 |
| 动作匹配、轨迹、AI 路径跟随、Mass | 集成点 |
完整模板见 mover-recipes.md;默认移动集目录、数据模型详情、实用工具库和调试内容见 mover-reference.md。
CMC vs Mover
Mover 并非可直接替换 UCharacterMovementComponent 的组件,并且 UMoverComponent 不要求使用 ACharacter — 唯一的硬性要求是根组件为 USceneComponent。此表与 ue-character-movement 中的表一致。
| 情况 | 使用 |
|---|---|
| 面向任意引擎支持平台的正式发行项目 | CMC |
现有 ACharacter 层级、动画和 AI 已连接到 CMC | CMC |
| 今天就必须保持稳定的联网游戏 | CMC |
| 非胶囊体、非人形移动对象(载具、无人机),但仍需要回滚网络功能 | Mover |
由数据驱动模式/修饰器而非 Phys* 子类构成的移动 | Mover |
| 使用 Chaos 在物理线程上模拟的移动 | Mover(ChaosMover) |
| 能够接受实验性 API 频繁变化的原型 | Mover |
概念对应关系:CMC 移动模式 → UBaseMovementMode 对象;根运动源 → FLayeredMoveBase;LaunchCharacter/TeleportTo → FInstantMovementEffect;蹲伏 → FMovementModifierBase;散落在 Phys* 中的模式切换条件 → UBaseMovementModeTransition。
不要在同一个 pawn 上同时添加这两个组件。 与 CMC 不同,Mover 状态无法从外部写入:没有可赋值的 Velocity 属性。只能通过模式、分层移动、修饰器和即时效果来改变运动。
设置
启用 Mover(在 5.8 中为实验性)。它会引入 NetworkPrediction(在 5.8 中为 Beta)、MotionWarping、PoseSearch、Water 和 ChaosVD。可选配套插件:MoverExamples(实验性 — 示例地图和 Pawn)、ChaosMover(实验性 — 物理后端)、MoverAnimNext(实验性 — UAF 桥接)、MoverIntegrations(实验性 — Mass)。
// MyGame.Build.cs
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "Mover", "GameplayTags" });
// Add "EnhancedInput" when the pawn binds input actions (the pawn recipe includes EnhancedInputComponent.h).
// Add "ChaosMover" only for physics-driven Mover actors.
// Add "MoverMassIntegration" only when driving Mover pawns from Mass.
项目设置 → 插件 → Network Prediction(UNetworkPredictionSettingsObject,config=NetworkPrediction),推荐从插件 README 中的设置开始:
| 设置 | 值 | 原因 |
|---|---|---|
PreferredTickingPolicy | ENetworkPredictionTickingPolicy::Fixed | 共享时间线,组回滚 |
SimulatedProxyNetworkLOD | ENetworkLOD::Interpolated | 其他玩家的 Pawn 很难准确地向前预测 |
bEnableFixedTickSmoothing | true | 固定模拟速率与可变渲染速率 |
启用平滑后,将组件的 SmoothingMode 设为 EMoverSmoothingMode::VisualComponentOffset(默认值)。单人游戏:使用 ENetworkPredictionTickingPolicy::Independent,或者将 BackendClass 设为 UMoverStandaloneLiaisonComponent,以完全移除 Network Prediction。
架构
AActor (any APawn or AActor with a USceneComponent root)
└─ UMoverComponent (UActorComponent, IMovementInterface)
├─ BackendClass -> a UActorComponent implementing IMoverBackendLiaisonInterface
├─ MovementModes : TMap<FName, TObjectPtr<UBaseMovementMode>> (Instanced)
├─ StartingMovementMode : FName
├─ Transitions : TArray<TObjectPtr<UBaseMovementModeTransition>> (global)
├─ PersistentSyncStateDataTypes : TArray<FMoverDataPersistence>
├─ InputProducer / InputProducers : objects implementing IMoverInputProducerInterface
├─ MovementMixer : UMovementMixer
└─ SharedSettings : auto-built from each mode's SharedSettingsClasses
UCharacterMoverComponent 是 UMoverComponent 的子类,增加了角色动作:Jump()、CanActorJump()、Crouch()、UnCrouch()、CanCrouch()、GetCrouchIntent()、IsCrouching()、IsFlying()、IsFalling()、IsAirborne()、IsOnGround()、IsSwimming()、IsSlopeSliding(),以及 OnStanceChanged 委托。用于人形 Pawn。
模式不拥有游戏状态。模式是共享的 Instanced UObject,其调节参数位于 UPROPERTY 字段和共享设置对象(IMovementSettingsInterface,例如 UCommonLegacyMovementSettings)中;每帧发生变化的所有内容都属于 FMoverSyncState。
// Inside UMyClimbingMode::OnRegistered, after calling Super. Full class in mover-recipes.md.
const UCommonLegacyMovementSettings* Settings = GetMoverComponent()->FindSharedSettings<UCommonLegacyMovementSettings>();
Tick 流程
后端联络组件驱动三个阶段(EMoverTickPhase::ProduceInput、SimulateMovement、ApplyState):
- ProduceInput —
UMoverComponent::ProduceInput(const int32 DeltaTimeMS, FMoverInputCmdContext* Cmd)会对InputProducers中的每个条目调用IMoverInputProducerInterface::ProduceInput。BeginPlay会将InputProducer(默认使用实现了该接口的所有者)以及所有实现该接口的所有者组件填入其中 — 5.8 的.cpp从不读取bGatherInputFromAllInputProducerComponents。仅在控制实例上运行;重模拟期间不会重新运行。 - SimulateMovement —
OnPreSimulate(TimeStep, StartingData)会触发OnPreSimulationTick,随后模拟运行子步循环:- 应用已排队的瞬时效果;
- 每个活动的
FLayeredMoveBase::GenerateMove都会生成一个FProposedMove; - 除非分层移动完全覆盖移动(
mover.perf.SkipGenerateMoveIfOverridden),否则活动模式的GenerateMove会运行; UMovementMixer::MixProposedMoves会按EMoveMixMode混合这些移动;ProcessGeneratedMovement(通过BindProcessGeneratedMovement绑定)拥有最终决定权;- 先评估模式自有的
Transitions,再评估全局Transitions;第一个命中的转换会调用Trigger并设置FMovementModeTickEndState::NextModeName,如果没有命中,则执行UBaseMovementMode::SimulationTick; FMovementModeTickEndState::RemainingMs会退还时间,从而在同一帧中产生另一个子步。 随后,OnPostSimulate(TimeStep, StartingData, EndingData)会触发OnPostMovement和OnPostSimulationTick。
- ApplyState — 联络组件会调用
FinalizeFrame(SyncState, AuxState)(或FinalizeUnchangedFrame()),以移动 Actor 并触发OnPostFinalize。
移动模式
UBaseMovementMode 是 UCLASS(MinimalAPI, Abstract, Blueprintable, BlueprintType, EditInlineNew, DefaultToInstanced)。逐字重写以下内容:
virtual void OnRegistered(const FName ModeName, const FMoverSimContext& SimContext) override;
virtual void OnUnregistered(const FMoverSimContext& SimContext) override;
virtual void Activate(const FMoverEventContext& Context, FName PrevModeName, const FMoverSimContext& SimContext, const FMoverTickStartData& StartState, FMoverSyncState* OutSyncState, FMoverAuxStateContext* OutAuxState) override;
virtual void Deactivate(const FMoverEventContext& Context, FName NextModeName, const FMoverSimContext& SimContext) override;
virtual void GenerateMove_Implementation(const FMoverSimContext& SimContext, const FMoverTickStartData& StartState, const FMoverTimeStep& TimeStep, FProposedMove& OutProposedMove) const override;
virtual void SimulationTick_Implementation(const FSimulationTickParams& Params, FMoverTickEndData& OutputState) override;
GenerateMove 是 const,用于决定需要进行什么运动;SimulationTick 则执行运动(扫掠、地面检查),并写入结束时的 FMoverDefaultSyncState。在 OnRegistered、OnUnregistered、Activate 和 Deactivate 中调用 Super::——基类实现会管理其拥有的转换,并触发 Blueprint 事件。只有当 GenerateMove/SimulationTick 不会访问场景组件或其他游戏线程状态时,才设置 bSupportsAsync = true。
默认集合:UWalkingMode、UFallingMode、UFlyingMode、USwimmingMode、UNavWalkingMode,以及异步变体 UAsyncWalkingMode、UAsyncFallingMode、UAsyncFlyingMode、UAsyncNavWalkingMode,还有简化版 USimpleWalkingMode、USimpleFlyingMode、USimpleSpringWalkingMode、USmoothWalkingMode。默认模式名称为 DefaultModeNames::Walking、::Falling、::Flying、::Swimming。完整目录请参阅 mover-reference.md。
在运行时注册并切换:
static void MyEnableClimbing(UMoverComponent& MoverComp)
{
MoverComp.AddMovementModeFromClass(TEXT("Climbing"), UMyClimbingMode::StaticClass());
MoverComp.QueueNextMode(TEXT("Climbing"), /*bShouldReenter=*/false); // takes effect next sim frame
}
static void MyDisableClimbing(UMoverComponent& MoverComp)
{
MoverComp.RemoveMovementMode(TEXT("Climbing"));
}
分层移动
FLayeredMoveBase 表示临时的叠加或覆盖运动:它只提出运动方案,由活动模式执行。共享字段:MixMode(EMoveMixMode)、Priority、DurationMs(>0 表示定时,0 表示单个刻步,<0 表示手动控制)、StartSimTimeMs、FinishVelocitySettings(FLayeredMoveFinishVelocitySettings)。逐字重写以下内容:
virtual bool GenerateMove(const FMoverTickStartData& StartState, const FMoverTimeStep& TimeStep, const UMoverComponent* MoverComp, UMoverBlackboard* SimBlackboard, FProposedMove& OutProposedMove) override;
virtual bool IsFinished(double CurrentSimTimeMs) const override;
virtual FLayeredMoveBase* Clone() const override;
virtual void NetSerialize(FArchive& Ar) override;
virtual UScriptStruct* GetScriptStruct() const override;
virtual FString ToSimpleString() const override;
virtual void AddReferencedObjects(class FReferenceCollector& Collector) override;
NetSerialize 必须先调用 Super::NetSerialize(Ar),然后再序列化自己的字段——遗漏的内容无法在校正后保留。排队时会原样存储你的 TSharedPtr,直到之后的模拟帧将其刷新(仅在复制同步状态时才会克隆),因此请先将其完整填充:
static void MyQueueDash(UMoverComponent& MoverComp, const FVector& DashDirection)
{
TSharedPtr<FLayeredMove_LinearVelocity> Dash = MakeShared<FLayeredMove_LinearVelocity>();
Dash->Velocity = DashDirection.GetSafeNormal() * 2000.f;
Dash->DurationMs = 200.f;
Dash->MixMode = EMoveMixMode::OverrideVelocity;
MoverComp.QueueLayeredMove(Dash);
}
内置移动:FLayeredMove_LinearVelocity、FLayeredMove_JumpImpulseOverDuration、FLayeredMove_JumpTo、FLayeredMove_MoveTo、FLayeredMove_MoveToDynamic、FLayeredMove_RadialImpulse、FLayeredMove_MultiJump、FLayeredMove_Launch、FLayeredMove_AnimRootMotion、FLayeredMove_AnimRootMotion_SimDriven。
另一种实例化形式正在开发中:在 ULayeredMoveLogic 中实现无状态逻辑,并在 FLayeredMoveInstancedData 中存储复制数据,通过 FLayeredMoveActivationParams 激活。使用 RegisterMove<T>() / K2_RegisterMove 注册逻辑类,然后调用 QueueLayeredMoveActivation(UMyMoveLogic::StaticClass()) 或 QueueLayeredMoveActivationWithContext(Params)。在实例化路径完成之前,新增 C++ 代码请优先使用 FLayeredMoveBase;两种形式会并存于 FMoverSyncState 中。
移动修饰器
FMovementModifierBase 会改变模拟的行为方式,但不会提出运动方案——例如姿态、设置切换、模式重映射。重写以下内容:
virtual void OnStart(UMoverComponent* MoverComp, const FMoverTimeStep& TimeStep, const FMoverSyncState& SyncState, const FMoverAuxStateContext& AuxState) override;
virtual void OnEnd(UMoverComponent* MoverComp, const FMoverTimeStep& TimeStep, const FMoverSyncState& SyncState, const FMoverAuxStateContext& AuxState) override;
virtual void OnPreMovement(UMoverComponent* MoverComp, const FMoverTimeStep& TimeStep) override;
virtual void OnPostMovement(UMoverComponent* MoverComp, const FMoverTimeStep& TimeStep, const FMoverSyncState& SyncState, const FMoverAuxStateContext& AuxState) override;
virtual FMovementModifierBase* Clone() const override;
virtual void NetSerialize(FArchive& Ar) override;
virtual UScriptStruct* GetScriptStruct() const override;
virtual bool Matches(const FMovementModifierBase* Other) const override;
static FMovementModifierHandle MyQueueStance(UMoverComponent& MoverComp)
{
return MoverComp.QueueMovementModifier(MakeShared<FStanceModifier>());
}
static void MyCancelStance(UMoverComponent& MoverComp, const FMovementModifierHandle& Handle)
{
if (MoverComp.IsModifierActiveOrQueued(Handle))
{
MoverComp.CancelModifierFromHandle(Handle);
}
}
Mover 假定同一时间只能有一个给定类型的修饰器;默认的 Matches 只比较类型。若要允许多个修饰器,请重写 Matches,使用也由 NetSerialize 写入的字段。FStanceModifier(包含 EStanceMode 和 UStanceSettings)是内置的蹲伏实现,由 UCharacterMoverComponent::Crouch() 驱动。
瞬时移动效果
FInstantMovementEffect 会在子步开始或结束时应用一次,不消耗时间。重写 virtual bool ApplyMovementEffect(FApplyMovementEffectParams& ApplyEffectParams, FMoverSyncState& OutputState) override;,并在更改了任何内容时返回 true。内置效果:FTeleportEffect、FAsyncTeleportEffect、FJumpImpulseEffect、FApplyVelocityEffect。
static void MyQueueTeleport(UMoverComponent& MoverComp, const FVector& Destination)
{
TSharedPtr<FTeleportEffect> Teleport = MakeShared<FTeleportEffect>();
Teleport->TargetLocation = Destination;
MoverComp.QueueInstantMovementEffect(Teleport);
}
ScheduleInstantMovementEffect / ScheduleLayeredMove 会延迟执行,以便每个端点都在同一帧运行它们;它们仅适用于 ChaosMover 的联网物理路径。默认使用 Queue* 形式。
转换
UBaseMovementModeTransition 根据状态决定模式切换。重写 virtual FTransitionEvalResult Evaluate_Implementation(const FSimulationTickParams& Params) const override;(返回 FTransitionEvalResult(NextModeName) 或 FTransitionEvalResult::NoTransition),并重写 virtual void Trigger_Implementation(const FSimulationTickParams& Params) override; 来执行副作用。将它放入某个模式的 Transitions 数组中,以执行模式范围内的检查;或放入组件的数组中,以执行全局检查。bAllowModeReentry 和 bFirstSubStepOnly 分别控制是否允许重新进入模式,以及子步作用范围。转换并非必需——QueueNextMode、FProposedMove::PreferredMode 和 FMovementModeTickEndState::NextModeName 也可切换模式。
输入生成
Mover 不会读取 Enhanced Input。先在游戏线程上缓存输入,再在 ProduceInput 中进行转换:
// AMyMoverPawn : public APawn, public IMoverInputProducerInterface.
// CachedMoveInputIntent, bJumpHeld and bJumpPressedThisFrame are members filled by Enhanced Input handlers.
void AMyMoverPawn::ProduceInput_Implementation(int32 SimTimeMs, FMoverInputCmdContext& InputCmdResult)
{
FCharacterDefaultInputs& CharacterInputs = InputCmdResult.InputCollection.FindOrAddMutableDataByType<FCharacterDefaultInputs>();
CharacterInputs.ControlRotation = GetControlRotation();
CharacterInputs.SetMoveInput(EMoveInputType::DirectionalIntent, CharacterInputs.ControlRotation.RotateVector(CachedMoveInputIntent));
CharacterInputs.OrientationIntent = CharacterInputs.GetMoveInput().GetSafeNormal();
CharacterInputs.bIsJumpPressed = bJumpHeld;
CharacterInputs.bIsJumpJustPressed = bJumpPressedThisFrame;
bJumpPressedThisFrame = false;
}
使用固定间隔的 Tick 时,多个渲染帧可能对应一个输入命令(或者一个帧对应多个 Tick),因此应累积输入,而不是采样。FCharacterDefaultInputs 还包含 SuggestedMovementMode、MovementBase/MovementBaseBoneName/bUsingMovementBase。FMoverAIInputs 为 AI 提供 RVOVelocityDelta。完整的 Pawn 示例见 mover-recipes.md。
输入和同步状态
输入命令和同步状态都是由 FMoverDataStructBase 派生结构体构成的动态集合(FMoverDataCollection),因此无需派生组件即可使用自定义数据。
| 类型 | 包含内容 |
|---|---|
FMoverInputCmdContext | InputCollection — 每帧由所有者编写 |
FMoverSyncState | MovementMode、LayeredMoves、LayeredMoveInstances、MovementModifiers、SyncStateCollection |
FMoverAuxStateContext | AuxStateCollection — 很少变化的模拟输入 |
FMoverDefaultSyncState | 位置、朝向、速度、AngularVelocityDegrees、MoveDirectionIntent、移动基底 |
FMoverTickStartData | 此 Tick 的 InputCmd、SyncState、AuxState |
FMoverTickEndData | SyncState、AuxState、MovementEndState、MoveRecord |
FProposedMove | PreferredMode、DirectionIntent、LinearVelocity、AngularVelocityDegrees、bHasDirIntent、MixMode |
FMoverTimeStep | ServerFrame、BaseSimTimeMs、StepMs、bIsResimulating、bIsFirstResimFrame |
每个 Mover Actor 都必须包含 FMoverDefaultSyncState。使用世界/基底空间访问器读取——GetLocation_WorldSpace()、GetVelocity_WorldSpace()、GetOrientation_WorldSpace()、GetAngularVelocityDegrees_WorldSpace()、GetTransform_WorldSpace()——并使用 SetTransforms_WorldSpace(Location, Orient, Velocity, AngularVelocityDegrees, Base, BaseBone) 写入。
void UMyClimbingMode::SimulationTick_Implementation(const FSimulationTickParams& Params, FMoverTickEndData& OutputState)
{
const FMoverDefaultSyncState* StartingSyncState = Params.StartState.SyncState.SyncStateCollection.FindDataByType<FMoverDefaultSyncState>();
check(StartingSyncState);
FMyClimbState& OutClimb = OutputState.SyncState.SyncStateCollection.FindOrAddMutableDataByType<FMyClimbState>();
OutClimb.SurfaceNormal = StartingSyncState->GetOrientation_WorldSpace().Vector();
}
自定义状态结构体必须重写 Clone、NetSerialize、GetScriptStruct、ToString、ShouldReconcile 和 Interpolate。凡是必须跨帧保留且不会被重新写入的数据,都应作为 FMoverDataPersistence 放入 PersistentSyncStateDataTypes。
UMoverBlackboard(GetSimBlackboard() / GetSimBlackboard_Mutable())会在网络状态之外缓存派生值——CommonBlackboard::LastFloorResult、LastWaterResult、LastFoundDynamicMovementBase、TimeSinceSupported。它正由支持回滚的黑板(GetRollbackBlackboardExternal())取代,因此始终应将查询结果视为可能缺失,并提供备用值。TryGet<T> 和 Set<T> 严格要求类型一致:存储和读取时必须使用相同类型(以 double 写入的值也必须以 double 读取)。
后端与网络
IMoverBackendLiaisonInterface(Backends/MoverBackendLiaison.h)定义了契约:GetCurrentSimTimeMs()、GetCurrentSimFrame()、IsAsync()、IsFixedDt()、ShouldResim()、待处理/呈现同步状态访问器,以及 OnSimulationRollback(NewSyncState, NewBaseTimeStep, PreRollbackTimeStep)。在 UMoverComponent::BackendClass 中设置实现该接口的组件类。
| 后端 | 适用场景 | 说明 |
|---|---|---|
UMoverNetworkPredictionLiaisonComponent | 联机或单机游戏 | Network Prediction(5.8 中为 Beta);固定或独立 Tick、回滚 + 重模拟 |
UMoverStandaloneLiaisonComponent | 单机游戏,非物理驱动移动 | 无 Network Prediction 开销 |
UChaosMoverBackendComponent | 物理驱动的 Pawn | ChaosMover(5.8 中为 Experimental);异步、固定 dt、Chaos Networked Physics |
网络模型:每个端点都会模拟共享时间线,客户端略微领先。客户端仅发送指定模拟帧的输入命令;服务器会缓冲这些命令、进行模拟并广播状态。客户端会进行比较,并决定是否回滚和重模拟——FMoverSyncState::ShouldReconcile 会比较 MovementMode、SyncStateCollection 和 MovementModifiers。
对代码的影响:
- 下一帧模拟读取的所有内容都必须包含在同步状态(或辅助状态)中,并且必须实现
NetSerialize。缓存在模式对象上的状态、在NetSerialize中跳过某个字段的分层移动,或黑板数据都不会在回滚时恢复。 - 重模拟期间会重新运行
GenerateMove/SimulationTick/ApplyMovementEffect。请确保它们具有确定性:不要使用未携带种子状态的 RNG,不要生成对象,也不要播放一次性音频或 VFX。使用FMoverTimeStep::bIsResimulating和FMoverEventContext::bIsCausedByRollback控制外观效果。 - 重模拟时不会重新运行
ProduceInput,并且它只会在控制实例上运行——瞄准辅助和锁定属于此处,移动逻辑则不属于此处。 - 模拟代理:优先使用
ENetworkLOD::Interpolated;其他玩家的前向预测在方向改变和跳跃时容易预测错误。 - Mover 不负责解决 GAS/移动同步问题;GAS 仍会独立复制。
有关 RPC 规则、网络角色、DOREPLIFETIME 和 Push Model,请参阅 ue-networking-replication。
读取状态与事件
static void MyReadMoverState(const UMoverComponent& MoverComp)
{
const FMoverSyncState& SyncState = MoverComp.GetSyncState();
const FName ModeName = MoverComp.GetMovementModeName();
const FVector Velocity = MoverComp.GetVelocity();
const FVector Intent = MoverComp.GetMovementIntent();
const FRotator TargetOrientation = MoverComp.GetTargetOrientation();
const bool bGrounded = MoverComp.HasGameplayTag(Mover_IsOnGround, /*bExactMatch=*/false);
FHitResult FloorHit;
const bool bHasFloor = MoverComp.TryGetFloorCheckHitResult(FloorHit);
UE_LOG(LogMyGame, Verbose, TEXT("%s: mode %s, speed %.1f, grounded %d, floor %d, intent %s, facing %s"),
*SyncState.MovementMode.ToString(), *ModeName.ToString(), Velocity.Size(),
bGrounded ? 1 : 0, bHasFloor ? 1 : 0, *Intent.ToString(), *TargetOrientation.ToString());
}
UMoverComponent 上的多播委托:OnPreSimulationTick(FMover_OnPreSimTick)、OnPostSimulationTick(FMover_OnPostSimTick)、OnPostMovement(FMover_OnPostMovement)、OnPostSimulationRollback(FMover_OnPostSimRollback)、OnMovementModeChanged(FMover_OnMovementModeChanged)、OnTeleportSucceeded、OnTeleportFailed、OnBasedMovementApplied、OnGameplayTagAdded、OnGameplayTagRemoved、OnMovementTransitionTriggered、OnPostFinalize,以及仅限 C++ 的 OnPostSimEventReceived。UCharacterMoverComponent 还添加了 OnStanceChanged。
在 MoverTypes.h 中声明的原生标签:Mover_IsOnGround、Mover_IsInAir、Mover_IsFalling、Mover_IsFlying、Mover_IsSwimming、Mover_IsCrouching、Mover_IsNavWalking、Mover_AnimRootMotion、Mover_SkipAnimRootMotion、Mover_SkipVerticalAnimRootMotion、Mover_DisableLanding。使用 AddGameplayTag / RemoveGameplayTag 添加或移除自定义标签;使用 HasGameplayTag 或 GetGameplayTags() 查询。
集成点
- 动画 —
GetPredictedTrajectory(FMoverPredictTrajectoryParams)为运动匹配返回TArray<FTrajectorySampleInfo>。FLayeredMove_AnimRootMotion和FLayeredMove_AnimRootMotion_SimDriven驱动根运动;ConvertLocalRootMotionToWorld与ProcessLocalRootMotionDelegate/ProcessWorldRootMotionDelegate让 Motion Warping 能够接入。MoverAnimNext(在 5.8 中为实验性功能)连接到 UAF。蒙太奇和动画图归属ue-animation-system。 - AI — 在 Mover 组件旁添加
UNavMoverComponent;它实现INavMovementInterface和IRVOAvoidanceInterface,而ConsumeNavMovementData(OutIntent, OutVelocity)会将数据传入ProduceInput。UNavWalkingMode/UAsyncNavWalkingMode会将对象投影到导航网格上。导航网格生成和查询归属ue-ai-navigation。 - Mass —
MoverIntegrations(在 5.8 中为实验性功能)提供UMoverMassAgentTrait、UMoverMassAgentOrientationSyncTrait和UMassMoverInputComponent(一个IMoverInputProducerInterface组件)。实体/处理器设计归属ue-mass-entity。 - **物理
** — 运动学模式使用UMovementUtils::TrySafeMoveUpdatedComponent和TryMoveToSlideAlongSurface进行扫掠;碰撞会通过HandleImpact(FMoverOnImpactParams&)进行处理。ChaosMover 则会在物理线程上驱动 Chaos 角色地面约束。通道、扫掠和响应归属ue-physics-collision`。
已弃用 — 请勿使用
| 请勿使用 | 在 5.8 中使用 | 来源 |
|---|---|---|
UBaseMovementMode::OnActivate() | Activate() | UE_DEPRECATED(5.6) in MovementMode.h |
UBaseMovementMode::OnDeactivate() | Deactivate() | UE_DEPRECATED(5.6) in MovementMode.h |
UBaseMovementMode::OnGenerateMove() | GenerateMove_Implementation() | UE_DEPRECATED(5.6) in MovementMode.h |
UBaseMovementMode::OnSimulationTick() | SimulationTick_Implementation() | UE_DEPRECATED(5.6) in MovementMode.h |
UBaseMovementModeTransition::OnEvaluate() | Evaluate_Implementation() | UE_DEPRECATED(5.6) in MovementModeTransition.h |
UBaseMovementModeTransition::OnTrigger() | Trigger_Implementation() | UE_DEPRECATED(5.6) in MovementModeTransition.h |
UMoverComponent::GetFutureTrajectory() | GetPredictedTrajectory() | UE_DEPRECATED(5.5) in MoverComponent.h |
UMoverComponent::HasValidCachedState() | 无 — 现在状态始终有效 | UE_DEPRECATED(5.6) in MoverComponent.h |
UMoverComponent::HasValidCachedInputCmd() | 无 — 现在输入命令始终有效 | UE_DEPRECATED(5.6) in MoverComponent.h |
SetTransforms_WorldSpace(Loc, Orient, Velocity, Base, BaseBone) | 接受 FVector WorldAngularVelocityDegrees 的重载 | UE_DEPRECATED(5.7) in MoverDataModelTypes.h |
UMovementUtils::ComputeAngularVelocity(FRotator, ...) | ComputeAngularVelocityDegrees() | UE_DEPRECATED(5.7) in MoveLibrary/MovementUtils.h |
UMovementUtils::ApplyAngularVelocity(FRotator, FRotator, float) | ApplyAngularVelocityToRotator() / ApplyAngularVelocityToQuat() | UE_DEPRECATED(5.7) in MoveLibrary/MovementUtils.h |
UMovementUtils::IsAngularVelocityZero(FRotator) | 角速度是一个 FVector — 直接对其进行测试 | UE_DEPRECATED(5.7) in MoveLibrary/MovementUtils.h |
UFloorQueryUtils::FindFloor / ComputeFloorDist(使用单独的浮点参数) | 接受 FFloorCheckSettings 的重载 | UE_DEPRECATED(5.8) in MoveLibrary/FloorQueryUtils.h |
UAirMovementUtils::IsValidLandingSpot / TryMoveToFallAlongSurface / TestFallingMoveAlongHitSurface(使用单独的浮点参数) | 接受 FFloorCheckSettings 的重载 | UE_DEPRECATED(5.8) in MoveLibrary/AirMovementUtils.h |
UGroundMovementUtils::TryMoveToStepUp / TestMoveToStepOver(使用单独的浮点参数) | 接受 FFloorCheckSettings 的重载 | UE_DEPRECATED(5.8) in MoveLibrary/GroundMovementUtils.h |
EBlackboardPersistencePolicy::NextFrameOnly | EBlackboardPersistencePolicy::ThroughNextFrame | UE_DEPRECATED(5.8) in MoveLibrary/RollbackBlackboard.h |
常见错误
将每帧状态存储在模式对象上: 模式是共享的实例化 UObject,并非每帧存储空间。在 SimulationTick 中写入的成员不会回滚,因此在存在延迟时会悄然失步。应将其放入 FMoverSyncState 内的 FMoverDataStructBase,并将类型添加到 PersistentSyncStateDataTypes。
忘记设置后端: UMoverComponent 不会通过 TickComponent 驱动更新。如果没有将 BackendClass 设置为实现 IMoverBackendLiaisonInterface 的组件,并且在使用 Network Prediction liaison 时没有启用 NetworkPrediction 插件,Actor 就根本不会移动。
在同一个 Pawn 上混用 CMC 和 Mover: 两个组件争用同一个根组件会导致抖动和校正。选择其中一个;UMoverComponent 不需要 ACharacter。
假设存在 CMC API:
// WRONG — no such members on UMoverComponent
MoverComp.Velocity = FVector(0.0, 0.0, 600.0);
MoverComp.SetMovementMode(MOVE_Flying);
// RIGHT
static void MyStartFlyingJump(UMoverComponent& MoverComp)
{
TSharedPtr<FLayeredMove_JumpImpulseOverDuration> Jump = MakeShared<FLayeredMove_JumpImpulseOverDuration>();
Jump->UpwardsSpeed = 600.f;
MoverComp.QueueLayeredMove(Jump);
MoverComp.QueueNextMode(DefaultModeNames::Flying);
}
在 NetSerialize 中遗漏字段: 只序列化部分字段的分层移动或状态结构体在校正后会以不同的值重放,导致只在存在延迟时才出现橡皮筋效应。应序列化移动逻辑读取的所有内容,并首先调用 Super::NetSerialize(Ar)。
将移动入队后又修改它: QueueLayeredMove、QueueMovementModifier 和 QueueInstantMovementEffect 会将你的 TSharedPtr 保留在队列中,由模拟稍后刷新(可能在另一个线程上),之后同步状态还会克隆它。之后所做的修改会与模拟产生竞争,可能被看到,也可能不会——入队后绝不要再触碰该结构体。
期待模式立即切换: QueueNextMode 会在下一模拟帧生效,而不是调用所在的帧。应通过 GetNextMovementModeName() 读取,而不是 GetMovementModeName()。
在模拟过程中生成或播放效果: 模式和分层移动代码会在重新模拟期间重新运行。应使用 !TimeStep.bIsResimulating 控制外观效果,或在 OnPostFinalize / OnMovementModeChanged 中执行。
两个相同类型的修改器: Mover 每种类型只保留一个,因为默认的 Matches 会比较结构体类型。如果需要多个,请使用 NetSerialize 同样会写入的字段重写 Matches。
盲目信任黑板: UMoverBlackboard 条目会在回滚和模式切换时失效。始终处理 TryGet 返回 false 的情况,绝不要以不同于存储时的类型读取值。
相关技能
ue-character-movement—UCharacterMovementComponent、ACharacter、FSavedMove_Character;Mover 旨在取代的系统ue-networking-replication— 网络角色、RPC、DOREPLIFETIME、推送模型、相关性;Mover 后端下方的复制层ue-animation-system— 由 Mover 轨迹驱动的蒙太奇、动画图、根运动和运动匹配ue-ai-navigation—UNavMoverComponent和UNavWalkingMode背后的导航网格生成、路径跟随和避让ue-physics-collision— Mover 扫掠和 ChaosMover 后端使用的碰撞通道、扫掠和 Chaos 刚体ue-gameplay-framework— Mover Actor 周边的 Pawn、控制器和占有关系配置ue-input-system— 传入ProduceInput的 Enhanced Input 操作和映射ue-mass-entity—MoverIntegrations特征背后的 Mass 实体和处理器