World Partition 存档恢复时机:DataLayer 状态与 travel 前预恢复
World Partition 存档恢复时机:DataLayer 状态与 travel 前预恢复
在 World Partition 项目里做存档,最容易踩的不是"存什么",而是"什么时候恢复"。
读档触发一次跨关卡 travel,目标世界从零初始化,存档里保存的 DataLayer 运行时状态要在
一串引擎回调里精确对上号——错一拍,玩家就会看到错误的 DataLayer 布局。
这篇讲两件配套的事:DataLayer 状态应该在哪个回调点应用,以及怎么让数据在那个回调点之前
就准备好。存档框架基于开源的 Save Extension 插件(ISaveExtensionInterface /SlotDataTask_Loader 这些名字来自它)。
这套恢复逻辑挂在一个 GameInstance 子系统上,那个子系统怎么保证被正确创建、初始化顺序
怎么排,见 GameInstance 子系统初始化顺序与最派生子系统模式。
一、DataLayer 运行时状态:必须在 WP 初始化时应用
DataLayer 的运行时状态(哪些 Activated、哪些 Loaded、哪些 Unloaded)是会变的游戏状态,
理应进存档。做法是写一个全局存档对象(实现 ISaveExtensionInterface),把每个 DataLayer
的状态存进一个 UPROPERTY(SaveGame) 数组:
UPROPERTY(SaveGame)
TArray<FDataLayerSaveInfo> DataLayerSaveInfos;
存没问题,坑在恢复的时机。
直觉上读档恢复后会监听 FWorldDelegates::OnPostWorldInitialization,在那里把保存的状态
写回 DataLayer。但在这个回调点,世界的 UDataLayerManager 还没创建——UWorld::GetDataLayerManager() 返回 null,于是状态应用被静默吞掉,没有任何报错,
画面就停在关卡默认的 DataLayer 布局上。
正确的时机是 UWorld::OnWorldPartitionInitialized()。这是 UDataLayerManager 在世界里
存在的最早时机。订阅它,在回调里执行恢复;如果订阅那一刻 WP 已经初始化过了,就立即执行一次:
void USaveDataLayerSubsystem::OnWorldPartitionInitialized(UWorld* InWorld)
{
if (InWorld != GetWorld()) return;
ApplySavedDataLayerStatesAndRefreshTriggers();
}
记一笔:DataLayer 状态务必在
OnWorldPartitionInitialized应用,不要退回OnPostWorldInitialization——那里GetDataLayerManager()必为 null。
二、travel 前预恢复:让数据在世界初始化时就到位
上面说 DataLayer 状态要在 OnWorldPartitionInitialized 应用。但有个前提问题:
跨关卡读档时,目标世界的初始化是在 OpenLevel 之后才发生的,而存档的反序列化通常排在
关卡加载之后的某个任务里——等存档数据读出来,世界早就初始化完了,恢复时机已经错过。
为此给 ISaveExtensionInterface 加一个 opt-in 的钩子:
// 默认 false;返回 true 的对象,会在跨关卡 OpenLevel 之前被同步预恢复
virtual bool ShouldRestoreBeforeLevelTravel() const { return false; }
loader 在跨关卡 OpenLevel 之前,同步预读一次存档,只把这些 opt-in 对象的
SaveGame 状态恢复出来。等目标世界走到 OnWorldPartitionInitialized,DataLayer 子系统里
已经有正确的状态可应用了。这一步是第一节那个修复能真正拿到数据的前提——两者是配套的。
代价是预读会引入一次同步读盘,所以做成 opt-in:只有真正需要"早于世界初始化"的对象才返回 true,
其余对象走正常的 travel 后异步恢复,不受影响。
时序小结
把两节串起来,跨关卡读档的关键顺序是:
- loader 决定跨关卡 travel;
- OpenLevel 之前:同步预读存档,恢复所有
ShouldRestoreBeforeLevelTravel()为 true 的对象(含 DataLayer 子系统); OpenLevel→ 目标世界开始初始化;OnWorldPartitionInitialized:UDataLayerManager就绪,DataLayer 子系统把第 2 步拿到的状态应用上去。
加载页到入场过场之间的黑屏过渡是另一条独立的线,见
加载页到过场的黑屏桥接。