00一句话总览与图集
WalkingSurface 的核心思路是:把静态可走地形的几何信息在 Editor 里离线烘焙成紧凑的只读结构,运行时用一次缓存友好的数据结构查询替代 UE 的 ComponentSweepMulti 物理扫掠。收益来自"预计算 + 缓存局部性 + 早退剪枝",代价是必须严格界定"什么时候这个结果可信",不可信就回退 UE。
R + L 因而又长又高」的空间关系。另有基于 Three.js 的 WalkingSurface 3D 几何图示(单文件、内嵌 Three.js、可拖拽旋转、切俯视/侧视、切图层、播放胶囊动画)。它按 UE 的 Z-up 坐标系建模,1 单位 = 1 m,所有几何按 Types.h:495-529 / Runtime.cpp:2715-2811 的口径绘制。FindFloor / ProbeForward / MoveAlongFloor「三接口」是 v5–v7 时代的旧设计。当前代码(kBuildVersion = 13)中 ProbeForward、MoveAlongFloor 的运行接口,以及 FEdgePrim / EdgeBVH / FWS*Result 等结构全库 0 命中(Types.h:113-115、00-brief.md:38-40):前者已被 SweepHorizontal(水平胶囊扫掠 + 烘焙线索)取代,后者只剩枚举与 StepLength / MinProgressEps 配置残留。当前生产实现 = FindFloor + SweepHorizontal(对外由 FWalkingSurfaceMove 的 9 个静态方法暴露)。本文第 6 章保留旧设计用于说明方案演进,并明确标注「已退役」。MoveSmooth 的那一次 SafeMoveUpdatedComponent(Delta, bSweep=true)——scope 内唯一一次带 sweep 的移动,也是 SweepMulti 稳态耗时的主要来源。收包帧本来就是无 sweep 直拍,不在此列。Clear 直接无 sweep 落位,Hit 截断落位,Fallback 回退 UE。IsAuthoritativeFloor 再对 FindFloor 结果做一次稳态区间校验。图集索引
01烘焙侧:数据从哪来
运行时所有"快"都建立在 Editor 期的离线预计算上。烘焙器把场景中符合 Profile 的静态几何转成可查询的 Patch / Tile,并额外产出精度块与水平线索。图 10 / 图 11 / 图 12 与动图 7 先给出烘焙产物的空间几何形态(俯视与剖面,含线索生成),图 8 与动图 5 给出完整流水线,图 9 与动图 6(第 5 章)展开线索的运行时判定。
FGroundPatch 平面片(FPlane FitPlane + int32 世界 cm 顶点)、Boundaries[0]=Outer(CCW) 与几何洞 (CW),以及 Grid → Marching Squares → Simplify → BuildRings → 凸剖分 / QueryBVH 的几何演化。BuildPatchPrecision 补顶点偏差)→ 阻挡边(旧「净空轮廓」已退役 vs 当前 G1 真实 [Zlo, Zhi] 足迹)→ FGroundClue 的墙脚接触边与模拟墙 Z 带。FGroundClue 烘焙生成几何:接触带(本格 + 8 邻格 FindFloor 凸块叶盒紧贴地面上方一层)→ 源面 XY 投影凸包边界 → 相邻面片共用边成对抵消 → 共线段合并 → ClampEdgesToGroundLayers 收成模拟墙(不留真实高度)→ uint16 量化落盘。下方对照 FBlockEdge(离线 G1 校验夹具,真实 MinZ/MaxZ)与 FGroundClue(生产落盘,模拟墙 Z 带)。Baker::Bake 两阶段分桶(轮 1 MergeOnly → 轮 2a ToStepA3 → 全局复判 → 轮 2b AfterStepA3)→ CompactWallEdges / FinishBuild → 落盘。右侧为 G1 几何驱动阻挡边的 B1~B6 六阶段。1.1 数据流
FWalkingSurfaceBuilder::BuildFull / BuildIncremental ← EditorModule 绑定 GetBuildEntry() delegate
└─ FromDataActor(拉取 Build_* 设置)
└─ FWalkingSurfaceBaker::Bake(World, DataActor) ★ 主烘焙,两阶段分桶
├─ 轮1 MergeOnly:三角面采集 → 可走面过滤 → 建全量平面池
├─ 轮2a ToStepA3 → 全局复判 → 轮2b AfterStepA3
├─ Grid 20cm / Tile 3200cm(32m,160×160 格)
├─ 边界:BoundarySimplify / WallEdgeAligner / ObstacleEdgeBake
└─ CompactWallEdges → FinishBuild
├─ Horizontal::BuildPatchPrecision → 精度块 + 水平线索
├─ RunPostBakeFindFloorValidation
└─ MarkPackageDirty(随 .uasset 保存)
↓ 落盘(kBuildVersion = 13 / kPrecisionLayoutVersion = 18)
AWalkingSurfaceData(Patches + Tiles + Precision + Clues)
↓ Runtime Build
FWSHotArena(见第 2 章)
1.2 关键产物
| 产物 | 内容 | 被谁消费 |
|---|---|---|
FGroundPatch | 地面多边形 outer(世界 XY 顶点)、拟合平面 A/B/C、法线;NoHole 方案下只含 outer | FindFloor 的平面求 Z、SweepHorizontal 的 patch 扫掠 |
FGroundTile | Tile 坐标、凸块记录、量化参数 QuantOrigin / QuantFactor | 按 XY 的 O(1) 定位 |
| 精度块 | 每 patch 拟合误差界、顶点偏差、模型偏差界;B5 槽位预展开(O(1) 取值) | 临界接触判定、PrecisionBudget 回退 |
| 水平线索 Clues | FGroundClue(12 B,真实接触边 + Z 带)+ FGroundClueNode BVH(16 B);按 tile 组织,运行时由 ClueTileGrid_ 扁平网格直接寻址 | IntersectsClues,仅在首命中邻域内查询 |
版本与数据模型:kBuildVersion = 13(v6 起 Tile 由 16m 改为 32m,需重 Bake)、kPrecisionLayoutVersion = 18(精度块独立布局版本,旧精度块整块跳过而 Patches/Tiles 照常读取)。数据模型是平面片(FPlane 拟合平面 + int32 世界 cm 的 XY 顶点),不是栅格高度图;边界为 Boundaries[0] = Outer(CCW) + 几何洞。量化 0.5cm,无 LOD、无通用压缩。注意 Runtime.h:32 注释里的「1600cm / 16m」是 v6 之前的旧描述,实际常量为 3200cm(见第 6 章「注释漂移」)。
BlockEdge 的两代语义(容易混淆):v5–v12 的 FGroundPatch::BlockEdges 由「Block cell 净空轮廓」派生,专供已退役的 ProbeForward / FEdgePrim,v13 已删除;当前生产形态的「G1 几何驱动重设计」是 WalkingSurfaceObstacleEdgeBake 的 FBlockEdge(直接取自真实非可走碰撞面的真实 Z 区间),再收敛为烘焙线索 FGroundClue + FGroundClueNode BVH。G1 的烘焙阶段:B1 采集 N.z < 0.71 的非可站立面(含朝下面)→ B2 面合并 → B3 足迹投影(真实 Z 区间 / 朝外法线,无栅格拉直)→ B4 可达性裁剪(唯一剔除规则)→ B5 共线合并 → B6 新结构 + 版本升级(01a-blockedge-audit.md:73-89)。
Runtime.h:26-28 明确写:FGroundPatch::AllVertices 实际是世界 XY,类型头注释里的"UV 局部坐标"是历史遗留描述,Runtime 一律按世界 XY 处理。烘焙与运行时的 Tile 尺寸必须一致(Runtime.h:31-38)。1.3 增量与重建
Editor 侧还有 DirtyTileTracker / GridBoundaryRebuild / BakerIncremental 支撑"只重建脏 tile",以及 Stepwise 分步烘焙、CoplanarMerge 共面合并(夹角容差 5°)、PatchAssemble 组装。烘焙侧还有 Stepwise 分步(单步已删,仅合并模拟)、DirtyTileTracker 的 ClosureMask 增量闭包、Co-planarMerge 共面合并(夹角容差 5°)、PatchAssemble 组装。这些不影响运行时查询语义,但决定了重 Bake 后旧 handler 如何失效:Runtime 用 BuildGeneration_ 代际号让 per-agent 缓存自动作废(QueryTypes.h:56),旧 handler 的 sweep 回退、原 FindFloor 继续用一致快照(MoveInterface.h:59-62)。
02热数据:为缓存行而设计
这是整个优化最"硬核"的部分:所有数据结构都围绕减少 cache miss 与指针追踪设计。目标是把逐帧查询从"多次散列 + 多次 scatter 读取"变成"一次连续内存上的顺序访问"。
2.1 一次连续分配(FWSHotArena)
// WalkingSurfaceRuntime.h:303-335(节选)
struct FWSHotArena
{
uint8* Ptr = nullptr; SIZE_T Size = 0, Used = 0; bool bLargePages = false;
void Allocate(SIZE_T Bytes, bool bTryLargePages); // Windows: MEM_LARGE_PAGES,失败回退 64B 对齐
};
// 布局:[稠密热表][tile0 QueryBVH | tile0 凸块 blob][tile1 ...] ← 一次分配,无拷贝
不可拷贝(内部视图指针指向本 arena),move-only。大页减少 TLB miss;连续布局让相邻 tile 的热数据物理相邻。
2.2 三条缓存行级优化
| 结构 | 做法 | 收益 |
|---|---|---|
FWSTileHot = 64 B | static_assert(sizeof(FWSTileHot) == 64)(Runtime.h:301);8 个 8B 槽放 tile 坐标 / BVH 偏移 / blob 偏移 / 精度槽位 | 一次访存取齐整块元数据,避免多次指针追踪 |
| 4-ary BVH 节点 16 B | uint16 BMin[3]/BMax[3] 量化 3D AABB + int32 i;4 个连续子节点 = 64 B 对齐(TAlignedHeapAllocator<64>);i ≥ 0 叶子、i < 0 内部用相对前向偏移(Runtime.h:66-89) | 每层一次访存测 4 个子节点;树深 log4 vs log2 减半;Z 与 XY 同量化体系 → 枝级 Z 剪枝 |
FConvexRecHeader 24 B + 顶点 blob | 记录头(PatchIndex / PlaneA/B/C / VertCount / OuterPrimIdx)与其顶点连续存放;QueryBVH 叶子 i 直接存 blob 槽偏移(Runtime.h:122-141) | 叶子 → 记录头 → 顶点一跳访存(旧布局 prim 数组 → 顶点池是两跳) |
2.3 热路径零浮点(FWSInlinePoly)
// Runtime.h:91-108
struct FWSInlinePoly
{
TArray<FIntPoint, TInlineAllocator<16>> Verts; // int32 世界 cm(8B/顶点,原 double 16B)
FIntPoint Center; int64 R2; // 包围圆半径平方,早退用
TArray<int64, TInlineAllocator<16>> EdgeSlopeX; // ((V[i].X-V[i-1].X)<<20)/(V[i].Y-V[i-1].Y)
};
// 量化误差 ≤ 0.5cm ≪ 烘焙 20cm grid;射线法全程 int32/int64,热路径无浮点
≤16 顶点内联存储,消除堆指针追踪的 cache miss;平面系数在 Build 期一次性折算为 A=−X/Z, B=−Y/Z, C=−W/Z,查询期只做 float 乘加、无 double、无除法,法线命中时现算一次(Runtime.h:110-120)。
2.4 索引与缓存
- 稠密 tile 索引:
DenseTileIndex_规则网格直接寻址替代TMap哈希;TileIndex_仅作覆盖范围过大时的兜底(Runtime.h:602-610)。线索同理有扁平网格ClueTileGrid_,无哈希。 - per-agent 查询缓存:
FWalkingSurfaceQueryCache { LastTile, LastPatch, Generation }(QueryTypes.h:52-63)。Runtime 本体无 mutable 成员、无 agent 状态,因此查询可多线程并发;hint 短路失败自动回退完整路径,几何验证保证正确性。 - 查询预热:
WarmupQueryData(Center, FootRadius)(Runtime.h:504-515)把足迹覆盖 tile 批量预取至 L1/L2,把串行 miss 转成并行加载。源码注释也诚实指出:数据预取无法消除 I-cache/BTB 的逐帧首次调用冷启动税,那是实测中耗时的主体。
03FindFloor 查询链
FindFloor 用世界 XY 定位到一个 Tile,再在 4-ary BVH 里剪枝出候选凸块,最后用平面解析式求 Z——全程没有物理 sweep、没有 double 除法。
lastPatch 短路与 LineTrace / Capsule 两种模式的取舍。3.1 两种查询模型
| Mode | 做法 | 适用与风险 |
|---|---|---|
LineTrace | 以 agent 中心 XY 向下 trace,窗口 [Start.Z−DownDist, Start.Z+2cm];多候选取窗口内最高 Z(向下首个命中),同 Z ±0.5cm 保留先入选者(Runtime.h:462-476) | 最快;天然区分上下层(桥上只命中桥)。风险:球心悬空(悬崖边/洞口)会误判可走 |
Capsule | 胶囊足迹 AABB(球心 ± R)一次查询 + 逐 patch 解析求 Zsweep = Zplane + R·(|N|−1) 择优;闭式解而非多点采样;足迹内接触求解完备(面 / 边 / 角接触)(Runtime.h:478-502) | 语义对齐 UE 胶囊 sweep 停位,修正点查询的悬空误判;输出 Location 与 ImpactPoint 双点分离(见第 4 章) |
3.2 回退契约
false 对上层可见即「需要回退」CharacterMovementComponent::FindFloor / 真实胶囊 sweep;上层也可按自身语义直接当作无地面(如 MoveAlongFloor 停止前进)。这是"不可信就交给 UE"的分层设计。3.3 精度消费三路径
EPrecisionPath(Runtime.h:381-393):B3Only(逐顶点线性扫,仅作开销参照)/ Reference(B5 慢路径,逐顶点取偏差与模型偏差界)/ Fast(B5 槽位预展开,O(1) 取值,判定结果与 Reference 逐位一致)。默认 Fast——这是"精度不降、查询变 O(1)"的典型手法。
04Capsule FindFloor:Zsweep 与双点分离
要让 WS 的地面结果能直接透传给 CMC 消费,必须复刻 UE 胶囊 sweep 的几何语义,而不只是"采样一个地面高度"。
Location 是胶囊中心停位,ImpactPoint 在上坡侧真实接触点,两者必须分离。// WalkingSurfaceQueryTypes.h:16-40(字段语义)+ Runtime.h:481-489(Capsule 模式)
Zplane = A·x + B·y + C // 拟合平面
N = normalize(−A, −B, 1)
Zsweep = Zplane + R·(|N| − 1) // 胶囊底部触地时的底部 Z(内部择优基准)
Location.Z = Zsweep + AgentHalfHeight − ShrinkHeight
ImpactPoint.Z = Zimpact = Zplane + R·(secθ − cosθ) ⟺ Zsweep + R·(1 − cosθ)
ShrinkHeight = max(AgentHalfHeight − AgentRadius, 0) × 0.1f // 对齐 UE ComputeFloorDist 的 ShrinkScale=0.9
MoveAlongFloor 用 ImpactPoint.Z 追踪脚下地面,ShouldComputePerchResult / ComputePerchResult 用 ImpactPoint − Location 判 perch radius,IsValidLandingSpot 用 Location 作胶囊中心算下半球与边缘容差。若两者相等,DistFromCenter = 0 会恒过校验。此外 FWalkingSurfaceFindFloorParams(QueryTypes.h:143-161)提供 bMatchUEImpactNormal:命中法线不可站立时按 UE FindMostOpposingFace 口径改取接触点附近最平的 patch 法线(停位不变)。GP CMC 走 IsAuthoritativeFloor 采信规则,保持 false。
05SweepHorizontal:只读查询替代物理扫掠
这是"优化实现"的主角,也是旧设计中 ProbeForward 的当前等价物。只读复用 QueryBVH / ConvexBlob + 精度槽位 + 烘焙线索,不调用 FindFloor、不改 Z,返回 Clear / Hit / Fallback 三态。
EWalkingSurfaceSweepFallback 回退原因。FGroundClue 12 B(量化端点 + ZLo/ZHi)、FGroundClueNode BVH 16 B、贴地窗口 [脚底, 脚底 + R + 位移],以及 IntersectsClues 的四步判定与 ReachSq 余量构成。VerticalSurface 回退 UE)。5.1 求解链(Runtime.h:526-534)
Delta.Z == 0、胶囊 Query 碰撞开启、非 bTraceComplexOnMove、烘焙通道与响应与胶囊一致(GP CMC :11377-11413)。IntersectsClues(Query, MaxDistance, ...)(Runtime.h:409-417):MaxDistance = 首命中距离 + kCriticalContactTolCm。检查是否存在 Z 区间与贴地窗口 [脚底, 脚底+R+位移] 相交、且沿位移越走越近的接触边(墙脚 / 台阶侧面 / 未建模几何)。把昂贵的线索查询限制在极小邻域是这一步的关键优化。Unknown + 回退原因(调用方改做原 sweep);否则 Clear(全程无阻挡)或 Hit(命中可走 patch 面,带 Time / 停位 / 法线)。Runtime.cpp:3495-3499 / :4168 的当前实现是先查线索 BVH、触到即回退(ClueFirst = 1),Runtime.h:529-530 的「patch 先」注释已漂移。本文图示按「邻域剪枝」的设计意图绘制,文字以实码为准:线索命中即回退 → 否则 patch 扫掠 + 精度判定 → Clear/Hit。读专题文档时请按迭代时间戳区分。5.2 结果与回退类型
// WalkingSurfaceQueryTypes.h:163-194
enum class EWalkingSurfaceSweepResult : uint8 { Clear, Hit, Fallback };
enum class EWalkingSurfaceSweepFallback : uint8 {
None, MissingData, InvalidInput, OutsideCoverage,
VerticalSurface, // 前方触到竖直阻挡的接触边(墙脚)
UncollectedSurface, // 台阶侧面、陡坡、碎石等 patch 未表达的静态几何
SmallTriangle, StepA3Pruned, CoveredPruned, GridExcluded, UnsupportedGeometry,
NoGroundSupport, // 起点悬空 / 离开可走面
InitialOverlap, // 起点已与地面模型重叠
OuterBoundaryContact, // 首命中落在 patch 外边 / 顶点上
PrecisionBudget, RoundShape, BudgetExceeded
};
FWalkingSurfaceSweepHit(QueryTypes.h:196-215)字段与 UE FHitResult 同口径:Time / Distance / Location / ImpactPoint / Normal / ImpactNormal / bBlockingHit / bStartPenetrating。但不含 Actor / Component / PhysMaterial / FaceIndex——上层若需要这些元数据,必须回退 UE。
5.3 对外接口
FWalkingSurfaceMove(MoveInterface.h:67-128)是纯静态工具类;FWalkingSurfaceMoveHandler 是 move-only 的不透明句柄,内部独占 Runtime 共享引用、agent 参数缓存与 per-agent 查询缓存。公开方法:Register / Unregister / UpdateAgentShape / SetFindFloorParams / FindFloor / HasSweepData / GetSweepCollisionFilter / SweepHorizontal / SweepHorizontalWithFallback。
SweepHorizontalWithFallback 在 WS 给出 Clear/Hit 时直接填 OutHit,否则用同一胶囊做 World->SweepSingleByChannel。06MoveAlongFloor:单步迭代(已退役的旧设计)
本章是历史方案,不是当前生产实现。当前代码中 MoveAlongFloor 已退役,仅剩 EWalkingSurfaceMoveResult 枚举与 StepLength / MinProgressEps 配置残留;其「沿地面走一段」的职责由 SweepHorizontal + GP CMC 的落位逻辑承担(见第 5、8 章)。保留本章是为了说明方案演进与迭代常量的来龙去脉。
01-clarify.md 关键事实 #6 与 03-plan.md 的三落地形态 A/B/C 都是围绕这套迭代设计的;理解它被否决、被 SweepHorizontal 取代的原因,才能理解当前「独立水平查询、不与 FindFloor 混用」这条约束的来由。图中所有步骤均为旧设计语义。以下常量在当前代码中仍定义于 FWalkingSurfaceRuntimeConfig,但已无对应的 MoveAlongFloor 实现消费,属配置残留。
// WalkingSurfaceTypes.h:23-95 + Runtime.h:35-53
kGridCellSize = 20.f; // 烘焙栅格单格边长(cm),全局对齐
kTileWorldSizeX/Y = 3200.f; // 32m;v6 起由 16m 改为 32m(需重 Bake)
kTileGridSizeX/Y = 160; // 每 tile 160×160 格
StepLength = 100.f; // MoveAlongFloor 迭代步长(1m)
MinProgressEps = 1.f; // 单步允许位移 < 1cm 视为探测失败
kCollectSlopeCos = 0.015f; // ≈89.1° 采集阈值
kStandableSlopeCos = 0.71f; // 可站立法线余弦
kBuildVersion = 13; kPrecisionLayoutVersion = 18;
kMaxSweepLengthCm = 256.f; // Runtime.cpp:42-43 单次水平扫掠位移上限
Runtime.h:32 的注释仍写「默认 1600cm = 16m」,但 Types.h:27-28 的实际常量是 3200cm = 32m(v6 起改),本文以常量为准。同类漂移还有:QueryTypes.h:134-136 说 Capsule 模式「球心 XY 无覆盖时判无地面」,而 Runtime.cpp:5146-5151 实码已改为「不要求球心 XY 被可走 patch 覆盖」(旧门槛实测去掉后不引入差异,属对 LineTrace 中心悬空误判的历史修正)。专题 01-clarify.md 关键事实 #6 描述的旧设计:MoveAlongFloor 每步 = FindFloor 锚定 → ProbeForward 探测(圆柱中心 = 地面 + 半高)→ 允许位移 < MinProgressEps(1cm) 判失败 → 提交 XY → 终点再 FindFloor 锚定保证贴地;3P 外推每帧约 10cm ≪ StepLength 100cm,因此单次迭代;无滑墙、只截断,与 3P 现状行为同构。
FindFloorLineTrace 单点查询,不走 GP CMC 注册的 Capsule 模式——悬崖/洞口两口径结论不同(LineTrace 中心悬空可能误判可走,是漏报方向);② FindFloorDownDist 默认 200cm,下坡落差超窗 → Failed/Partial → 回退(安全方向);③ Failed 输出原地,回退信号清晰。EWalkingSurfaceMoveResult(QueryTypes.h:65-71):Completed(走完全程)/ Partial(被阻挡,OutNewLocation 为最远可达点)/ Failed(起步即失败,原地)。
07采信边界与回退
优化的成败不在"查得快",而在"知道什么时候不能信"。两道闸门:FindFloor 的 IsAuthoritativeFloor 与 SweepHorizontal 的 FallbackReason。
// WalkingSurfaceQueryTypes.h:95-127
constexpr float kFloorWalkableNormalMargin = 0.02f;
constexpr float kFloorTrustMinDist = 1.8f;
constexpr float kFloorTrustMaxDist = 2.5f;
FORCEINLINE bool IsAuthoritativeFloor(const FVector& HitNormal, float FloorDist, float WalkableFloorZ,
float TrustMinDist = 1.8f, float TrustMaxDist = 2.5f)
{
return HitNormal.Z >= WalkableFloorZ + 0.02f
&& FloorDist >= TrustMinDist && FloorDist <= TrustMaxDist;
}
为什么是 1.8~2.5:UE 的 AdjustFloorHeight 把稳态 FloorDist 维持在 [MIN_FLOOR_DIST 1.9, MAX_FLOOR_DIST 2.4],两端各放宽 0.1cm 浮点余量。稳态贴地时 UE 不做位移,WS 与 UE 等价;区间外(上坡/下台阶/下坡过渡帧)CMС 本来就要做 AdjustFloorHeight 物理 sweep,回退只增加同量级一次查询,且避免 WS 因看不到立面/陡坡而"悬停在坡上行走"或"停位偏高"。
7.1 GP CMC 里的实际采信/回退
| 情况 | 动作 | 代码位置 |
|---|---|---|
| WS Clear | SafeMoveUpdatedComponent(Delta, MoveQuat, false, OutHit) 直接落位,等价 MoveComponent 对 Clear sweep 的落位(无 PullBack、无阻挡派发) | GP CMC :11442-11449 |
| WS Hit 且可信 | IsTrustedWalkingSurfaceHit → MoveTime = PulledBackHitTime(WSHit.Time, Delta) → 截断落位 + MakeWalkingSurfaceMoveHit 回填 FHitResult | :11451-11465, :942-983 |
| WS Fallback / 不可信 Hit / 前置不满足 | SimProxyMoveSmoothSweepUE(SweepSingle 或原 SweepMulti),统计 Incompatible / HitRejected / NonHorizontal | :11382-11413, :11466-11480 |
| FindFloor 结果不权威 | IsAuthoritativeFloor 为 false → 返回 false → 回退 UE 原生 ComputeFloorDist | :11923-11928 |
| 悬空放宽(P5) | 前帧有支撑且当前悬空 ≤ 25cm → 查询 Start.Z 钳到前帧地面;只加速 Clear,Hit/回退照走 UE | :11416-11431, :304-314 |
08GP CMC 生产接入
专题 README 里"未接生产 GP"的结论已经过时:GPCharacterMovementComponent.cpp 里已经落地了完整接入,只是 CVar 默认 0(关闭),处于可随时灰度/回退的状态。
8.1 接入点
NewSimulateMovement → MoveSmooth → (deferred scope 内唯一一次带 sweep 的移动)
└─ if (!SimProxyMoveSmoothSweep_WalkingSurface(Delta, Hit)) // :11641
SimProxyMoveSmoothSweepUE(Delta, MoveQuat, Hit); // UE 路径
// 另一条独立接入(地面检测):
FindFloor_WalkingSurface(CapsuleLocation, DownDist, OutFloorResult, SweepRadius) // :11825
├─ EnsureWalkingSurfaceHandler() → FWalkingSurfaceMove::Register(..., Capsule) // :11761-11789
├─ FWalkingSurfaceMove::FindFloor(*Handler, WSStart, WSHit, WSDownDist) // :11884
└─ IsAuthoritativeFloor(...) → 不权威则回退物理 // :11927
8.2 CVar 矩阵(GP CMC :265-368)
| CVar | 默认 | 模式 |
|---|---|---|
gp.Movement.WalkingSurfaceHorizontalSweep | 0 | 0 关闭 / 1 Clear+Hit(完整替换)/ 2 仅测量(非 Shipping,统计 WS/UE 耗时与 Hit 一致性)/ 3 仅 Clear(旧 A/B 模式) |
gp.Movement.WalkingSurfaceFindFloor | 0 | 0 关闭 / 1 完全替换 FindFloor / 2 仅替换高度图失效时的回退调用 |
gp.Movement.SimProxySweepSingle | 1 | 0 原 SafeMoveUpdatedComponent(bSweep=true) / 1 用 SweepSingleByChannel 找首个阻挡后 bSweep=false 落位,避免收集沿途 touch |
gp.Movement.WalkingSurfaceHorizontalSweep.HoverTolCm | 25 | 悬空放宽阈值;0 关闭 |
gp.Movement.WalkingSurfaceFindFloor.TrustMinDist / TrustMaxDist | 1.8 / 2.5 | 采信区间,与 kFloorTrust* 一致 |
gp.Movement.WalkingSurfaceFindFloor.Compare* | 0 | WS vs UE 原生 sweep 屏幕对照(开关 / 容差 / 绘制 / ForceSync 位掩码),调试用 |
px.SimProxy.MovementEnginePure | 0 | 整条 SimProxy 链路一键回退 UE 原生实现(含 FindFloor / AdjustFloorHeight / SmoothCorrection) |
8.3 统计与观测
FWalkingSurfaceSweepRuntimeStats(GP CMC :11254-11276)累计 Calls / WSMove / UEMove / NonHorizontal / Incompatible / HoverRelaxed / HoverClear / HitConsumed / HitRejected 等,控制台命令 gp.Movement.WalkingSurfaceHorizontalSweep.Stats 打印耗时直方图。Runtime 侧另有 FSweepStats(SupportNodes / Convex / GapSkipped / Sweeps / NearMiss / BudgetHit)与 FClueQueryStats(Nodes / Candidates / PlanePruned / Exact / HitTile)用于开销归因(Runtime.h:415-425)。
09优化前后与实测
收益不是"WS 查询比一次 sweep 快多少"这么简单,而是净收益 = 干净帧占比 × (Sweep − WS 查询) − 回退帧占比 × WS 查询,还要扣掉 I-cache 冷启动税。
9.1 两条路径对照
| UE 原生路径 | WalkingSurface 路径 | |
|---|---|---|
| 数据来源 | 运行时物理场景(Chaos),逐帧查询 | Editor 离线烘焙的 Patches / Tiles / Clues |
| 查询方式 | ComponentSweepMulti + 强制收集 touch + 穿透处理 | 4-ary BVH 剪枝 → 凸块/平面解析 → 邻域线索查询 |
| 覆盖 | 静态 + 动态、复杂碰撞、材质/Actor/Component 元数据 | 仅静态、简单碰撞、仅几何结果(无 Actor/Component/PhysMaterial) |
| 落位 | 按 Hit 截断 + 沿途 overlap 派发 | Clear → 无 sweep 直落;Hit → PullBack 截断并回填 FHitResult |
| 失效处理 | 天然正确 | Fallback → 回退 UE;FindFloor 另有 IsAuthoritativeFloor 闸门 |
9.2 专题实测结论(3p-movesmooth-sweep-skip)
| 指标 | 数值 | 口径 / 来源 |
|---|---|---|
| 70K 语料对照 UE 静态胶囊 sweep | 假 Clear 0、多报 0 | MS4 地图,迭代 12 / B5 / B7 多轮一致(04-conclusion.md:15,45) |
| 采信命中停位误差 | max 0.0075 cm(p99 0.0031) | 迭代 12 ClueFirst=1,配对命中 4631 条 |
| 地面短步采信率(迭代演进) | 78.1% → 83.6% → 83.7% | 迭代 12(04-conclusion.md:50)→ 迭代 13(b7_result.md:41)→ b10(b10_result.md);目标 ≥90% 未达标(语料中 14% 为真碰台阶/陡面/竖面,上限约 86%) |
| 含回退的净开销(迭代 12) | 0.75× UE(地面) / 0.94×(全部) | Editor Development,reps=5;UE 静态 sweep 均值 11.0 / 4.8 µs(04-conclusion.md:16,51-53) |
| 后续收敛(迭代 13 / b7) | 采信查询 845 ns vs UE 4630 ns(0.18×);地面净开销 0.556× UE、全部 0.911× | MS4 70K,reps=5 / inner=8(b7_result.md:35-46) |
| tile 级线性扫 → 8×8 子格索引 | 23.8 → 8.2 µs(p50 7.5 → 0.7 µs) | 32m tile 内 8×8(4m)子格(b5_runtime_cost.md:28-34) |
| 生产形态总计 vs UE sweep | 23.9 → 8.45 µs vs 11.6 µs(首次 1.36× 快) | 「空夹具」Fast 路径(b5_runtime_cost.md:29-31、memory.md:45) |
| 迭代 25 留出集 / 冷协议 | 采信 ~70%、等价 A/B 全字段差异 0;采信总耗时 0.40× UE、全部查询净开销 0.84× UE | 2×20,000 独立种子,冷/热双协议(b22_review_retest.md:34-61) |
| 关键口径修正 | SweepHorizontal 整体 175~240 µs 中 99% 是 Editor 校验夹具 | 病态 scope 155 万图元,不是生产运行时开销(b5_runtime_cost.md:5-20、gotchas.md:28) |
03-plan.md 的结论台账给出:收益上限 = sweep 段占比 × 干净帧占比;#11 指出不 sweep 路径仍要付"无 sweep 落位 + scope 析构传播"成本;02-review.md 指出 SimProxy 胶囊 GenerateOverlapEvents=false 时收集到的 touch 被丢弃("白收集的 touch"),正是可优化掉的部分。主要折损:回退率、悬空帧(NoGroundSupport)、I-cache/BTB 冷启动税、动态障碍密集场景。· 目标「3p 稳态帧均下降 ≥50%」的真实实测数字:未找到;RF1 基线测量 0 有效样本后被用户标 defer;单客户端 PIE 场内无其它移动英雄导致接入点
calls = 0 无样本(rf-1-baseline.md:7,38-41、README.md:68)。· 「慢样本 0」只在 70K 语料 + 16.67cm 单步 + 热缓存 min-of-reps 成立;冷协议逐样本中位数约 1%(原始)/ 2.5%(包装) 慢于 UE,30fps 长步长(33.33cm)退化到净开销 0.97×UE(b22_review_retest.md:65-68)。
· 「UE sweep 11.6 µs」是 bench 参照口径,不可当作 3p 帧内分项。启用前应以
gp.Movement.WalkingSurfaceHorizontalSweep = 2(仅测量)在目标场景重新采回退率与耗时直方图。
10风险台账与当前状态
| 风险 | 表现 | 缓解 / 现状 |
|---|---|---|
| 动态障碍不参与 | 其它角色 / 小兵 / 可移动物不在烘焙数据内,包间可能视觉穿插直至下一个网络包 | 专题澄清记录 #3:人堆场景风险已接受;收包帧本来就是无 sweep 直拍,有现成参照 |
| BlockEdge 漏报方向 | 坡下地面也会出边;LineTrace 中心悬空可能误判可走 | 用户 2026-10-28 选定 G1 几何驱动重设计;GP CMC 默认注册 Capsule 模式规避点查询悬空 |
| 下坡落差超窗 | FindFloorDownDist 默认 200cm,超窗 → Failed/Partial → 回退(安全方向) | 回退契约保证正确性 |
| I-cache 冷启动税 | 逐帧首次调用的耗时主体是代码/数据冷启动,数据预取无法消除 | WarmupQueryData 只能缓解数据 miss;专题登记为 P2/P4 测量风险 |
| 碰撞契约不一致 | 地图未烘焙水平扫掠,或胶囊通道/响应与烘焙不符 | GetSweepCollisionFilter 比对,整局走原 sweep,每进程只记一次日志 |
| 元数据缺失 | WS 命中不含 Actor/Component/PhysMaterial/FaceIndex | 上层需要这些字段时一律回退 UE(MoveInterface.h:120-122) |
| 启用状态 | 专题 README:未正式启用、未接生产 GP、22 文件 3114 行未提交、地图未保存;迭代 26 曾封装 SimProxyMoveSmoothSweep_WalkingSurface 但「封装后版本尚未编译」、PIE calls=0 | 当前源码已落地完整接入(GP CMC :11348 起,含 2026-11-10 的 P5 悬空放宽),但 CVar 默认 0 = 生产未开启、真实帧内收益未验证;px.SimProxy.MovementEnginePure 可整链路回退 |
00-brief.md:15-22 全 TODO、04-conclusion.md:83)。另已登记:冷缓存 I-cache/BTB 税、30fps 长步长退化、SweepHorizontalWithFallback 包装层 p50≈285ns 固定开销、栈占用 40–60KB 的小栈线程风险、单面薄片 Chaos 背面剔除差异、旧精度块读失败连带清空 Patches/Tiles(已修)。GPCharacterMovementComponent.cpp 里已有 2026-11-04 / 11-10 的接入与"用户拍板默认启用悬空放宽"注释,说明实现后来继续推进过。本文以当前源码为准描述实现,以专题文档为准描述实验结论与风险,两者时间戳不同,不要混为"同一版本的状态"。11代码索引与常量
| 模块 | 文件 / 符号 | 位置 | 要点 |
|---|---|---|---|
| Runtime | WalkingSurfaceRuntime.h | :11-29 | 总注释:QueryBVH / 双路径 / 世界 XY 口径 |
FWalkingSurfaceRuntimeConfig | :35-53 | Tile 3200cm / Grid 20cm / StepLength 100 / MinProgressEps 1 | |
FWalkingSurfaceBVHNode / FWSInlinePoly | :66-120 | 4-ary 量化 BVH、int32/int64 热路径 | |
FWSHotArena / FWalkingSurfaceRuntime | :303-372 | 一次分配 / 只读并发 / Build | |
FindFloorLineTrace / FindFloor | :473-502 | 两种查询模型与回退契约 | |
SweepHorizontal / IntersectsClues | :409-417, :526-543 | 邻域线索剪枝、生产形态入口 | |
| Types | FWalkingSurfaceFloorHit | QueryTypes.h:16-40 | Location / ImpactPoint 双点分离 |
FWalkingSurfaceQueryCache | :52-63 | lastPatch 短路 + Generation 失效 | |
IsAuthoritativeFloor / kFloorTrust* | :95-127 | 法线余量 0.02 / 区间 1.8~2.5 | |
EWalkingSurfaceSweepResult / Fallback / SweepHit | :163-215 | 三态与全部回退原因 | |
| Interface | FWalkingSurfaceMoveHandler | MoveInterface.h:21-45 | move-only、per-agent 缓存 |
FWalkingSurfaceMove | :67-128 | Register / FindFloor / SweepHorizontal / WithFallback | |
| GP CMC | CVar 组 | :265-368 | 四个主开关 + 采信区间 + 对照调试 |
SimProxyMoveSmoothSweep_WalkingSurface | :11348-11560 | 3P 前向 sweep 替换主实现 | |
FindFloor_WalkingSurface | :11825-11960 | 地面检测替换 + 采信判定 | |
SweepRuntimeStats | :11254-11276 | 耗时直方图与判定计数 | |
| 专题 | 01-clarify.md 关键事实 #1~#7 | Docs/PerfExplore/3p-movesmooth-sweep-skip | 基线、贴地、BlockEdge、三接口 |
04-conclusion.md / memory.md | 同上 | 70K 对照、净开销、子格索引数字 |
ws.Horizontal.ClueFirst(默认 1)、ws.Horizontal.WalkableBudget、ws.Sweep.HullPrune、ws.Clue.CheapBound、ws.Bake.Clue.RepresentTolCm / RepresentCos / MaxRadius / MaxHalfHeight / MaxDelta、ws.Horizontal.PerfAudit、ws.Ground.FindFloorFingerprint、ws.Horizontal.ProdRegression、ws.Precision.RoundTrip、gp.Movement.WalkingSurfaceHorizontalSweep.Stats [reset]。烘焙侧另有 Build_bBakeHorizontalSweep(默认开)、ws.Bake.EmitVerticalFaces 等。kBuildVersion = 13 / kPrecisionLayoutVersion = 18;ProbeForward 与 MoveAlongFloor 运行接口、FEdgePrim / EdgeBVH / FWS*Result 均已退役,旧布局 v7–v12 读入时丢弃相关字段(WalkingSurfaceModule.cpp:202-213)。引擎 F:\PX_Trunk\UE UE 5.8.2;插件 F:\PX_Trunk\PX\Plugins\WalkingSurface;集成 F:\PX_Trunk\PX\Source\GPFramework\GPGameplay\Character\Movement\GPCharacterMovementComponent.cpp(约 17500 行,分析期间被并发编辑,行号以函数名为准)。本文所有图示与动图由脚本按源码结构绘制,为示意性可视化,非引擎截图。