相机生命周期:跨 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 回调被拒绝。