00一句话总览
UE 的"贴地"不是把角色直接放在地面上,而是允许胶囊底部悬浮在一个很窄的区间(1.9 ~ 2.4 cm)内,靠每帧 FindFloor 向下探测 + AdjustFloorHeight 把这个距离拉回区间中点。1P 和 DS 跑的是同一套完整 PhysWalking 物理;3P 没有输入,只按复制速度做 MoveSmooth 外推,再用精简版的 FindFloor + AdjustFloorHeight 贴地,最终高度由服务器快照兜底。
PerformMovement → StartNewPhysics → PhysWalking:CalcVelocity → MoveAlongFloor(含 StepUp)→ FindFloor → AdjustFloorHeight → SetBaseFromFloor。每帧本地算完,画面立即响应。ServerMove → MoveAutonomous → PerformMovement → 同一个 PhysWalking 在服务器世界重放。服务器的 AdjustFloorHeight 结果写进 ReplicatedMovement.Location.Z,成为所有客户端的高度基准。SmoothCorrection 把 Capsule 直拍到权威位置;包间 SimulateMovement 用复制速度 MoveSmooth 外推,再 FindFloor / AdjustFloorHeight 贴地;下一个网络包纠正残差。- 贴地 ≠ 落地:
AdjustFloorHeight是"保持贴地"(MOVE_Walking 内),SetPostLandedPhysics是"从空中落地"(MOVE_Falling → Walking)。 - 1P/DS 与 3P 用的不是同一个入口:前者
PhysWalking,后者SimulateMovement;后者不调CalcVelocity、不做完整的StepUp流程。 - Capsule 与 Mesh 分离:3P 上 Capsule 是权威位置,Mesh 因
MeshTranslationOffset被平滑;贴地判定只看 Capsule。
01动画沙盘:地形剖面与三端
上半部分是地形剖面(平地 → 台阶 → 高台 → 下坡),三个胶囊分别代表 1P / DS / 3P 的位置;下半部分是三泳道时序。切换「实现模式」可对比 原生 UE(3P 有本地 FindFloor/AdjustFloorHeight)与 PX 项目(3P 移除本地贴地,Z 完全继承)。
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 决定)。 |
bLineTrace | sweep 不可用(穿模/不可走斜面)时改用线检测,避免把墙"爬"上去(: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 一帧内逐步
deltaTime < MIN_TICK_TIME、无 Controller(3P 除外)、无 Query Collision 直接返回。bJustTeleported = false(:5717)。while (remainingTime >= MIN_TICK_TIME && Iterations < MaxSimulationIterations),用 GetSimulationTimeStep 把长帧拆成子步,长帧/掉帧时物理仍稳定。MaintainHorizontalGroundVelocity()(:5748 → :5674)把 Velocity 投影到重力水平面,贴地移动不允许有垂直速度;CalcVelocity(timeTick, GroundFriction, ...)(:5756)按输入加速度/摩擦/制动算水平速度;ApplyRootMotionToVelocity(:5760)。MoveAlongFloor(MoveVelocity, timeTick, &StepDownResult)(:5784 → :5591)。它用 ComputeGroundMovementDelta 把水平位移投影到当前地面法线(上坡自动抬升),再 SafeMoveUpdatedComponent(RampVector, ..., bSweep=true, Hit)(:5601-5602)。遇到障碍时:可走斜坡→二次投影;CanStepUp→StepUp;否则 SlideAlongSurface。StepUp 已经算过地板(StepDownResult.bComputedFloor)直接复用,否则 FindFloor(当前位置, CurrentFloor, bZeroDelta, NULL)。ShouldCatchAir 检查是否冲出边缘(是则 HandleWalkingOffLedge / StartFalling);否则 AdjustFloorHeight() + SetBaseFromFloor(CurrentFloor)。这一步才把 Z 拉回 2.15 cm 稳态区间。CheckFall(:5897 → :5432)→ StartFalling(:5458)→ SetMovementMode(MOVE_Falling)。仍在地面时 :5907-5915 用实际位移反算 Velocity,再 MaintainHorizontalGroundVelocity() 保持水平。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。
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:
Velocity 来自 FRepMovement::LinearVelocity(由 APawn::PostNetReceiveVelocity 写入)。:2292-2300:若 bSimGravityDisabled 或"在地面且复制速度为零"(bZeroReplicatedGroundVelocity),直接把 Velocity 清零,避免没有后续包时一直往下滑。UpdateProxyAcceleration()(:2308 → :7018)取复制加速度,没有则用速度方向做"假加速度"给动画用。MoveSmooth(Velocity, DeltaSeconds, &StepDownResult)(:6951)。在地面时它调 MoveAlongFloor(:6977),空中时 SafeMoveUpdatedComponent(..., bSweep=true)(:6982)。因为 MaintainHorizontalGroundVelocity 把地面速度 Z 清零,外推 Delta 基本是纯水平,终点 Z 继承起点 Z。IsMovingOnGround() || MOVE_Falling 且"速度 Z ≤ 0"(bShouldFindFloor)才 FindFloor(当前位置, CurrentFloor, Velocity.IsZero(), NULL);否则 CurrentFloor.Clear()。StepUp 若已算过地板则复用 StepDownResult.FloorResult。NewFallVelocity + SetMovementMode(MOVE_Falling);可走且 IsMovingOnGround(:2360-2366)→
AdjustFloorHeight() + SetBaseFromFloor(CurrentFloor) ← 这就是 3P 的本地贴地;Falling 且 FloorDist ≤ MIN_FLOOR_DIST(:2367-2373)→
SetPostLandedPhysics 落地;否则继续 NewFallVelocity。
UpdateCharacterStateAfterMovement / OnMovementUpdated / UpdateComponentVelocity(:2392-2412),bJustTeleported = false,缓存 LastUpdateLocation/Rotation/Velocity。之后 SmoothClientPosition(:8426)把 Mesh 偏移逐帧衰减。FindFloor + AdjustFloorHeight),但它:不消费输入、不调 CalcVelocity、不走 PhysWalking 的完整子步循环、不跑 CheckFall 的完整边缘流程。而且 AdjustFloorHeight:6395 明确跳过 SimulatedProxy 的 bForceNextFloorCheck 置位——地面状态的最终裁判仍是下一个网络包。FindFloor/AdjustFloorHeight 能补一部分,但外推速度、地形、动态平台都与服务器有偏差,所以必须用服务器快照周期性拉回。3P 的贴地是「本地近似 + 权威兜底」。07网络快照与 Z 的关系
| 阶段 | 1P 的 Z | DS 的 Z | 3P 的 Z |
|---|---|---|---|
| 本地/权威物理帧 | PhysWalking → AdjustFloorHeight 后的预测 Z | PhysWalking → 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 上台阶、SlideAlongSurface | MoveSmooth:地面走 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_DIST | 1.9 / 2.4 cm :99-100 | 贴地稳态区间;AdjustFloorHeight 只在超出时修正到中点 2.15。 |
bAlwaysCheckFloor | true: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-2293 | 3P 速度为零时不再施加重力、不再外推,防止"卡住还继续滑"。 |
bNetworkSkipProxyPredictionOnNetUpdatep.NetEnableSkipProxyPredictionOnNetUpdate | 1(CVar) :143-150, :2311 | 收包帧可以整帧跳过 MoveSmooth 外推,少一帧误差、代价是多一帧延迟。 |
MaxStepHeight / WalkableFloorAngle | 组件属性 | 决定 CanStepUp 与 IsWalkable;直接影响 StepUp 与 FindFloor 的判定。 |
p.NetShowCorrections / p.NetCorrectionLifetime | 0 / 4s :421-435 | 把主控端纠正位置画出来(红=错误、绿=纠正后),调试 1P 回滚重放时很有用。 |
Net.RepMovement.DrawDebug | 0 ActorReplication.cpp:184 | 画出 3P 收到的 ReplicatedMovement Capsule,直观看到权威 Z 与平滑 Mesh 的分离。 |
10PX 项目差异
本文前 9 章讲的是引擎原生。PX 项目在 UGPCharacterMovementComponent 里对贴地链路做了明显的性能化改造,其中最重要的一条是:3P 的本地贴地被整体移除。
| 改动 | 原生 UE | PX 项目 |
|---|---|---|
| 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 | :5692 | walking 主循环(: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 | |
| DS | ServerMove_PerformMovement | :10033 | 逐 move 校验并重放,:10119 MoveAutonomous |
MoveAutonomous | :10730 | :10758 PerformMovement,与 1P 同一套物理 | |
AActor::GatherCurrentMovement | ActorReplication.cpp:438 | 写 ReplicatedMovement.Location(含权威 Z) | |
| 3P | SimulateMovement | :2208 | 贴地外推主链路 :2304-2417 |
MoveSmooth | :6951 | :6977 地面 MoveAlongFloor / :6982 空中 sweep | |
UpdateProxyAcceleration | :7018 | 复制加速度,否则用速度方向兜底 | |
UpdateFloorFromAdjustment | :10802 | 收包后刷新 CurrentFloor(:2269 调用) | |
ACharacter::PostNetReceiveLocationAndRotation | Character.cpp:2004 | :2021 调 SmoothCorrection | |
SmoothCorrection / SmoothClientPosition | :8245 / :8426 | Capsule 直拍 / Mesh 偏移逐帧衰减 | |
AActor::OnRep_ReplicatedMovement | ActorReplication.cpp:186 | RepNotify 入口,: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 章。