🚢 落兵船跳跃惯性 + 防跳出 · 实现说明

ConfigId 70215 TroopShipPlatformHelper.ts 快速了解"跳起来带惯性"和"正常移动跳不出船舱"是怎么做到的
📖 名词解释 之前是什么情况 为啥会产生 bug 现在怎么改的 安全保护 演进历程

📖 一、名词解释(先看这个)

BP_SceneItem_Base_C (= MainActor = 文中说的"壳")
这艘船在场景里的主 Actor,是一个逻辑锚点 / 空壳容器。三个称呼指的是同一个东西。 它坐标正常,但自己不带模型、本身静止不动。之前叫它"壳"造成了误解,这里统一说明。
SM_MAL_01_A_Car* (真船体 Mesh)
挂在 MainActor 下面、由关卡序列 Seq_TroopShip 驱动的一堆静态网格(船的甲板、船舱等)。 玩家真正踩着、真正在移动的是它们;但它们的世界坐标是 -120万 的大世界偏移值。
SceneItemComponent (挂在 MainActor 上的 TS 组件)
我们写的逻辑组件,就挂在 BP_SceneItem_Base_C 上。代码里的 this.Owner 就是它。 因为它继承自 BaseActorComponent,所以能被塞给下面的 BasePlatform。
BasePlatform (引擎的"移动平台携带"入口)
UE 角色移动组件的一个字段。只要把某个平台设给它,引擎就会自动让角色跟着这个平台走——站立跟随、起跳后的空中惯性全自动处理,非常平滑。 但它有个硬限制 👉 只认一个 Actor 的坐标,且类型必须是 BaseActorComponent。
KeepInAir(空中携带)
引擎在角色起跳离地后,继续按 BasePlatform 的移动量带着角色在空中漂移的机制。 这就是"跳起来能落回相对船的起跳点"的原理来源。
位移增量 vs 绝对坐标
绝对坐标 = 物体现在在世界的哪个点(真船体是 -120万,不可用)。
位移增量 = 这一帧比上一帧移动了多少(真船体的这个值完全正常)。整个方案的基石就是"只用增量、不用绝对坐标"。
船体结构图
图1 · 船的结构:静止的锚点、在动的真船体、引擎的携带入口

二、之前是什么情况

最初玩家站在移动中的落兵船甲板上,有两个问题:

期望的目标是:船像"会动的地面"——站着、走动、跳跃全都相对船,就跟站在普通地面上一样自然。

三、为啥会产生这个 bug(根本原因)

排查后发现,症结在于这艘船的结构很特殊,和引擎的移动平台机制"对不上":

对象作用坐标情况引擎能用它做平台吗
BP_SceneItem_Base_C
(MainActor / 壳)
逻辑锚点、空壳 坐标正常 能,但它静止不动 → 跟了等于没跟
SM_MAL_01_A_Car*
(真船体 Mesh)
Sequence 驱动、真正在动 绝对坐标是 -120万偏移值 不能——类型不符,且坐标是垃圾值
⚠️ 核心矛盾(死结):
引擎的 BasePlatform 只肯读 MainActor 一个物体的坐标,可 MainActor 静止不动;
而真正在动的船体 Mesh,引擎既管不到(类型不符),坐标又是 -120万 不能直接用。
→ 于是不管怎么设,引擎都拿不到"船在动"这个信息,惯性自然无从谈起。这就是 bug 的根。
核心矛盾死结图
图2 · 死结:锚点坐标正常却静止,真船体在动却是 -120万垃圾坐标

插曲:船体为什么会报出 -120万 这种离奇坐标?

这不是 bug,是 UE 的一个正常机制被撞上了。船的移动序列 Seq_TroopShip 在代码里设了一句关键配置:

// SceneInteractionActorWarp.ts newDirector.bOverrideInstanceData = true; instanceData.TransformOriginActor = this.UeActor; // ← 相对原点播放

TransformOriginActor 的意思是:这个序列里的动画,不用世界绝对坐标,而是相对某个原点来算。

美术做序列时,是在编辑器原点(0,0,0)附近摆船、K 动画的,序列里存的是"船从 0 开到 5000"这种相对小数值。运行时引擎把这套动画变换到关卡里,而本项目的世界原点做过大世界坐标偏移(WorldPartition 大世界常见),于是:

同一个物理点报告的坐标用的坐标系
你(玩家)站的地方X = 79872偏移后的正常世界坐标
你脚下的真船体网格X = -1200000序列相对原点坐标
🧭 它俩指的是同一个地方,只是"从哪个原点开始数"不同——就像同一处位置,"从北京量"和"从纽约量"是两个数,差了一个固定偏移(约128万)。
→ 所以绝对坐标不能直接用;但"每帧移动了多少"这个位移增量,在两个坐标系里是一模一样的 —— 这正是整个方案能成立的根本原因。

那"直接读真船体坐标"行不行?—— 试过,会弹飞

曾经直接把真船体 Mesh 的世界坐标拿来驱动平台,结果那 -120万 的垃圾坐标直接把玩家瞬移到十万八千里外,一碰船就被弹飞。这条路堵死。

四、关键突破:找到可靠的数据源

加临时诊断日志实测,站在开动的船上时读到:

462 → 2076
脚下船体组件的物理速度 (cm/s) — 真实有速度!
≈ 相等
船体每帧位移 ≈ 玩家每帧位移 — 站立跟随本就正常

结论:真船体的绝对坐标是垃圾值(-120万),但它的每帧位移增量(这一帧比上一帧动了多少)完全正常。
→ 只用"位移增量",就能安全知道船每帧动了多少,且永远不会碰到 -120万。

五、现在是怎么改的

思路一句话:让 MainActor(壳)跟着真船体动起来,再把它交给引擎当移动平台——这样引擎自带的平滑携带机制就能全自动生效。

  1. 每帧驱动 MainActor 随船动:读真船体这一帧的位移增量,用 AddActorWorldOffset 把 MainActor 挪同样的量。MainActor 坐标始终正常,绝不出现 -120万。
  2. 站上船:把角色的 BasePlatform 设为 MainActor。引擎"站立携带"自动生效 → 人跟着船平移,平滑如地面。
  3. 起跳离地:引擎自动转入 KeepInAir 空中携带 → 人在空中继续随船漂移 → 落回相对船的起跳点。
  4. 真正离船:清除 BasePlatform,MainActor 归位到锚点。
解法流程图
图3 · 解法四步:读增量 → 驱动锚点 → 设为平台 → 引擎自动携带(站立+空中)
💡 回答"为啥不让 MainActor 跟壳一起动,而是它不动壳动?"
其实"壳"就是 MainActor,是同一个东西,不存在两个物体。现在的做法正是"让 MainActor 动起来"——每帧都在移动它。
唯一讲究的是用什么驱动它:用真船体的位移增量(安全),而不是绝对坐标(-120万,会弹飞)。

顺带修掉的第二个 bug:正常移动会被"跳出"船舱

现象与成因

站立判定靠引擎的"角色脚下踩着谁"。但跳跃、颠簸、状态重置的瞬间角色会短暂离地,这个信息瞬时丢失 → BasePlatform 被反复"设/清" → 携带断断续续 → 某帧人被甩出船(日志实测坐标 Y 从 57353 突变到 12980)。

修复:离船宽限期

新增 LeaveGraceFrames = 20:判"离船"时不立即生效,要求连续 20 帧都没踩到本船才真正判离。跳跃/颠簸那种零点几秒的瞬时丢失被过滤掉,携带保持连续 → 正常移动 / 跳跃再也不会被甩出船。

离船宽限期图
图4 · 宽限期:连续 20 帧没踩到船才判离,过滤跳跃/颠簸的瞬时丢失

六、防弹飞的安全阀

保护措施作用
只用位移增量,不用绝对坐标从根上避免 -120万 偏移坐标进入计算
MaxFollowDeltaPerFrame = 1000单帧位移超 10 米判定为坐标抽风/重置,跳过该帧不移动,绝不弹飞
sweep = falseMainActor 只作位置参考,不与船体碰撞体物理互推(曾因 sweep=true 弹飞过)
离船宽限期过滤瞬时状态抖动,避免携带断续导致的甩飞

七、方案演进(踩过的坑)

版本做法结果
v1MainActor 静止,跟随它不弹飞,但船不动→没惯性
v2跟随真船体绝对坐标-120万坐标 → 一碰船就弹飞
v3起跳时给一次性初速度有惯性但落回原地,跟不上变速的船
v4空中每帧手动挪玩家有惯性了,但抖动/能被甩出
v5 (当前)MainActor 按位移增量随船动 + 引擎原生携带 + 离船宽限期平滑如地面,惯性正确,跳不出船 ✓