轻度传送(LightTeleport)落地阶段,只有角色 1014 会被向上弹起几帧再恢复正常起立。 定位结论:落地 Seq(Seq3)的配置长度大于下落技能的蒙太奇长度——下落技能 1018 先播完、先结束, 技能结束时的清理动作与仍在驱动角色的 Seq3 打架,落地姿态被弹回。已用「两侧长度对齐」临时修复并验证。
| 项 | 内容 |
|---|---|
| 现象 | 角色 1014 传送落地瞬间,深蹲最低点后向上回弹几帧,再进入正常起立 |
| 复现范围 | QA 报告只有 1014 明显复现(大体型角色落地蹲得深,回弹更醒目;时序问题是全局性的) |
| 根因 | Seq3 配置长度 > 下落技能(1018)蒙太奇长度 → 技能先于 Seq3 结束(本人实测确认) |
| 直接动因 | 技能结束的「三连击」:硬切蒙太奇 + 恢复重力 + Phase 切换,全部落在 Seq3 还在播的窗口里 |
| 已实施修复 | 已验证 两侧长度对齐(配置修复,无代码改动) |
| 保底方案 | 待实施 双驱动完成闸门 + 长度校验(本文 §6) |
录屏逐帧可见的完整过程:
Phase: Skill(下落技能 1018 生效中)Phase: Skill → Grounded、CurrentState: NotMovingPhase 从 Skill 切到 Grounded 的瞬间——
说明弹起不是动画资产问题,而是技能结束时机与Seq 播放进度不同步。
传送落地有两个并行的视觉驱动者:
| 驱动者 | 是什么 | 由谁结束 |
|---|---|---|
| 下落技能 1018 | LevelSkillLogicLightTeleportDown,播放技能蒙太奇(下落姿态),结束时恢复重力、切 Phase |
蒙太奇播完回调 OnCompleted |
| Seq3 落地表演 | TeleportController.ExitLoadLevelAsync 加载的 LevelSequence,绑定真实战斗角色驱动落地动画与位移 |
OnFinished → emit TeleportDownSkillComplete |
当 Seq3 配置长度 > 下落技能蒙太奇长度时:蒙太奇先播完 → 技能结束。技能结束瞬间做了三件事, 每一件都在和还在播放的 Seq3 抢角色:
// TeleportController.OnStartDown(顺序严格:先 Seq 后技能) await this.ExitLoadLevelAsync(); // ① Seq3 加载 → BindRealRoleToSeq3 → 播放(时长 T_s) await this.StartTeleportDown(); // ② BeginLevelSkill(1018) // LevelSkillLogicLightTeleportDown.OnStart(蒙太奇路径) const playResult = this.SkillInstance?.PlaySkillMontage( 0, 1, 0, () => { this.OnTeleportDownComplete(); }, // ← 蒙太奇播完(T_m)直接结束技能,没有等 Seq3 startSection, ); // OnTeleportDownComplete this.SkillComp?.EndSkill(skillId, '传送-轻度传送下降完成', ESkillEndReasonType.SkillEnd); // Skill.ClearSkillMontage / OnEnd this.AnimComp!.MainAnimInstance!.Montage_Stop(0.2, montage); // ① 硬切 this.ApplyPlayerGravityScale(true); // ② 重力恢复 // ③ Phase: Skill → Grounded(状态机接管)
LevelSkillLogicLightTeleportDown.OnStart 有三条结束路径,只有一条有「等 Seq3」的闸门:
| 路径 | 结束触发 | 等 Seq3? | 现状 |
|---|---|---|---|
| 主控 + SkipLocalPlayerDownMontage=true (默认开关:主控跳过蒙太奇,Seq3 独占落地) |
Seq3 完成事件 | ✅ WaitSeq3ThenComplete() |
正确 事件 + 15s 兜底定时器 |
| 蒙太奇播放路径 (skip=false 的并行分支 / 模拟端) |
蒙太奇 OnCompleted | ❌ 无 | 事故路径 蒙太奇先完 → 直接结束技能 |
| 起播失败路径 | TimerSystem.Next 下一帧 |
❌ 无 | 同类隐患 下一帧就结束,更早 |
WaitSeq3ThenComplete 的注释写着:立即结束会让 end 上报同步到模拟端,把模拟端还在播的下落蒙太奇
Montage_Stop 掉。只是这个闸门只覆盖了 skip 分支,蒙太奇路径漏了。
TeleportDownSkillComplete(Seq3 完成的唯一信号)在 TeleportController 里有
7 处 emit(OnFinished 正常路径 + 6 处失败收闸:配置缺失 / 绑定失败 / 播放器缺失 / 资源加载失败 / Actor 缺失 / 淡入淡出路径)。
排查「闸门为什么提前/延后关」时需要逐处找,是典型的多点收闸结构。
两侧长度对齐:把下落技能蒙太奇长度与 Seq3 落地段配置长度调成一致, 技能结束与 Seq3 完成同帧到达,双驱动窗口消失,弹起不再出现。
核心原则:技能结束条件 = 自身蒙太奇播完 AND 落地 Seq3 完成(双驱动完成闸门),
复用已有的 WaitSeq3ThenComplete 模式,不是新发明。
OnTeleportDownComplete,
改查 Seq3 流程状态——还在进行 → 复用 WaitSeq3ThenComplete()(事件 + 已有 15s 兜底);
已结束 → 立即结束(避免反向场景「Seq 比 蒙太奇短、事件已发过、干等 15s」)WaitSeq3ThenComplete() 加防重入保护IsDownSeqFlowActive 标志:ExitLoadLevelAsync 入口置 true,
收闸时清 falseTeleportDownSkillComplete emit 收口成一个私有方法(清标志 + emit),
暴露只读查询给技能侧Log.Error
带两侧数值——错配在编辑器跑一次传送就能看到,不必等 QA 录屏await this.ExitLoadLevelAsync(); // Seq3 流程先建立 → IsDownSeqFlowActive 必然先为 true await this.StartTeleportDown(); // 技能后启动 → 读标志时 Seq3 流程必然已存在 // 标志读取 + Once 注册在同一个同步块内,事件 emit 也是同步的 // JS 单线程 → 不存在「读到 false 但事件还没来得及发」的窗口
「多个异步视觉驱动者 + gameplay 状态机抢角色控制权」这类问题,业界三种模式:
| 模式 | 代表 | 做法 | 本工程对应 |
|---|---|---|---|
| 1. 技能编排 (完成屏障) |
Epic GAS / Lyra | 技能持有多个异步任务(蒙太奇任务 + 等事件任务),全部完成才 EndAbility; 同时保证被打断时也必须能结束(防技能永不结束) | 本方案 双 AND 闸门即此模式; Lyra 换弹技能(播蒙太奇+等事件,事件没来用蒙太奇结束兜底)与之同构 |
| 2. 序列独占 | UE 过场惯例 | 序列期间锁输入、DisableMovement,seq 全权驱动角色;极端形式 = loading screen | SkipLocalPlayerDownMontage=true 默认已是此方向(主控 seq 独占);
事故的并行分支是遗留路径 |
| 3. 编辑期校验 | 各厂 CI / 表校验 | 上线前比对 seq 长度 vs 技能/蒙太奇长度,超阈值报错 | 待补 长度校验(§6-③) |
WaitSeq3ThenComplete),实施是「补完」而非「改造」。
与最理想的差距只在缺预防层——闸门(止血)+ 长度校验(防再犯)双层才是完整答案。
曾评估过更简变体(主控只认 Seq3 完成、无视蒙太奇时序),但反向场景(Seq 比蒙太奇短)会切蒙太奇;
双 AND 两个方向都安全,维持双 AND。
| # | 事项 | 状态 |
|---|---|---|
| 1 | 根因定位:Seq3 配置长度 > 下落技能蒙太奇长度,技能先结束与 Seq3 双驱动冲突 | 实测确认 |
| 2 | 临时修复:两侧长度对齐 | 已验证 |
| 3 | 保底:蒙太奇路径接入 WaitSeq3ThenComplete 双驱动闸门 | 待实施 |
| 4 | 保底:7 处 emit 收口 + IsDownSeqFlowActive 查询 | 待实施 |
| 5 | 预防:配置加载时长度比对校验(Log.Error) | 待实施 |
相关代码:Src/Game/World/Entity/Skill/SkillLogic/LevelSkillLogic/LevelSkillLogicLightTeleportDown.ts ·
Src/Game/Module/Teleport/TeleportController.ts ·
Src/Game/World/Entity/Skill/Skill.ts(ClearSkillMontage) ·
下落技能 ID 1018(TeleportDefineConstants.TeleportDownSkillId)