mcpField
U
ue-character-movement

ue-character-movement

github.com/quodsoler开发4191.2k 次安装4 周前更新
代码仓库

关于 ue-character-movement

面向 UE 5.8 开发者的角色移动 Skill,聚焦 ACharacter 与 UCharacterMovementComponent,涵盖移动模拟、自定义模式、网络预测及 5.8 移动基座变更。

主要功能

  1. 01移动模式与切换
  2. 02地面检测与跨步
  3. 03自定义移动模式
  4. 04网络预测与校正
  5. 05根运动与自定义重力
  6. 06UE 5.8 移动基座 API

适用场景

  1. 01实现沿墙跑或攀爬模式
  2. 02排查多人移动校正问题
  3. 03让角色随平台移动
  4. 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 都已接入 CMCCMC
需要当前就保持稳定的联网游戏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 成员持有者
MovementBaseMovementBaseInterfaceDataFBasedMovementInfo
MovementBasePhysicsObjectOwnerFRepRootMotionMontage
MovementBaseMovementBasePhysicsObjectOwnerFCharacterNetworkMoveData
StartBase / EndBaseStartMovementBaseInterfaceData / EndMovementBaseInterfaceDataFSavedMove_Character
LastServerMovementBaseLastMovementBaseInterfaceDataUCharacterMovementComponent

过去接收 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_JumpPressed0x01跳跃输入
FLAG_WantsToCrouch0x02蹲伏输入
FLAG_Reserved_10x04引擎保留
FLAG_Reserved_20x08引擎保留
FLAG_Custom_00x10自定义
FLAG_Custom_10x20自定义
FLAG_Custom_20x40自定义
FLAG_Custom_30x80自定义

在 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_ConstantForceForce、StrengthOverTime击退、风力
FRootMotionSource_RadialForceLocation、LocationActor、Radius、Strength、bIsPush、bNoZForce爆炸、漩涡
FRootMotionSource_MoveToForceStartLocation、TargetLocation、PathOffsetCurve冲刺到固定点
FRootMotionSource_MoveToDynamicForceStartLocation、SetTargetLocation()追踪冲刺
FRootMotionSource_JumpForceRotation、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。

关键属性

属性默认值备注
MaxWalkSpeed600地面速度上限
MaxWalkSpeedCrouched300默认是 MaxWalkSpeed 的一半
MaxSwimSpeed300
MaxFlySpeed600
MaxCustomMovementSpeed600MOVE_Custom 中 GetMaxSpeed() 返回值的上限
MaxAcceleration2048
BrakingDecelerationWalking2048默认与 MaxAcceleration 相同
GroundFriction8
GravityScale1世界重力的倍率
JumpZVelocity420
AirControl0.05下落时的横向控制能力
MaxStepHeight45
WalkableFloorZ0.71约 44.8 度;通过 SetWalkableFloorAngle 设置
MaxSimulationTimeStep0.05子步长上限
MaxSimulationIterations8每帧的子步数
NetworkSmoothingModeExponential(CharacterMovementComponent.cpp:718)ENetworkSmoothingMode::Disabled、Linear、Exponential
bOrientRotationToMovementfalse朝向速度方向旋转
bUseControllerDesiredRotationfalse朝向控制器旋转方向

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::MovementBaseFBasedMovementInfo::MovementBaseInterfaceDataCharacter.h 中的 UE_DEPRECATED(5.8)
FRepRootMotionMontage::MovementBaseFRepRootMotionMontage::PhysicsObjectOwnerCharacter.h 中的 UE_DEPRECATED(5.8)
FCharacterNetworkMoveData::MovementBaseMovementBasePhysicsObjectOwnerGameFramework/CharacterMovementReplication.h 中的 UE_DEPRECATED(5.8)
FSavedMove_Character::StartBase / EndBaseStartMovementBaseInterfaceData / EndMovementBaseInterfaceDataCharacterMovementComponent.h 中的 UE_DEPRECATED(5.8)
GetLastServerMovementBase() / LastServerMovementBaseGetLastServerMovementBaseInterfaceData() / LastMovementBaseInterfaceDataCharacterMovementComponent.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)
公共的 CrouchedHalfHeightSetCrouchedHalfHeight() / 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 RPCServerMovePacked / ClientMoveResponsePackedCharacterMovementReplication.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、作为数据的移动模式、分层移动、ChaosMover
  • ue-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 System
  • ue-input-system — Enhanced Input 操作、映射上下文、触发器、修改器和用户设置

评论

支持 Markdown