跨世界呈现:加载界面不能归 World 所有

自动关联目录:跨世界呈现:加载界面不能归 World 所有

核心判断:需要遮住 World 切换的呈现对象,生命周期必须高于 World;但它只能保存最小显示快照,不能把旧 World、脚本 VM 或 UObject 图一起带过去。

一、业务冲突:加载层必须活过最危险的销毁区间

切换地图时,旧 World、PlayerController、运行时 UI 和脚本环境都会销毁,新 World 尚未 Ready。加载层既要持续显示、压住输入,又要在失败时恢复;若它属于旧 World,会在黑屏区间先被销毁,World Timer 也无法负责兜底。

自定义加载 UI 又来自内容树,直接持有旧文档节点或 VM 引用会把整套运行时生命周期拖过 Travel,并让回调落入已失效上下文。

二、实际实现:GameInstance 状态机托管 Slate Overlay

工程把传送呈现放在 UGameInstanceSubsystem,不保存旧 VM/Lua 引用。状态分为解析、组装、目标就绪、Travel、到达、完成与失败;确认进度只由阶段推进,显示进度可以在当前阶段上限内插值,但不会用计时器伪造“已经完成”。

Overlay 直接挂到 Viewport 的 Slate 层,因此能跨过 World 销毁。自定义 UI 被转换成受节点数和深度限制的最小快照,只保留支持的展示属性与资源引用。输入压制记录原 LocalController 弱引用,结束时恢复同一对象,避免在新 World 上错误抵消 IgnoreInput 计数。

stateDiagram-v2
    Idle --> Resolving
    Resolving --> Assembling
    Assembling --> TargetReady
    TargetReady --> Travelling
    Travelling --> Arriving
    Arriving --> Completed
    Resolving --> Failed
    Assembling --> Failed
    Travelling --> Failed
    Failed --> Idle: restore input + dismiss
    Completed --> Idle: commit + fade

三、值得保留:确认进度与显示进度分离

用阶段驱动确认进度,能够保证倒退事件被拒绝、重复事件幂等、前跳可接受;显示层只在下一阶段上限内平滑,不会出现“进度到满但还没完成”的协议谎言。失败状态保持已确认进度,也更利于诊断卡在哪一阶段。

失败清理使用 CoreTicker 而非 World Timer,正是所有权与计时源一致。Slate 绑定属性在定制引擎失效机制下可能不及时刷新,当前实现主动 Push 状态,也比假设自动绑定永远有效更稳健。

四、结构性缺陷:专用流程仍缺少统一的跨 World 事务契约

当前状态机能解决传送呈现,但输入、相机、音频、UI 快照和错误页仍分别清理。若任一模块在 Travel 前后没有使用同一个 SessionId,迟到回调可能关闭新 Overlay、恢复错误控制器或播放旧场景声音。

自定义 UI 快照虽然有结构预算,还需要资源字节预算、允许类型白名单、失败占位与平台能力降级。只限制节点数不能阻止少量超大图片造成跨世界驻留峰值。

五、目标设计:World-independent Presentation Transaction

创建由 GameInstance 拥有的事务,携带 SessionId、Source/Target World Generation、Phase Revision、输入令牌、相机快照、音频场景令牌和加载 UI 制品。各模块只通过事务注册补偿动作;Commit 按顺序交接到新 World,Abort 逆序补偿并确保幂等。

World 回调、下载、资源加载和淡出 Ticker 都必须携带 SessionId。新事务开始时先取消旧事务,旧回调只能命中已关闭的 Session 并被记录为 Stale。UI 快照使用已验证的小型制品,不携带任何旧 UObject 或脚本引用。

void FinishTransition(SessionId id, Outcome outcome)
{
    Transaction* t = transitions.Find(id);
    if (!t || t->IsClosed()) return;
    if (outcome == Success) t->CommitTo(targetWorldGeneration);
    else t->RunCompensationsReverse();
    t->RestoreOwnedInputLease();
    t->CloseAndInvalidateCallbacks();
}

六、恢复矩阵:每个失败点都要有非 World 兜底

失败位置仍存活的所有者恢复动作
目标解析失败GameInstance 事务显示原因、恢复输入、关闭 Overlay
资源组装失败GameInstance + 制品句柄释放部分资源、保留错误证据
旧 World 已销毁GameInstance + Slate不访问旧 UObject,等待或 Abort
新 World 未 ReadyPhase 状态机保持确认进度,不提前 Commit
到达后相机失败新 World Bridge默认相机兜底并标记降级
淡出中再次切换SessionId/Revision取消旧 Ticker,新事务接管

跨世界体验的工程质量,取决于失败时能否回到可操作状态。成功路径只有一条,旧 World 已死、新 World 未活、异步仍在飞的组合才是必须被架构正面承认的主路径。