相机生命周期:跨 World 恢复需要会话快照

自动关联目录:相机生命周期:跨 World 恢复需要会话快照

核心判断:编辑态相机恢复不是“记住几个字段”,而是跨 World 的一次事务。快照必须活得比旧 World 久,又不能长到污染下一次会话。

一、业务冲突:Play 会销毁保存相机状态的对象

进入试玩前要保存编辑视角,退出后恢复 Camera、Projection、Focus 和参数。问题在于承载相机管理器的 Scene/World Subsystem 会随 World Travel 一起销毁;把快照存在成员变量里,恰好在最需要恢复时丢失。

反过来,把快照放进进程静态对象虽然跨过了 World 生命周期,却可能在多窗口、并行会话、异常退出或打开另一份内容时被错误消费。

二、实际实现:完整快照、延迟重绑和一次性清除

工程在 Play 前保存相机 ID、CFrame、Focus、投影与一组运行参数;退出后先尝试解析原 CameraId,失败则选择当前、保留或新建默认相机,必要时重新初始化 Actor,再恢复属性与 ViewTarget。快照消费后立即清除,避免下一轮重复应用。

由于 Scene Subsystem 会重建,快照后来迁到 cpp 作用域的静态存储,解决了 Run-Stop-Run 无法恢复的问题。Play 结束还会先清除相机跟踪,再销毁 Actor,避免销毁回调把退出过程误判为运行中故障。

stateDiagram-v2
    Edit --> SnapshotSaved: Enter Play
    SnapshotSaved --> WorldTearingDown
    WorldTearingDown --> RuntimeWorld
    RuntimeWorld --> Restoring: Exit Play
    Restoring --> Edit: bind actor + ViewTarget + consume
    Restoring --> Edit: source missing -> fallback camera

三、值得保留:快照覆盖语义状态,而非只存 Transform

只保存 Transform 会丢失正交尺寸、FOV、Focus、主体和控制模式,恢复后画面看似接近,后续手势与脚本行为却不同。当前实现保存完整参数,并在 Actor 缺失时走幂等创建,说明恢复对象是逻辑相机状态,不是旧 UObject。

退出顺序也值得保留:先停止跟踪和控制,再触发 Actor/World 销毁,最后在新上下文恢复。生命周期事件的顺序比增加空指针判断更能消除偶发问题。

四、结构性缺陷:进程静态变量没有归属和关联键

函数静态存储只回答了“活多久”,没回答“属于谁”。它没有 GameInstance、编辑文档、PlaySessionId、来源 World 与目标 World,任何后来到达的 Restore 都可能消费唯一快照。异常路径未清除时,污染还会跨越下一次试玩。

恢复过程中若异步预设、Subject 或 Actor 初始化晚到,也需要拒绝旧会话回调;一次性布尔值不足以区分连续两轮 Play。相机恢复必须与 UI、输入和加载呈现共享同一 Session,而不是各自维护全局缓存。

五、目标设计:GameInstance 级 Session Transaction

在比 World 更长、比进程更短的所有者中保存 PlaySessionSnapshot,键为 SessionId + DocumentId,记录来源 World Generation、相机语义状态和恢复阶段。Enter 原子写入,World Teardown 只更新阶段,目标 World Ready 后按键领取,Commit 后删除;Abort 也必须显式删除。

每个异步回调携带 SessionId 与 RestoreGeneration。恢复时先重建逻辑 Camera,再解析 Subject,随后物化 Actor,最后设置 ViewTarget;最后一步才算提交。任一步失败都退到默认相机并留下原因,不保留半恢复状态。

void Restore(SessionKey key, WorldGeneration worldGen)
{
    Snapshot s = sessions.Claim(key);
    if (!s.MatchesTarget(worldGen)) return;
    CameraId id = models.RestoreOrFallback(s.camera);
    bridge.Materialize(id, worldGen);
    bridge.BindViewTarget(id);
    sessions.CommitAndErase(key);
}

六、决策与测试:跨生命周期状态必须有明确所有者

方案能跨 World隔离会话结论
Scene/World Subsystem 成员否是不能承载恢复快照
cpp 函数静态是否可救急,不是最终架构
GameInstance 会话仓库是是适合单客户端会话
持久文件是是仅用于崩溃恢复,不用于普通退出
旧 Actor 指针否否禁止作为恢复身份
版本化逻辑快照是是推荐,以 ID 重建表示

测试必须覆盖连续 Run-Stop-Run、Play 中切 World、原相机被删除、Subject 换代、恢复中再次退出、双窗口并行和异常 Abort。每次结束都断言快照仓库为空、ViewTarget 有效、旧 Generation 回调被拒绝。