UNREAL ENGINE · GROUNDED WALKING MOVEMENT · TECHNICAL NOTE

UE 原生贴地移动逻辑
1P 预测 → DS 重放 → 3P 跟随

基于 F:\PX_Trunk\UE(UE 5.8.2)逐行梳理 UCharacterMovementComponent 的原生贴地机制:PhysWalking / FindFloor / MoveAlongFloor / AdjustFloorHeight / StepUp,以及主控端如何本地贴地预测、DS 如何在权威世界重放、3P 模拟端如何用 SimulateMovement 做有限贴地外推。全文附交互动画沙盘。

1P ROLE_AutonomousProxy DS ROLE_Authority 3P ROLE_SimulatedProxy 引擎 UE 5.8.2 (Branch UE5) MIN_FLOOR_DIST 1.9 / MAX_FLOOR_DIST 2.4 bAlwaysCheckFloor = true

00一句话总览

UE 的"贴地"不是把角色直接放在地面上,而是允许胶囊底部悬浮在一个很窄的区间(1.9 ~ 2.4 cm)内,靠每帧 FindFloor 向下探测 + AdjustFloorHeight 把这个距离拉回区间中点。1P 和 DS 跑的是同一套完整 PhysWalking 物理;3P 没有输入,只按复制速度做 MoveSmooth 外推,再用精简版的 FindFloor + AdjustFloorHeight 贴地,最终高度由服务器快照兜底。

1P 主控端 完整物理 · 本地立即贴地
输入 → PerformMovement → StartNewPhysics → PhysWalking:CalcVelocity → MoveAlongFloor(含 StepUp)→ FindFloor → AdjustFloorHeight → SetBaseFromFloor。每帧本地算完,画面立即响应。
DS 同一套物理 · 权威 Z
收 ServerMove → MoveAutonomous → PerformMovement → 同一个 PhysWalking 在服务器世界重放。服务器的 AdjustFloorHeight 结果写进 ReplicatedMovement.Location.Z,成为所有客户端的高度基准。
3P 模拟端 有限外推 · 快照兜底
收快照 → SmoothCorrection 把 Capsule 直拍到权威位置;包间 SimulateMovement 用复制速度 MoveSmooth 外推,再 FindFloor / AdjustFloorHeight 贴地;下一个网络包纠正残差。
三条容易混淆的界线
  1. 贴地 ≠ 落地:AdjustFloorHeight 是"保持贴地"(MOVE_Walking 内),SetPostLandedPhysics 是"从空中落地"(MOVE_Falling → Walking)。
  2. 1P/DS 与 3P 用的不是同一个入口:前者 PhysWalking,后者 SimulateMovement;后者不调 CalcVelocity、不做完整的 StepUp 流程。
  3. Capsule 与 Mesh 分离:3P 上 Capsule 是权威位置,Mesh 因 MeshTranslationOffset 被平滑;贴地判定只看 Capsule。

01动画沙盘:地形剖面与三端

上半部分是地形剖面(平地 → 台阶 → 高台 → 下坡),三个胶囊分别代表 1P / DS / 3P 的位置;下半部分是三泳道时序。切换「实现模式」可对比 原生 UE(3P 有本地 FindFloor/AdjustFloorHeight)与 PX 项目(3P 移除本地贴地,Z 完全继承)。

速度 1.0×
地形
实现
RTT 80ms
TERRAIN PROFILE · MOVE_Walking 1P 主控端 AutonomousProxy · 本地预测 DS Authority · 重放 3P 模拟端 SimulatedProxy · 外推+平滑
1P 预测位置 DS 权威位置 3P 显示位置 纠正/快照包 FindFloor 向下探测
CURRENT STEP
待开始
点击「播放」或「单步」。动画按真实调用顺序演示:输入 → PhysWalking → FindFloor → MoveAlongFloor → StepUp → AdjustFloorHeight → DS 重放 → 复制 → 3P 外推贴地 → 平滑。
—
FLOOR STATE
MovementModeMOVE_Walking
CurrentFloor.FloorDist2.15 cm
bWalkableFloortrue
bForceNextFloorCheckfalse
bJustTeleportedfalse
Velocity.Z0.0
稳态区间 1.9 ~ 2.4 cm,条带超出即触发 AdjustFloorHeight。
NETWORK / SMOOTHING
3P Capsule Z 来源服务器快照
MeshTranslationOffset0.0 cm
3P 本地贴地开启
原生 UE 的 3P 在包间会做 FindFloor + AdjustFloorHeight;PX 项目把这段移除,Z 完全继承起点,贴地交给下一个网络包。
提示:切到「PX 项目」并选「下坡」地形,可看到 3P 胶囊在包间悬空、直到下一个网络包才被拉回;「原生 UE」下则会看到本地 FindFloor/AdjustFloorHeight 把 Z 拉回贴地区间。
DEMO A · FindFloor → AdjustFloorHeight 闭环
Z
向下胶囊 sweep 得到 FloorDist;超出 [1.9, 2.4] 时用 MoveDist = AvgFloorDist − OldFloorDist 垂直修正,并置 bForceNextFloorCheck。此子动画持续循环。
DEMO B · StepUp 三步(上台阶)
① 向上抬 StepTravelUpHeight → ② 向前移动 Delta → ③ 向下扫回地面 StepTravelDownHeight。任一步失败则 RevertMove() 回退,改走 SlideAlongSurface。

02贴地的物理模型

UE 的 walking 角色不是"贴"在地面上,而是浮在地面上方 1.9~2.4 cm。这个区间由两个常量定义,所有贴地逻辑都围绕它展开。

// CharacterMovementComponent.cpp:99-100
const float UCharacterMovementComponent::MIN_FLOOR_DIST = 1.9f;
const float UCharacterMovementComponent::MAX_FLOOR_DIST = 2.4f;
// :799  构造函数中默认每帧都做地面检测
bAlwaysCheckFloor = true;

2.1 FFindFloorResult 与 CurrentFloor

字段 / 函数含义
FloorDist胶囊底部到地面的距离(sweep 结果)。AdjustFloorHeight 唯一的输入量。
LineDist回退到 line trace 时的距离;bLineTrace = true 时用它替代 FloorDist(:6346-6358)。
bBlockingHit / bWalkableFloor是否命中、坡度是否可走(由 IsWalkable() 与 WalkableFloorAngle 决定)。
bLineTracesweep 不可用(穿模/不可走斜面)时改用线检测,避免把墙"爬"上去(:6348-6353)。
HitResult命中结果,供 SetBaseFromFloor 设置 MovementBase(移动平台)、ComputePerchResult 做边缘栖息判定。

2.2 FindFloor 到底做了什么

FindFloor:7261 先算探测距离,再决定是否真的发射检测:

// :7275-7291(节选)
const float HeightCheckAdjust = (IsMovingOnGround() ? MAX_FLOOR_DIST + UE_KINDA_SMALL_NUMBER : -MAX_FLOOR_DIST);
float FloorSweepTraceDist = FMath::Max(MAX_FLOOR_DIST, MaxStepHeight + HeightCheckAdjust);
...
if ( bAlwaysCheckFloor || !bCanUseCachedLocation || bForceNextFloorCheck || bJustTeleported )
{
    MutableThis->bForceNextFloorCheck = false;
    ComputeFloorDist(CapsuleLocation, FloorLineTraceDist, FloorSweepTraceDist,
                     OutFloorResult, CapsuleRadius, DownwardSweepResult);   // :7290
}
else if ( ... 有有效 MovementBase 且无需强制 ... )
{
    OutFloorResult = CurrentFloor;    // :7313 直接复用缓存,省一次物理查询
    bNeedToValidateFloor = false;
}

ComputeFloorDist(:7110)用缩短并缩窄的胶囊向下做 FloorSweepTest(:7177),命中点必须在胶囊底部半球范围内(IsWithinEdgeTolerance),否则缩小半径重试;sweep 不可用才回退 line trace。之后 FindFloor 还会做 Perch 判定(:7326-7360):站在边缘时用一个更小的"栖息半径"再探一次,决定能不能继续站在沿上。

缓存跳过的意义
默认 bAlwaysCheckFloor = true,所以原生实现每帧都真做向下 sweep;只有项目主动关闭该标志、且有稳定 MovementBase 时才会走 :7313 复用 CurrentFloor。这是后面第 10 章「FindFloor 跳帧优化」的切入点。

2.3 AdjustFloorHeight:把距离拉回区间中点

// :6335-6399(节选)
void UCharacterMovementComponent::AdjustFloorHeight()
{
    if (!CurrentFloor.IsWalkableFloor()) return;                 // :6340
    float OldFloorDist = CurrentFloor.FloorDist;
    if (CurrentFloor.bLineTrace) { ... OldFloorDist = CurrentFloor.LineDist; }   // :6346-6358

    if (OldFloorDist < MIN_FLOOR_DIST || OldFloorDist > MAX_FLOOR_DIST)       // :6362
    {
        const float AvgFloorDist = (MIN_FLOOR_DIST + MAX_FLOOR_DIST) * 0.5f;   // 2.15
        const float MoveDist = AvgFloorDist - OldFloorDist;                    // :6367
        SafeMoveUpdatedComponent(MoveDist * -GetGravityDirection(), ..., true, AdjustHit);  // :6368
        ...
        bJustTeleported |= !bMaintainHorizontalGroundVelocity || (OldFloorDist < 0.f);  // :6392
        if (CharacterOwner && CharacterOwner->GetLocalRole() != ROLE_SimulatedProxy)
            bForceNextFloorCheck = true;                                        // :6395-6397
    }
}

注意 :6395:SimulatedProxy 不会因高度修正置 bForceNextFloorCheck——这是原生代码里就存在的 1P/3P 差异,因为 3P 的地面状态本来就要靠下一个网络包刷新。

031P:完整 PhysWalking 循环

主控端的贴地是完整的、带输入的物理模拟,每帧本地执行,不等服务器。

3.1 调用链

APlayerController::PlayerTick → ProcessPlayerInput / UpdateRotation
  └─ ACharacter::AddMovementInput
       └─ UCharacterMovementComponent::TickComponent                 :1663
            └─ ControlledCharacterMove(InputVector, DeltaTime)        :6481
                 └─ ReplicateMoveToServer(DeltaTime, Acceleration)    :8968
                      └─ PerformMovement(NewMove->DeltaTime)          :9090 → :2790
                           └─ StartNewPhysics(DeltaSeconds, 0)       :3057 → :3554
                                └─ case MOVE_Walking: PhysWalking() :3579 → :5692

3.2 PhysWalking 一帧内逐步

1
进入与守卫
:5697-5713:deltaTime < MIN_TICK_TIME、无 Controller(3P 除外)、无 Query Collision 直接返回。bJustTeleported = false(:5717)。
2
子步迭代
:5726-5731:while (remainingTime >= MIN_TICK_TIME && Iterations < MaxSimulationIterations),用 GetSimulationTimeStep 把长帧拆成子步,长帧/掉帧时物理仍稳定。
3
速度归一化与积分
MaintainHorizontalGroundVelocity()(:5748 → :5674)把 Velocity 投影到重力水平面,贴地移动不允许有垂直速度;CalcVelocity(timeTick, GroundFriction, ...)(:5756)按输入加速度/摩擦/制动算水平速度;ApplyRootMotionToVelocity(:5760)。
4
沿地面移动
MoveAlongFloor(MoveVelocity, timeTick, &StepDownResult)(:5784 → :5591)。它用 ComputeGroundMovementDelta 把水平位移投影到当前地面法线(上坡自动抬升),再 SafeMoveUpdatedComponent(RampVector, ..., bSweep=true, Hit)(:5601-5602)。遇到障碍时:可走斜坡→二次投影;CanStepUp→StepUp;否则 SlideAlongSurface。
5
更新地面
:5806-5815:若 StepUp 已经算过地板(StepDownResult.bComputedFloor)直接复用,否则 FindFloor(当前位置, CurrentFloor, bZeroDelta, NULL)。
6
贴地修正与 base
:5858-5873:地板可走 → ShouldCatchAir 检查是否冲出边缘(是则 HandleWalkingOffLedge / StartFalling);否则 AdjustFloorHeight() + SetBaseFromFloor(CurrentFloor)。这一步才把 Z 拉回 2.15 cm 稳态区间。
7
落地/坠落切换与收尾
地板不可走 → CheckFall(:5897 → :5432)→ StartFalling(:5458)→ SetMovementMode(MOVE_Falling)。仍在地面时 :5907-5915 用实际位移反算 Velocity,再 MaintainHorizontalGroundVelocity() 保持水平。
关键点
1P 的贴地是闭环:FindFloor 给出 FloorDist → MoveAlongFloor 沿地面走 → AdjustFloorHeight 修正垂直偏差 → SetBaseFromFloor 绑定 MovementBase。每一步都在本地当帧完成,所以玩家按键到画面响应只有一帧。

04台阶与边缘:StepUp / CheckFall

4.1 StepUp 三步

StepUp:7613 在 MoveAlongFloor 撞到可跨越障碍时被调用(:5642)。它用 FScopedCapsuleMovementUpdate(:7675)把三次移动合并成一次 transform 更新,中间态不会触发 overlap 回调:

// :7642-7654  上台高度按实际 FloorDist 扣减
float StepTravelUpHeight   = MaxStepHeight;
float StepTravelDownHeight = (MaxStepHeight + MAX_FLOOR_DIST * 2.f);
...
// :7680  ① 向上
MoveUpdatedComponent(-GravDir * StepTravelUpHeight, PawnRotation, true, &SweepUpHit);
// :7691  ② 向前
MoveUpdatedComponent(Delta, PawnRotation, true, &Hit);
// ③ 向下扫回地面(StepTravelDownHeight),并计算 StepDownResult.FloorResult
// 任一步 bStartPenetrating 或失败 → ScopedStepUpMovement.RevertMove() 回到 OldLocation (:7685/:7699)

成功后 MoveAlongFloor 会用 StepDownResult.bComputedFloor 把新的 CurrentFloor 交给 PhysWalking,跳过重复 FindFloor(:5808-5811)。若 StepUp 失败则 HandleImpact + SlideAlongSurface 沿墙滑行(:5645-5646)。

4.2 走出边缘 → 转成 Falling

CheckFall(:5432)在 PhysWalking 检测到 !CurrentFloor.IsWalkableFloor() 时调用;bMustJump 或 CanWalkOffLedges() 为真则 HandleWalkingOffLedge + StartFalling。StartFalling(:5458)把剩余时间退还给 StartNewPhysics,并 SetMovementMode(MOVE_Falling)(:5474)。反方向由 ProcessLanded(:6413)/ SetPostLandedPhysics(:6452)把 Falling 转回 Walking。

对 3P 的影响
3P 的 SimulateMovement 里也有一套精简的"没地板就转 Falling / 有地板就 SetPostLandedPhysics"逻辑(见第 6 章),但不会调用 CheckFall 的完整边缘处理、也不会走 StepUp 的上台阶流程——它只有 MoveSmooth 内部的 StepUp 兜底(MoveSmooth:7002,且仅 IsFlying 分支)。

05DS:权威世界重放

服务器不信任客户端上报的位置,只信任输入。它用同一套 PerformMovement → PhysWalking 在自己的世界里把这段输入重放一遍,因此贴地结果与 1P 同源但更权威。

ServerMovePacked_ServerReceive                          :9949
  └─ ServerMove_HandleMoveData                           :9994
       └─ ServerMove_PerformMovement (Old/Pending/New)   :10033
            ├─ VerifyClientTimeStamp                     :10061 → :9442
            ├─ GetServerMoveDeltaTime                    :10088
            ├─ MoveAutonomous(TimeStamp, DeltaTime, Flags, Accel)  :10119 → :10730
            │    └─ PerformMovement(DeltaTime)          :10758 → :2790
            │         └─ StartNewPhysics → PhysWalking  :3057 / :3554 / :5692
            └─ ServerMoveHandleClientError (仅 NewMove) :10130 → :10254

换句话说:1P 与 DS 跑的是同一个 PhysWalking 函数、同一组 FindFloor/AdjustFloorHeight 常量。差别只在两端世界的碰撞/其它角色状态不同,所以结果会有偏差——这正是 ServerMoveHandleClientError 要仲裁的东西。

服务器每帧的 GatherCurrentMovement(ActorReplication.cpp:438)把当前 ReplicatedMovement.Location 写进复制属性,其中 Z 就是服务器 AdjustFloorHeight 之后的权威贴地高度。ACharacter::GatherCurrentMovement(Character.cpp:1734)还会按需追加 Acceleration。这份数据以 COND_SimulatedOrPhysics 推给 3P(ActorReplication.cpp:593-596),主控端收不到它。

两条下行通道别混淆
发给主控端的是 ClientAdjustPosition / Ack(RPC,带时间戳与权威状态,用来维护 SavedMoves);发给 3P 的是 ReplicatedMovement(属性复制,带权威 Z)。前者让 1P 纠正预测,后者让 3P 有贴地基准。

063P:SimulateMovement 贴地外推

3P 没有输入、没有 SavedMove。它先收服务器快照把 Capsule 放到权威位置,包间再用复制速度做有限外推,并用精简版的 FindFloor + AdjustFloorHeight 尽量贴地。

6.1 收包帧:先接受权威 Z

AActor::OnRep_ReplicatedMovement                  ActorReplication.cpp:186
  └─ ACharacter::PostNetReceiveLocationAndRotation  Character.cpp:2004
       ├─ bNetworkSmoothingComplete = false          :2019
       ├─ bJustTeleported |= (OldLocation != NewLocation)  :2020
       ├─ SmoothCorrection(Old, New)                  :2021 → CMC.cpp:8245
       └─ bNetworkUpdateReceived = true               :2024

SmoothCorrection 把 Capsule 直接 SetWorldLocationAndRotation(NewLocation, NewRotation)(:8326),差值记进 MeshTranslationOffset;Capsule 的 Z 此刻就是服务器贴地后的权威 Z。同帧 bNetworkUpdateReceived = true,下一帧 SimulateMovement 会据此处理 MovementMode / 重力 / Floor 刷新(:2249-2269)。

6.2 包间帧:SimulateMovement 的贴地链路

SimulateMovement:2208 的核心在 :2304-2417:

1
速度来源与门控
Velocity 来自 FRepMovement::LinearVelocity(由 APawn::PostNetReceiveVelocity 写入)。:2292-2300:若 bSimGravityDisabled 或"在地面且复制速度为零"(bZeroReplicatedGroundVelocity),直接把 Velocity 清零,避免没有后续包时一直往下滑。UpdateProxyAcceleration()(:2308 → :7018)取复制加速度,没有则用速度方向做"假加速度"给动画用。
2
外推位移
:2310-2315:MoveSmooth(Velocity, DeltaSeconds, &StepDownResult)(:6951)。在地面时它调 MoveAlongFloor(:6977),空中时 SafeMoveUpdatedComponent(..., bSweep=true)(:6982)。因为 MaintainHorizontalGroundVelocity 把地面速度 Z 清零,外推 Delta 基本是纯水平,终点 Z 继承起点 Z。
3
FindFloor
:2317-2333:仅当 IsMovingOnGround() || MOVE_Falling 且"速度 Z ≤ 0"(bShouldFindFloor)才 FindFloor(当前位置, CurrentFloor, Velocity.IsZero(), NULL);否则 CurrentFloor.Clear()。StepUp 若已算过地板则复用 StepDownResult.FloorResult。
4
分支:贴地 / 坠落 / 落地
不可走(:2347-2358)→ NewFallVelocity + SetMovementMode(MOVE_Falling);
可走且 IsMovingOnGround(:2360-2366)→ AdjustFloorHeight() + SetBaseFromFloor(CurrentFloor) ← 这就是 3P 的本地贴地;
Falling 且 FloorDist ≤ MIN_FLOOR_DIST(:2367-2373)→ SetPostLandedPhysics 落地;否则继续 NewFallVelocity。
5
收尾
UpdateCharacterStateAfterMovement / OnMovementUpdated / UpdateComponentVelocity(:2392-2412),bJustTeleported = false,缓存 LastUpdateLocation/Rotation/Velocity。之后 SmoothClientPosition(:8426)把 Mesh 偏移逐帧衰减。
原生 3P 贴地的准确表述
原生 UE 的 3P 有本地贴地(FindFloor + AdjustFloorHeight),但它:不消费输入、不调 CalcVelocity、不走 PhysWalking 的完整子步循环、不跑 CheckFall 的完整边缘流程。而且 AdjustFloorHeight:6395 明确跳过 SimulatedProxy 的 bForceNextFloorCheck 置位——地面状态的最终裁判仍是下一个网络包。
为什么 3P 不能只靠本地贴地
纯水平外推在上坡会被 sweep 截断而低于应贴地高度,在下坡会悬空(Z 继承起点)。本地 FindFloor/AdjustFloorHeight 能补一部分,但外推速度、地形、动态平台都与服务器有偏差,所以必须用服务器快照周期性拉回。3P 的贴地是「本地近似 + 权威兜底」。

07网络快照与 Z 的关系

阶段1P 的 ZDS 的 Z3P 的 Z
本地/权威物理帧PhysWalking → AdjustFloorHeight 后的预测 ZPhysWalking → AdjustFloorHeight 后的权威 Z上一次收包的权威 Z(Capsule),Mesh 另有平滑偏移
包间外推帧继续本地预测—(等下一个 move)MoveSmooth 纯水平 → Z 继承;原生再 FindFloor/AdjustFloorHeight 修正
收到服务器数据Ack 或 ClientAdjustPosition → 回滚 + 重放 SavedMoves,Z 被权威值覆盖—SmoothCorrection → Capsule 直拍到权威 Z,偏移转给 Mesh

7.1 SmoothCorrection 如何不破坏贴地

SmoothCorrection:8245 用 FScopedPreventAttachedComponentMove 阻止 Mesh 跟随,只把 Capsule 移到 NewLocation(:8315 / :8326),把 OldLocation - NewLocation 累加到 MeshTranslationOffset(:8279-8297)。位移超过 MaxSmoothNetUpdateDist 会限幅、超过 NoSmoothNetUpdateDist 直接放弃平滑(:8289-8293)。所以贴地判定用的 Capsule 始终是权威位置,玩家眼睛看到的 Mesh 是被平滑后的视觉位置。

7.2 动画怎么读到这些

动画蓝图通过 TryGetPawnOwner() 读 Velocity / MovementMode(ReplicatedMovementMode → ApplyNetworkMovementMode)/ IsFalling();ACharacter 用 Mesh->PrimaryComponentTick.AddPrerequisite(CharacterMovement, ...)(Character.cpp:152-156)保证动画在移动组件之后更新。贴地修正带来的小幅 Z 抖动不会改变 MovementMode,动画状态机因此保持稳定。

081P vs 3P 贴地对照表

维度1P 主控端3P 模拟端
入口PerformMovement → StartNewPhysics → PhysWalking(:3057 / :3579 / :5692)TickComponent → SimulatedTick → SimulateMovement(:1807 / :1898 / :2208)
输入玩家输入 → Acceleration → CalcVelocity(:5756)无输入;Velocity 来自 FRepMovement.LinearVelocity
位移MoveAlongFloor 含 ComputeGroundMovementDelta 投影、StepUp 上台阶、SlideAlongSurfaceMoveSmooth:地面走 MoveAlongFloor,空中 SafeMoveUpdatedComponent + slide;无 CalcVelocity
FindFloor每子步后 FindFloor(..., bZeroDelta, NULL)(:5814),默认 bAlwaysCheckFloor=true 每帧真做仅 bShouldFindFloor(速度 Z ≤ 0)时调用(:2320-2329),可用 StepDownResult 复用
AdjustFloorHeight:5871,并置 bForceNextFloorCheck = true:2364,不置 bForceNextFloorCheck(:6395 对 SimulatedProxy 跳过)
离开地面CheckFall → StartFalling → MOVE_Falling(完整边缘/悬崖处理)SetMovementMode(MOVE_Falling) + NewFallVelocity(:2347-2357),无 CheckFall
落地ProcessLanded / SetPostLandedPhysics(:6413 / :6452)FloorDist ≤ MIN_FLOOR_DIST → SetPostLandedPhysics(:2369-2373)
位置权威本地预测值,可被服务器纠正(回滚 + 重放 SavedMoves)Capsule = 服务器快照;Mesh = 平滑后的视觉位置
贴地最终保证者本地 PhysWalking 闭环服务器快照的 Z + 下一个网络包;本地外推只是近似

09关键变量与 CVar 速查

变量 / CVar默认 / 定义作用
MIN_FLOOR_DIST / MAX_FLOOR_DIST1.9 / 2.4 cm
:99-100
贴地稳态区间;AdjustFloorHeight 只在超出时修正到中点 2.15。
bAlwaysCheckFloortrue
:799
为 true 时 FindFloor 每帧真做向下 sweep;false 且 base 稳定时复用缓存 CurrentFloor(:7313)。
bForceNextFloorCheck运行时标志
:7287-7289
强制下一帧重新检测;AdjustFloorHeight 后由非 SimulatedProxy 置位(:6397),收网络更新/传送/纠正后也会置位。
bJustTeleported运行时标志
:5717
本帧有瞬移/高度修正;影响速度反算与 FindFloor 是否跳过缓存。
bNetworkUpdateReceived运行时标志
:2249
3P 本帧收到属性更新时门控 MovementMode / 重力 / UpdateFloorFromAdjustment(:2269 → :10802)。
bSimGravityDisabled / bZeroReplicatedGroundVelocity:2292-22933P 速度为零时不再施加重力、不再外推,防止"卡住还继续滑"。
bNetworkSkipProxyPredictionOnNetUpdate
p.NetEnableSkipProxyPredictionOnNetUpdate
1(CVar)
:143-150, :2311
收包帧可以整帧跳过 MoveSmooth 外推,少一帧误差、代价是多一帧延迟。
MaxStepHeight / WalkableFloorAngle组件属性决定 CanStepUp 与 IsWalkable;直接影响 StepUp 与 FindFloor 的判定。
p.NetShowCorrections / p.NetCorrectionLifetime0 / 4s
:421-435
把主控端纠正位置画出来(红=错误、绿=纠正后),调试 1P 回滚重放时很有用。
Net.RepMovement.DrawDebug0
ActorReplication.cpp:184
画出 3P 收到的 ReplicatedMovement Capsule,直观看到权威 Z 与平滑 Mesh 的分离。

10PX 项目差异

本文前 9 章讲的是引擎原生。PX 项目在 UGPCharacterMovementComponent 里对贴地链路做了明显的性能化改造,其中最重要的一条是:3P 的本地贴地被整体移除。

改动原生 UEPX 项目
3P 包间贴地SimulateMovement 在 MoveSmooth 之后有 FindFloor(引擎 :2328) / AdjustFloorHeight(:2364)NewSimulateMovement(GP cpp ≈6716)在 MoveSmooth 后直接收尾;源码注释明确写「普通 SimProxy 不再本地推断地面状态…禁止再触发 FindFloor/高度图/AdjustFloorHeight」(≈5974-5980)。贴地完全交给下一个网络包(Docs/PerfExplore/3p-movesmooth-sweep-skip/01-clarify.md 关键事实 #3/#4)。
收包帧落位SmoothCorrection 用 SetWorldLocationAndRotation 移动 Capsule项目 NewSmoothCorrection 用 SetWorldLocationAndRotationNoPhysics 无 sweep 直拍;英雄「Z 立即接受权威位置、水平误差转软补偿」。
FindFloor 频率bAlwaysCheckFloor = true,每帧检测按 IsLocallyControlled() 分频:Local1PTime = 0.016(1P 60fps)/ Local3PTime = 0.033(3P 30fps)(GP cpp ≈12350 / 12447,白皮书 §5.1)。
AdjustFloorHeight纯物理 sweep 结果驱动项目 override(GP cpp ≈15882)加入高度图交叉验证、上楼梯 Z 振荡修复 A1/A2/A3(FloorDist 归一、跳过 LineDist 强同步、MoveDist 幅度钳制)。
回退通道无px.SimProxy.MovementEnginePure = 1(GP cpp ≈691-697)可一键把 SimulateMovement / MoveSmooth / FindFloor / AdjustFloorHeight / SmoothCorrection 等整条 SimProxy 链路回退到 UE 原生实现。
后果(项目文档已确认)
包间外推 Delta = Velocity * DeltaSeconds,地面模式速度 Z 被清零 → 终点 Z 完全继承起点:平地成立、上坡被 sweep 截断而不贴地、下坡悬空。由于收包帧本来就是无 sweep 直拍,这个行为差异在项目里被接受,代价由「下一个网络包」承担。想对比本文的原生行为,把 px.SimProxy.MovementEnginePure 置 1 即可。

口径提醒:白皮书 03_Movement_Technical_Whitepaper.md §5.1/§5.2 含示意性伪代码(如 PhysUpdateOnSimulatedProxy、InterpolatePosition),不能当引擎实现引用;本文一律以 F:\PX_Trunk\UE 的源码行号为准。项目侧文件在分析期间被并发编辑,行号以函数名为准。

11函数索引与代码位置

除特别注明外,行号均位于 F:\PX_Trunk\UE\Engine\Source\Runtime\Engine\Private\Components\CharacterMovementComponent.cpp(14103 行)。

阶段函数行号作用
通用贴地PerformMovement:2790物理帧总入口,:3057 调 StartNewPhysics
StartNewPhysics:3554:3579 MOVE_Walking → PhysWalking
PhysWalking:5692walking 主循环(:5726 子步迭代)
MaintainHorizontalGroundVelocity:5674把速度投影到重力水平面,Z 清零
MoveAlongFloor:5591沿地面法线投影移动,:5602 sweep,:5642 StepUp
FindFloor:7261:7287 缓存门控、:7290 ComputeFloorDist、:7326 Perch
ComputeFloorDist:7110:7177 向下胶囊 sweep,回退 line trace
AdjustFloorHeight:6335:6362 区间判定、:6367 MoveDist、:6397 强制下帧检测
台阶/边缘StepUp:7613:7680 上、:7691 前、向下扫回;失败 RevertMove
CheckFall:5432走出边缘 → StartFalling
StartFalling:5458:5474 SetMovementMode(MOVE_Falling)
ProcessLanded / SetPostLandedPhysics:6413 / :6452落地转回 Walking
DSServerMove_PerformMovement:10033逐 move 校验并重放,:10119 MoveAutonomous
MoveAutonomous:10730:10758 PerformMovement,与 1P 同一套物理
AActor::GatherCurrentMovementActorReplication.cpp:438写 ReplicatedMovement.Location(含权威 Z)
3PSimulateMovement:2208贴地外推主链路 :2304-2417
MoveSmooth:6951:6977 地面 MoveAlongFloor / :6982 空中 sweep
UpdateProxyAcceleration:7018复制加速度,否则用速度方向兜底
UpdateFloorFromAdjustment:10802收包后刷新 CurrentFloor(:2269 调用)
ACharacter::PostNetReceiveLocationAndRotationCharacter.cpp:2004:2021 调 SmoothCorrection
SmoothCorrection / SmoothClientPosition:8245 / :8426Capsule 直拍 / Mesh 偏移逐帧衰减
AActor::OnRep_ReplicatedMovementActorReplication.cpp:186RepNotify 入口,:277-278 调 PostNetReceive*
闭环ClientAdjustPosition_Implementation:11295:11451 置 bUpdatePosition
ClientUpdatePositionAfterServerUpdate:8667回滚 + 重放未确认 SavedMoves
版本与口径
引擎基准为 F:\PX_Trunk\UE\Engine\Build\Build.version 标定的 UE 5.8.2(Branch UE5)。该份源码被 PX 项目插入了 CODE_WALK_STATS 等统计插桩(例如 :5694),并在 :8623-8636 有一处对 3P Mesh 平滑的实质改动,但贴地物理链路本身与原生一致。项目业务层的覆盖集中在 F:\PX_Trunk\PX\Source\GPFramework\GPGameplay\Character\Movement\,见第 10 章。