关于 ue-character-movement
面向 UE 5.8 开发者的角色移动 Skill,聚焦 ACharacter 与 UCharacterMovementComponent,涵盖移动模拟、自定义模式、网络预测及 5.8 移动基座变更。
主要功能
- 01移动模式与切换
- 02地面检测与跨步
- 03自定义移动模式
- 04网络预测与校正
- 05根运动与自定义重力
- 06UE 5.8 移动基座 API
适用场景
- 01实现沿墙跑或攀爬模式
- 02排查多人移动校正问题
- 03让角色随平台移动
- 04选择 CMC 或 Mover
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ quodsoler/ unreal-engine-skills --skill ue-character-movement安装后在 Claude Code 中输入/ue-character-movement确认已加载。
附带文件references/cmc-extension-patterns.mdreferences/movement-pipeline.md
详细介绍
UE 角色移动
目标引擎:UE 5.8。以下 API 已根据 5.8 头文件验证;旧形式列在“已弃用 — 请勿使用”下。
本技能涵盖 UCharacterMovementComponent(CMC)和 ACharacter:移动模式管线、地面检测与踏步、自定义移动模式、客户端预测与校正、根运动、自定义重力,以及 5.8 移动基座改版。这里的所有内容都属于 Engine 模块,无需插件,也无需在 "Engine" 之外添加额外的 Build.cs 条目。公共头文件:GameFramework/CharacterMovementComponent.h(该文件已包含 GameFramework/RootMotionSource.h、GameFramework/CharacterMovementReplication.h、CharacterMovementComponentAsync.h 和 Interfaces/MovementBaseInterface.h),GameFramework/Character.h。
上下文
如果 .agents/ue-project-context.md 文件存在,请阅读它(模块名称、约定、已启用插件、GAS/网络设置)。如果文件缺失,请继续操作。
根据请求和代码库确定所属领域。只有当两种合理解读会导致不同代码时才提问。
| 请求涉及…… | 前往 |
|---|---|
| 在 CMC 和 Mover 插件之间进行选择 | CMC 与 Mover |
| 类层次结构、移动模式、模式转换 | CMC 架构 |
| 站在升降机、旋转平台、载具、物理物体上 | 移动基座 |
Tick 的执行流程、PhysWalking/PhysFalling、扫描、滑动 | 移动管线 |
| 穿过地面、踏步、可行走角度、边缘支撑 | 地面检测 |
| 跑墙、攀爬、冲刺、抓钩及任何新移动类型 | 自定义移动模式 |
| 多人游戏不同步、橡皮筋效应、已保存移动、压缩标志 | 网络预测 |
| 击退、冲刺、动画驱动的运动 | 根运动 |
| 在墙壁/天花板上行走、星球重力 | 自定义重力 |
| AI Agent、由导航网格驱动的行走、RVO 避让 | 导航网格行走 |
| 在游戏代码中跳跃/蹲伏/发射 | ACharacter API |
| 速度、摩擦力、制动、旋转标志 | 关键属性 |
深入的模板请参阅 cmc-extension-patterns.md;各模式的内部机制和流程图请参阅 movement-pipeline.md。
CMC 与 Mover
Mover(在 5.8 中为实验性功能)是一个独立插件(Mover,默认未启用),基于 UMoverComponent、UBaseMovementMode、FLayeredMoveBase 和 UCharacterMoverComponent 构建。它不能直接替换 CMC。
| 情况 | 使用 |
|---|---|
| 面向任意引擎支持的平台发布游戏 | CMC |
已有的 ACharacter 层次结构、动画和 AI 都已接入 CMC | CMC |
| 需要当前就保持稳定的联网游戏 | CMC |
| 非胶囊、非人形移动物体(载具、无人机),但仍需要回滚联网 | Mover |
由数据驱动的模式/修改器组合移动,而非使用 Phys* 子类 | Mover |
| 使用 Chaos 在物理线程上模拟的移动 | Mover(ChaosMover) |
| 可以接受实验性 API 变动的原型 | Mover |
Mover 的详细信息请参阅 ue-mover。不要在同一个 Pawn 上混用这两个组件。
CMC 架构
UActorComponent -> UMovementComponent -> UNavMovementComponent -> UPawnMovementComponent -> UCharacterMovementComponent
UNavMovementComponent 还实现了 INavMovementInterface;UCharacterMovementComponent 另外实现了 IRVOAvoidanceInterface 和 INetworkPredictionInterface,并声明为 UCLASS(MinimalAPI)。
CMC 是 ACharacter 的默认子对象(ACharacter::CharacterMovementComponentName)。ACharacter 拥有胶囊体、网格体和高层动作(Jump、Crouch、LaunchCharacter);CMC 负责模拟、地面检测和预测。
移动模式
EMovementMode(Engine/EngineTypes.h):
| 模式 | 含义 |
|---|---|
MOVE_None | 不处理移动 |
MOVE_Walking | 地面移动,包含地面检测和上台阶 |
MOVE_NavWalking | 由导航网格投影驱动的行走 |
MOVE_Falling | 空中状态:重力、空中控制、着陆 |
MOVE_Swimming | 带浮力的流体移动 |
MOVE_Flying | 自由的 3D 移动,无重力 |
MOVE_Custom | 用户定义;通过 uint8 子模式分发到 PhysCustom |
// UCharacterMovementComponent. SetMovementMode is BlueprintCallable;
// OnMovementModeChanged is protected and not a UFUNCTION (override it in C++)
virtual void SetMovementMode(EMovementMode NewMovementMode, uint8 NewCustomMode = 0);
virtual void OnMovementModeChanged(EMovementMode PreviousMovementMode, uint8 PreviousCustomMode);
ACharacter::OnMovementModeChanged(EMovementMode PrevMovementMode, uint8 PreviousCustomMode = 0) 在 CMC 钩子之后触发,并广播 MovementModeChangedDelegate(FMovementModeChangedSignature)。将自定义模式的进入/退出逻辑放在 CMC 重写中;当前子模式是 uint8 CustomMovementMode 成员。
移动基底
移动基底是角色站立并随之移动的对象。在 5.8 中,它不再是 UPrimitiveComponent*:而是 FMovementBaseInterfaceData(Interfaces/MovementBaseInterface.h),其中包含一个 TWeakObjectPtr<UObject> PhysicsObjectOwner,以及提供基底行为的 IPhysicsBodyInstanceOwner。这样,非组件物理对象也可以作为基底。
读取基底
// UCharacterMovementComponent
UObject* GetMovementBaseObject() const; // BlueprintCallable
const FMovementBaseInterfaceData* GetMovementBaseInterfaceData() const;
FMovementBaseInterfaceData* GetMovementBaseInterfaceData_Mutable();
const FMovementBaseInterfaceData* GetLastServerMovementBaseInterfaceData() const;
APawn 声明了 GetMovementBaseObject()、GetMovementBaseInterfaceData() 和 GetMovementBaseInterfaceData_Mutable();ACharacter 将这三者都重写为 final。FMovementBaseInterfaceData 本身提供 IsValid()、Clear()、Set()、GetBodyInstanceOwner()、GetMovementBaseObject() 和 GetMovementBaseObjectOwner()。
static void MyLogCurrentBase(const UCharacterMovementComponent& Movement)
{
const FMovementBaseInterfaceData* BaseData = Movement.GetMovementBaseInterfaceData();
if (!MovementBaseUtility::IsMovementBaseDataValid(BaseData))
{
return;
}
const FName BoneName = Movement.GetCharacterOwner()->GetBasedMovement().BoneName;
const FVector BaseVelocity = MovementBaseUtility::GetMovementBaseVelocity(BaseData, BoneName);
// Component-specific work now needs an explicit cast.
if (const UPrimitiveComponent* BaseComponent = Cast<UPrimitiveComponent>(BaseData->GetMovementBaseObject()))
{
UE_LOG(LogMyGame, Verbose, TEXT("Based on %s at %s"), *BaseComponent->GetName(), *BaseVelocity.ToString());
}
}
设置基底
virtual void SetBase(FMovementBaseInterfaceData* MovementBaseInterfaceData, const FName BoneName = NAME_None, bool bNotifyActor = true); // CMC and ACharacter
void SetBaseFromFloor(const FFindFloorResult& FloorResult); // CMC
根据已有内容构建该结构体:
FMovementBaseInterfaceData NewBase = MovementBaseUtility::GetMovementBaseDataFromHitResult(&Hit);
SetBase(&NewBase, Hit.BoneName);
MovementBaseUtility::GetMovementBaseDataFromPhysicsOwner(UObject*) 可从复制的 PhysicsObjectOwner 执行相同操作。
工具命名空间
现在,所有 MovementBaseUtility 辅助函数都接受 FMovementBaseInterfaceData*,而不是 UPrimitiveComponent*:IsDynamicBase、IsSimulatedBase、UseRelativeLocation、AddTickDependency、RemoveTickDependency、GetMovementBaseVelocity、GetMovementBaseTangentialVelocity、GetMovementBaseTransform、TransformLocationToWorld、TransformLocationToLocal、TransformDirectionToWorld、TransformDirectionToLocal。5.8 新增:DoesMovementBaseDataMatch、IsMovementBaseDataValid、GetMovementBaseDataFromPhysicsOwner、GetMovementBaseDataFromHitResult。
存储和复制的基底状态
| 旧成员 | 5.8 成员 | 持有者 |
|---|---|---|
MovementBase | MovementBaseInterfaceData | FBasedMovementInfo |
MovementBase | PhysicsObjectOwner | FRepRootMotionMontage |
MovementBase | MovementBasePhysicsObjectOwner | FCharacterNetworkMoveData |
StartBase / EndBase | StartMovementBaseInterfaceData / EndMovementBaseInterfaceData | FSavedMove_Character |
LastServerMovementBase | LastMovementBaseInterfaceData | UCharacterMovementComponent |
过去接收 UPrimitiveComponent* ClientMovementBase 的所有服务器权威和校正虚函数现在都有一个 FMovementBaseInterfaceData* 重载:ServerMoveHandleClientError、ServerCheckClientError、ServerExceedsAllowablePositionError、ServerShouldUseAuthoritativePosition、ClientAdjustPosition_Implementation、ClientVeryShortAdjustPosition_Implementation、ClientAdjustRootMotionPosition_Implementation、ClientAdjustRootMotionSourcePosition_Implementation、OnClientCorrectionReceived(以及非虚函数 RevertMove)。请重写新的重载——重写已弃用的重载将导致其不再被调用,且不会有任何提示。
ACharacter::BaseChange() 和 ACharacter::OnRep_ReplicatedBasedMovement() 是通知钩子;ACharacter::GetBasedMovement() 返回 FBasedMovementInfo。
移动管线
PerformMovement(float DeltaTime)(受保护)是自主路径的入口;模拟代理会经过 SimulatedTick,只有在根运动处于活动状态时才会调用 PerformMovement,否则调用 SimulateMovement(通过 MoveSmooth 外推,并跳过 Phys* 函数)。PerformMovement 会到达 StartNewPhysics(float deltaTime, int32 Iterations),该函数根据 MovementMode 进行分派。所有 Phys* 函数都是 virtual;PhysFalling 是公共函数(CharacterMovementComponent.h:1663),其余五个是受保护函数(:1995-2007):
virtual void PhysWalking(float deltaTime, int32 Iterations);
virtual void PhysNavWalking(float deltaTime, int32 Iterations);
virtual void PhysFalling(float deltaTime, int32 Iterations);
virtual void PhysSwimming(float deltaTime, int32 Iterations);
virtual void PhysFlying(float deltaTime, int32 Iterations);
virtual void PhysCustom(float deltaTime, int32 Iterations);
在 Phys* 函数内部:
// BlueprintCallable
virtual void CalcVelocity(float DeltaTime, float Friction, bool bFluid, float BrakingDeceleration);
// UMovementComponent; also has an FRotator overload
virtual bool SafeMoveUpdatedComponent(const FVector& Delta, const FQuat& NewRotation, bool bSweep,
FHitResult& OutHit, ETeleportType Teleport = ETeleportType::None);
SafeMoveUpdatedComponent 封装了 MoveUpdatedComponent,并在初始重叠时调用 ResolvePenetration——在自定义 Phys* 代码中应始终优先使用它。重写它时,请在声明旁添加 using UMovementComponent::SafeMoveUpdatedComponent;,否则会隐藏 FRotator 重载。
发生阻挡命中时,CMC 对 SlideAlongSurface(const FVector& Delta, float Time, const FVector& Normal, FHitResult& Hit, bool bHandleImpact) 的重写会将剩余位移投影到表面上(行走时不会沿不可行走的斜坡向上滑动)。拐角命中会经过 TwoWallAdjust(FVector& WorldSpaceDelta, const FHitResult& Hit, const FVector& OldHitNormal) const。行走期间,ComputeGroundMovementDelta(const FVector& Delta, const FHitResult& RampHit, const bool bHitFromLineTrace) const 会调整位移方向,使其沿斜坡移动;随后 MaintainHorizontalGroundVelocity() 会将速度展平(当 bMaintainHorizontalGroundVelocity 为 false 时,会重新缩放以保持速度大小)。
CMC 重写了 SetUpdatedComponent(USceneComponent* NewUpdatedComponent);该函数用于绑定被移动的组件(胶囊体),并重新注册 Tick。子步进受 MaxSimulationTimeStep 和 MaxSimulationIterations 限制;MIN_TICK_TIME 是丢弃时间片的下限。CMC 也可以通过 CVar p.AsyncCharacterMovement(默认值为 0;引擎标记为尚未完全开发)在物理线程上运行整个模拟,使用 FCharacterMovementComponentAsyncInput / FCharacterMovementComponentAsyncOutput / FCharacterMovementComponentAsyncCallback——自定义 Phys* 状态不会自动镜像到该路径中。各模式的完整说明及异步处理机制:movement-pipeline.md。
地面检测
FFindFloorResult 定义在 CharacterMovementComponentAsync.h 中(USTRUCT(BlueprintType)),而不在 CMC 头文件中:
| 成员 | 含义 |
|---|---|
bBlockingHit | 扫掠命中了某物,且未处于初始穿透状态 |
bWalkableFloor | 命中表面通过了可行走性测试 |
bLineTrace | 结果来自直线追踪回退 |
FloorDist | 与扫掠胶囊体之间的距离 |
LineDist | 与直线追踪之间的距离(仅当 bLineTrace 为 true 时有效) |
HitResult | 完整的 FHitResult |
辅助函数:IsWalkableFloor()(bBlockingHit && bWalkableFloor)、GetDistanceToFloor()、Clear()、SetFromSweep()、SetFromLineTrace()。
// Both are const on UCharacterMovementComponent
virtual void FindFloor(const FVector& CapsuleLocation, FFindFloorResult& OutFloorResult,
bool bCanUseCachedLocation, const FHitResult* DownwardSweepResult = NULL) const;
virtual void ComputeFloorDist(const FVector& CapsuleLocation, float LineDistance, float SweepDistance,
FFindFloorResult& OutFloorResult, float SweepRadius,
const FHitResult* DownwardSweepResult = NULL) const;
FindFloor 会委托给 ComputeFloorDist(向下进行胶囊体扫掠,然后通过直线追踪验证接触点)。缓存结果为 CurrentFloor。AdjustFloorHeight() 会将胶囊体调整到 MIN_FLOOR_DIST 与 MAX_FLOOR_DIST 之间。
可行走性:WalkableFloorAngle(角度,限制在 0–90 度)及其派生值 WalkableFloorZ(默认值为 0.71,约为 44.8 度)。通过 SetWalkableFloorAngle(float) 设置角度——直接写入 WalkableFloorZ 会导致这两个值不同步。virtual bool IsWalkable(const FHitResult& Hit) const 是针对不同表面自定义规则的重写入口。
跨步会通过 virtual bool StepUp(const FVector& GravDir, const FVector& Delta, const FHitResult& Hit, FStepDownResult* OutStepDownResult = NULL) 执行,并受 MaxStepHeight 限制;PerchRadiusThreshold 和 PerchAdditionalHeight 用于控制在边缘驻足的行为。
自定义移动模式
MOVE_Custom 加上 uint8 CustomMovementMode,可提供 256 种子模式,并复用 CMC 的预测流程。
// MyCharacterMovement.h
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/CharacterMovementComponent.h"
#include "MyCharacterMovement.generated.h"
UENUM(BlueprintType)
enum class EMyMovementMode : uint8
{
None = 0,
Climbing = 1,
Dash = 2
};
UCLASS()
class MYGAME_API UMyCharacterMovement : public UCharacterMovementComponent
{
GENERATED_BODY()
public:
UMyCharacterMovement();
UPROPERTY(Transient)
uint8 bWantsToClimb : 1;
UPROPERTY(Transient)
FVector ClimbSurfaceNormal;
virtual void PhysCustom(float deltaTime, int32 Iterations) override;
virtual void OnMovementModeChanged(EMovementMode PreviousMovementMode, uint8 PreviousCustomMode) override;
virtual float GetMaxSpeed() const override;
protected:
void PhysClimb(float deltaTime, int32 Iterations);
};
void UMyCharacterMovement::PhysCustom(float deltaTime, int32 Iterations)
{
Super::PhysCustom(deltaTime, Iterations); // dispatches ACharacter::K2_UpdateCustomMovement
if (CustomMovementMode == static_cast<uint8>(EMyMovementMode::Climbing))
{
PhysClimb(deltaTime, Iterations);
}
}
首先调用 Super::PhysCustom(deltaTime, Iterations):基类实现会转发到 ACharacter::K2_UpdateCustomMovement(float DeltaTime),因此跳过它或最后才调用它,都会破坏 Blueprint 钩子以及之后可能添加的任何基类簿记逻辑。此技能规定了这一调用顺序。
使用 SetMovementMode(MOVE_Custom, static_cast<uint8>(EMyMovementMode::Climbing)) 进入模式,使用 SetMovementMode(MOVE_Falling) 离开模式。重写 virtual float GetMaxSpeed() const override 可限制速度(基类在 MOVE_Custom 下会返回 MaxCustomMovementSpeed)。带预测功能的完整攀爬模式见 cmc-extension-patterns.md。
网络预测
自主客户端会在本地模拟,将每一帧记录为 FSavedMove_Character,发送给服务端,并在服务端发送校正时重放尚未确认的移动。未保存和恢复的自定义移动状态会在重放时丢失。传输机制本身(RPC 规则、角色、相关性、推送模型)属于 ue-networking-replication。
FSavedMove_Character
实用字段:bPressedJump、bWantsToCrouch、bForceMaxAccel、bForceNoCombine、TimeStamp、DeltaTime、StartLocation、StartVelocity、StartFloor、StartRotation、StartControlRotation、StartMovementBaseInterfaceData、SavedLocation、SavedVelocity、EndMovementBaseInterfaceData、Acceleration、MaxSpeed、SavedRootMotion。
要重写的虚函数,原样如下:
virtual void Clear();
virtual void SetMoveFor(ACharacter* C, float InDeltaTime, FVector const& NewAccel, class FNetworkPredictionData_Client_Character& ClientData);
virtual void PrepMoveFor(ACharacter* C);
virtual uint8 GetCompressedFlags() const;
virtual bool CanCombineWith(const FSavedMovePtr& NewMove, ACharacter* InCharacter, float MaxDelta) const;
virtual void PostUpdate(ACharacter* C, EPostUpdateMode PostUpdateMode);
virtual bool IsImportantMove(const FSavedMovePtr& LastAckedMove) const;
virtual void CombineWith(const FSavedMove_Character* OldMove, ACharacter* InCharacter, APlayerController* PC, const FVector& OldStartLocation);
压缩标志
FSavedMove_Character::CompressedFlags 是一个嵌套的 enum:
| 标志 | 值 | 用途 |
|---|---|---|
FLAG_JumpPressed | 0x01 | 跳跃输入 |
FLAG_WantsToCrouch | 0x02 | 蹲伏输入 |
FLAG_Reserved_1 | 0x04 | 引擎保留 |
FLAG_Reserved_2 | 0x08 | 引擎保留 |
FLAG_Custom_0 | 0x10 | 自定义 |
FLAG_Custom_1 | 0x20 | 自定义 |
FLAG_Custom_2 | 0x40 | 自定义 |
FLAG_Custom_3 | 0x80 | 自定义 |
在 GetCompressedFlags() 中打包标志,在 CMC 上通过 virtual void UpdateFromCompressedFlags(uint8 Flags) 解包。总共只有 4 个位;超出部分应通过自定义移动数据传递。
预测数据
FNetworkPredictionData_Client_Character 持有 SavedMoves、FreeMoves、PendingMove、LastAckedMove、MaxSavedMoveCount 和网格平滑偏移量。重写 virtual FSavedMovePtr AllocateNewMove() 以返回你的子类,并在 virtual class FNetworkPredictionData_Client* GetPredictionData_Client() const override 中按需创建数据(CMC 成员是 ClientPredictionData;GetPredictionData_Client_Character() 是类型化访问器)。
打包移动 RPC
移动 RPC 位于 ACharacter 上:
void ServerMovePacked(const FCharacterServerMovePackedBits& PackedBits); // Server, unreliable, WithValidation
void ClientMoveResponsePacked(const FCharacterMoveResponsePackedBits& PackedBits); // Client, unreliable, WithValidation
对于无法放入 4 个自定义标志的状态,派生 FCharacterNetworkMoveData(重写 ClientFillNetworkMoveData(const FSavedMove_Character& ClientMove, ENetworkMoveType MoveType) 和 Serialize(UCharacterMovementComponent&, FArchive&, UPackageMap*, ENetworkMoveType)),派生 FCharacterNetworkMoveDataContainer 并重新指向 NewMoveData / PendingMoveData / OldMoveData,然后在 CMC 构造函数中调用 SetNetworkMoveDataContainer(...),并传入生命周期长于该组件的容器。在 MoveAutonomous 或 UpdateFromCompressedFlags 内部,GetCurrentNetworkMoveData() 可访问当前活动数据。响应侧可用 FCharacterMoveResponseDataContainer 和 SetMoveResponseDataContainer(...) 实现对应机制。模板见:cmc-extension-patterns.md。
根运动
FRootMotionSource(GameFramework/RootMotionSource.h)由游戏逻辑驱动移动。基础字段:Priority(uint16,值越大优先级越高)、LocalID(uint16)、InstanceName、Duration(负值表示持续运行,直到被移除)、AccumulateMode(ERootMotionAccumulateMode::Override 或 Additive)、FinishVelocityParams(ERootMotionFinishVelocityMode)。
| 结构体 | 关键字段 | 用途 |
|---|---|---|
FRootMotionSource_ConstantForce | Force、StrengthOverTime | 击退、风力 |
FRootMotionSource_RadialForce | Location、LocationActor、Radius、Strength、bIsPush、bNoZForce | 爆炸、漩涡 |
FRootMotionSource_MoveToForce | StartLocation、TargetLocation、PathOffsetCurve | 冲刺到固定点 |
FRootMotionSource_MoveToDynamicForce | StartLocation、SetTargetLocation() | 追踪冲刺 |
FRootMotionSource_JumpForce | Rotation、Distance、Height、TimeMappingCurve | 定向跳跃弧线 |
// UCharacterMovementComponent
uint16 ApplyRootMotionSource(TSharedPtr<FRootMotionSource> SourcePtr);
TSharedPtr<FRootMotionSource> GetRootMotionSource(FName InstanceName);
TSharedPtr<FRootMotionSource> GetRootMotionSourceByID(uint16 RootMotionSourceID);
void RemoveRootMotionSource(FName InstanceName);
void RemoveRootMotionSourceByID(uint16 RootMotionSourceID);
bool HasRootMotionSources() const;
TSharedPtr<FRootMotionSource_ConstantForce> Knockback = MakeShared<FRootMotionSource_ConstantForce>();
Knockback->InstanceName = TEXT("Knockback");
Knockback->Duration = 0.3f;
Knockback->Force = KnockbackDirection * KnockbackStrength;
Knockback->AccumulateMode = ERootMotionAccumulateMode::Override;
Knockback->Priority = 5;
GetCharacterMovement()->ApplyRootMotionSource(Knockback);
激活的源位于 CurrentRootMotion(FRootMotionSourceGroup)中,并通过 virtual void ApplyRootMotionToVelocity(float deltaTime) 合并到速度中。动画根运动走的是另一条路径:RootMotionParams(FRootMotionMovementParams)由动画图填充,通过 HasAnimRootMotion() 查询,并通过 FRepRootMotionMontage 复制。蒙太奇的制作以及 UAnimInstance::SetRootMotionMode 属于 ue-animation-system。
自定义重力
virtual void SetGravityDirection(const FVector& GravityDir);
bool HasCustomGravity() const;
FVector GetGravityDirection() const;
FQuat GetWorldToGravityTransform() const;
FQuat GetGravityToWorldTransform() const;
FVector RotateGravityToWorld(const FVector& World) const;
FVector RotateWorldToGravity(const FVector& Gravity) const;
FVector ProjectToGravityFloor(const FVector& Vector) const;
FVector::FReal GetGravitySpaceZ(const FVector& Vector) const;
void SetGravitySpaceZ(FVector& Vector, const FVector::FReal Z) const;
GravityDirection、WorldToGravityTransform、GravityToWorldTransform 和 bHasCustomGravity 均受保护——务必通过访问器操作,并始终使用 SetGravityDirection 设置方向,以便重建缓存的四元数。当 HasCustomGravity() 为 true 时,行走、下落和地面检测都会在重力空间中进行;在自定义 Phys* 代码中,使用 ProjectToGravityFloor / GetGravitySpaceZ,不要硬编码 Z 运算。
NavMesh 行走
MOVE_NavWalking 会将胶囊体投射到导航网格上,而不是与世界几何体进行扫掠检测——对人群来说效率高得多,这也是 AI 角色能够站在虚空中的原因。FindNavFloor(const FVector& TestLocation, FNavLocation& NavFloorLocation) const 会执行投射。
| 属性 | 效果 |
|---|---|
bProjectNavMeshWalking | 向底层几何体发射射线,以贴合高度 |
bProjectNavMeshOnBothWorldChannels | 射线检测时使用 WorldStatic 和 WorldDynamic |
bSlideAlongNavMeshEdge | 沿导航网格边缘滑动,而不是投射一个点并移动到该点 |
避让:通过 SetAvoidanceGroupMask(const FNavAvoidanceMask&) 设置 bUseRVOAvoidance、AvoidanceWeight 和 AvoidanceGroup(FNavAvoidanceMask)。导航数据、查询和寻路属于 ue-ai-navigation。
关键属性
| 属性 | 默认值 | 备注 |
|---|---|---|
MaxWalkSpeed | 600 | 地面速度上限 |
MaxWalkSpeedCrouched | 300 | 默认是 MaxWalkSpeed 的一半 |
MaxSwimSpeed | 300 | |
MaxFlySpeed | 600 | |
MaxCustomMovementSpeed | 600 | MOVE_Custom 中 GetMaxSpeed() 返回值的上限 |
MaxAcceleration | 2048 | |
BrakingDecelerationWalking | 2048 | 默认与 MaxAcceleration 相同 |
GroundFriction | 8 | |
GravityScale | 1 | 世界重力的倍率 |
JumpZVelocity | 420 | |
AirControl | 0.05 | 下落时的横向控制能力 |
MaxStepHeight | 45 | |
WalkableFloorZ | 0.71 | 约 44.8 度;通过 SetWalkableFloorAngle 设置 |
MaxSimulationTimeStep | 0.05 | 子步长上限 |
MaxSimulationIterations | 8 | 每帧的子步数 |
NetworkSmoothingMode | Exponential(CharacterMovementComponent.cpp:718) | ENetworkSmoothingMode::Disabled、Linear、Exponential |
bOrientRotationToMovement | false | 朝向速度方向旋转 |
bUseControllerDesiredRotation | false | 朝向控制器旋转方向 |
bOrientRotationToMovement 和 bUseControllerDesiredRotation 每帧都会相互冲突——请选择其中一个。
ACharacter API
virtual void Jump(); // BlueprintCallable, sets bPressedJump
virtual void StopJumping(); // BlueprintCallable
bool CanJumpInternal() const; // BlueprintNativeEvent, DisplayName "CanJump"
virtual bool CanJumpInternal_Implementation() const; // override this
virtual void LaunchCharacter(FVector LaunchVelocity, bool bXYOverride, bool bZOverride);
virtual void Landed(const FHitResult& Hit);
virtual void Crouch(bool bClientSimulation = false);
virtual void UnCrouch(bool bClientSimulation = false);
跳跃状态:bPressedJump、JumpMaxHoldTime、JumpMaxCount、JumpCurrentCount、JumpCurrentCountPreJump。CMC 端对应的是 virtual bool DoJump(bool bReplayingMoves, float DeltaTime) 和 virtual bool CanAttemptJump() const。Blueprint 事件:OnLanded、OnLaunched、OnWalkingOffLedge、OnReachedJumpApex。
bXYOverride / bZOverride 会替换对应的速度分量,而不是与其相加;LaunchCharacter 会调用 UCharacterMovementComponent::Launch(FVector const&) 和 HandlePendingLaunch()。
蹲伏:bIsCrouched 是复制标志;胶囊体半高由 CMC 上的 SetCrouchedHalfHeight(const float) / GetCrouchedHalfHeight() 设置和获取。
访问器:GetCharacterMovement()、GetCapsuleComponent()(UCapsuleComponent)、GetMesh()(USkeletalMeshComponent)。
已弃用 — 请勿使用
| 请勿使用 | 在 5.8 中改用 | 来源 |
|---|---|---|
ACharacter::GetMovementBase() | GetMovementBaseObject() / GetMovementBaseInterfaceData() | GameFramework/Character.h 中的 UE_DEPRECATED(5.8) |
UCharacterMovementComponent::GetMovementBase() | GetMovementBaseObject() | GameFramework/CharacterMovementComponent.h 中的 UE_DEPRECATED(5.8) |
SetBase(UPrimitiveComponent*, FName, bool) | SetBase(FMovementBaseInterfaceData*, FName, bool) | Character.h、CharacterMovementComponent.h 中的 UE_DEPRECATED(5.8) |
接受 const UPrimitiveComponent* 的任何 MovementBaseUtility 辅助函数 | 接受 const FMovementBaseInterfaceData* 的同名函数(旧形式中的 AddTickDependency/RemoveTickDependency 接受非 const 的 UPrimitiveComponent*) | Character.h 中的 UE_DEPRECATED(5.8) |
FBasedMovementInfo::MovementBase | FBasedMovementInfo::MovementBaseInterfaceData | Character.h 中的 UE_DEPRECATED(5.8) |
FRepRootMotionMontage::MovementBase | FRepRootMotionMontage::PhysicsObjectOwner | Character.h 中的 UE_DEPRECATED(5.8) |
FCharacterNetworkMoveData::MovementBase | MovementBasePhysicsObjectOwner | GameFramework/CharacterMovementReplication.h 中的 UE_DEPRECATED(5.8) |
FSavedMove_Character::StartBase / EndBase | StartMovementBaseInterfaceData / EndMovementBaseInterfaceData | CharacterMovementComponent.h 中的 UE_DEPRECATED(5.8) |
GetLastServerMovementBase() / LastServerMovementBase | GetLastServerMovementBaseInterfaceData() / LastMovementBaseInterfaceData | CharacterMovementComponent.h 中的 UE_DEPRECATED(5.8) |
ServerMoveHandleClientError、ServerCheckClientError、ClientAdjustPosition_Implementation、RevertMove 及其他 UPrimitiveComponent* 修正虚函数 | FMovementBaseInterfaceData* 重载 | CharacterMovementComponent.h 中的 UE_DEPRECATED(5.8) |
DoJump(bool bReplayingMoves) | DoJump(bool bReplayingMoves, float DeltaTime) | CharacterMovementComponent.h 中的 UE_DEPRECATED_FORGAME(5.5) |
公共的 CrouchedHalfHeight | SetCrouchedHalfHeight() / GetCrouchedHalfHeight() | CharacterMovementComponent.h 中的 UE_DEPRECATED_FORGAME(5.0) |
SetAvoidanceGroup(int32) | SetAvoidanceGroupMask(const FNavAvoidanceMask&) | CharacterMovementComponent.h 中的 meta=(DeprecatedFunction) |
UNavMovementComponent::bUseAccelerationForPaths、FixedPathBrakingDistance、bUpdateNavAgentWithOwnersCollision、bUseFixedBrakingDistanceForPaths、bStopMovementAbortPaths | 对应的 NavMovementProperties.* 字段 | GameFramework/NavMovementComponent.h 中的 UE_DEPRECATED(5.5) |
ServerMove、ServerMoveDual、ServerMoveOld、ClientAdjustPosition、ClientAckGoodMove RPC | ServerMovePacked / ClientMoveResponsePacked | CharacterMovementReplication.h 中的 DEPRECATED_CHARACTER_MOVEMENT_RPC(SUPPORT_DEPRECATED_CHARACTER_MOVEMENT_RPCS 默认为 0) |
常见错误
将移动基座视为组件:GetMovementBase() 已弃用,FBasedMovementInfo::MovementBase 不再保存基座。读取 GetMovementBaseInterfaceData(),只有在确实需要 UPrimitiveComponent 时才转换 GetMovementBaseObject()。
**重写 UPrimitiveComponent* 修正虚函数:**重写已弃用的 ServerCheckClientError / ClientAdjustPosition_Implementation 重载的子类可以编译通过,但这些函数永远不会被调用,因此修正会悄然回退到基类行为。请重写 FMovementBaseInterfaceData* 重载。
**最后调用 Super::PhysCustom,或完全不调用:**基类实现会转发到 ACharacter::K2_UpdateCustomMovement。请先调用它,再运行你的模式。
**直接写入 Velocity,而不是调用 CalcVelocity:**这会绕过摩擦、制动和加速度钳制。
// WRONG
Velocity = GetLastInputVector() * MaxWalkSpeed;
// RIGHT
CalcVelocity(DeltaTime, GroundFriction, false, BrakingDecelerationWalking);
**在自定义 Phys* 代码中使用 MoveUpdatedComponent:**它不会解决初始穿透问题,因此胶囊体会卡在几何体中。请使用 SafeMoveUpdatedComponent。
**未在 FSavedMove_Character 中保存自定义状态:**任何未在 SetMoveFor 中捕获并在 PrepMoveFor 中恢复的内容,都会在修正重放期间丢失;只有在有延迟时才会表现为橡皮筋效应。
重复使用栈局部网络移动数据容器:SetNetworkMoveDataContainer 会存储指针。超出作用域的容器会导致 CMC 中出现悬空指针。
**直接设置 WalkableFloorZ:**它是根据 WalkableFloorAngle 计算得出的。请调用 SetWalkableFloorAngle(),以确保两者保持一致。
**启用自定义重力时将 Z 写死:**请使用 ProjectToGravityFloor、GetGravitySpaceZ 和 RotateWorldToGravity,而不是 Velocity.Z。
相关技能
ue-mover— Mover 插件(5.8 中为实验性功能):UMoverComponent、作为数据的移动模式、分层移动、ChaosMoverue-networking-replication— RPC 规则、网络角色、DOREPLIFETIME、推送模型、相关性;CMC 预测所依赖的传输机制ue-animation-system— 蒙太奇、动画图、SetRootMotionMode、动画驱动的移动ue-gameplay-framework—ACharacter在 GameMode/Controller/Pawn 层级中的位置、占有、输入路由ue-physics-collision— 地面检测和跨步所用的扫描、碰撞通道与响应ue-gameplay-abilities— 触发移动模式或根运动源的能力和能力任务ue-ai-navigation—MOVE_NavWalking背后的导航网格生成与查询、避让配置ue-actor-component-architecture— Actor 和组件生命周期、附加、生成与 Tick 配置ue-gameplay-cameras— 弹簧臂、视图目标、摄像机修改器、震动和 Gameplay Camera Systemue-input-system— Enhanced Input 操作、映射上下文、触发器、修改器和用户设置