IoStore(.utoc / .ucas)与 Zen Store
IoStore(.utoc / .ucas)与 Zen Store
这篇记 UE5 的运行时加载器 Zen Loader(也就是 IoStore),以及实验性的 Zen Store。官方文档:Zen Loader、Zen Store。
IoStore 原理
Zen Loader 是 UE5 引入的新运行时加载器(随 UE5.0 发布),通过使用在 stage 阶段离线计算好的、优化过的包与对象依赖图来减少 CPU 开销。
它和老办法的区别在于打包形态:Zen Loader 不是把所有资产塞进一个或多个 .pak,而是把所有包数据、批量数据(bulk data)和着色器数据打进一组 .utoc + .ucas 容器文件。.pak 此时只用来放访问频率较低的松散文件;安装 Pak 文件时,对应的容器文件也会一起安装。
两种容器文件的分工:
.utoc(Table of Contents):描述容器,包括块大小和偏移量、压缩格式、块是否加密。.ucas(Container Archive Store):存实际数据。
加载流程:
- 需要加载某资产时,先通过
.utoc查它在.ucas里的位置。 - 按查到的偏移量从
.ucas读出实际数据。 - 解压、处理后加载进内存供引擎使用。
通过优化数据的存储和访问方式,IoStore 显著提升了加载速度,对大规模开放世界这类需要加载海量资产的项目尤其重要。
启用 / 禁用
容器文件是在用 UAT(Unreal Automation Tool)做 stage 时默认生成的,这些容器文件一旦存在,运行时就会激活 Zen Loader。换句话说,默认就是开的。
老的事件驱动加载器(EDL)已弃用、将在未来版本移除,但 stage 阶段仍可用以下方式退回 EDL:
命令行加:
-SkipIoStore
或者改 MyProject\Config\DefaultGame.ini,在打包设置里关掉:
[/Script/UnrealEd.ProjectPackagingSettings]
bUseIoStore=False
UE5.4 实测:默认打包产出 .utoc/.ucas;加了 -SkipIoStore 后退回 .pak 形态。两者的文件大小和数量会有差别,patch 也能在 IoStore 形态下生成。
与 .pak 的对比
| 维度 | 传统 .pak(EDL) | IoStore(Zen Loader) |
|---|---|---|
| 容器文件 | .pak | .utoc + .ucas(.pak 仅放低频松散文件) |
| 依赖图处理 | 运行时跟踪 | stage 阶段离线计算 |
| 包依赖跟踪耗时 | 几秒级 | 几毫秒级 |
| 加载器 | EDL(已弃用) | Zen Loader(默认) |
| CPU 开销 | 较高 | 较低 |
Zen Loader 排错
加载阶段,Zen Loader 用 LogStreaming 日志通道。命令行临时开 Verbose/VeryVerbose:
-LogCmds="LogStreaming veryverbose"
大项目全量日志很费时,可以按包名或包 ID 过滤:
-s.VerbosePackageNames="/Game/PackageA /Game/PackageB 0xABCD1234ABCD1234"
挂了调试器的话,下面这个会在指定包的某些加载阶段自动断点:
-s.DebugPackageNames
stage 阶段,UAT 会用 -ScriptsForProject="MyProject.uproject" 生成若干响应文件,列出每个容器里存了哪些包。这些命令和其他命令可以在 UAT 日志里找到:
Engine\Programs\AutomationTool\Saved\Logs\Log.txt
内部原理
Zen Loader 建立在 EDL 之上,需要 cooker 输出同样的东西:名称表、import/export 映射、预加载依赖等。区别是老的 EDL 运行时逻辑被挪到了离线,在 stage 阶段生成。这种离线预处理把运行时包依赖跟踪从几秒压到了几毫秒。
Zen Store(实验性)
Zen Store 是个实验性功能:把 cook 好的资产存到本地存储服务器,而不是文件管理器里一堆松散文件。它让游戏可以基于开发运行,无需部署游戏内容。发布产品里用它要谨慎。
注意区分:前面的 IoStore/Zen Loader 是关于"打包产物怎么组织和加载",Zen Store 这里说的是"cook 输出存到哪、怎么流式传给目标平台"。它们都属于 Zen 这套架构,但解决的是不同环节。
启用
改 MyProject\Config\DefaultGame.ini:
[Zen.AutoLaunch]
DataPath=%APPSETTINGSDIR%Zen/Data
LocalDataCachePathEnvOverride=UE-LocalDataCachePath
LocalDataCachePathEditorOverrideSetting=LocalDerivedDataCache
ExtraArgs=--http asio --gc-cache-duration-seconds 2937600 --gc-interval-seconds 21600
[/Script/UnrealEd.ProjectPackagingSettings]
bUseZenStore=true
DataPath:Zen Store 数据存储路径。LocalDataCachePathEnvOverride/LocalDataCachePathEditorOverrideSetting:覆盖本地数据缓存路径,确保服务器正确读写。ExtraArgs:额外参数。这里--http asio启用 asio(一个跨平台 C++ 网络/IO 库,用于高性能网络服务)作为 HTTP 实现;--gc-cache-duration-seconds 2937600设缓存持续 34 天;--gc-interval-seconds 21600设 GC 间隔 6 小时。
存储服务器能让游戏通过 HTTP 读数据块:
-ZenStoreHost=[ip]
这样游戏就能通过开发工具运行,不需要部署游戏内容。
它替代了什么
cook 输出存储以前是松散文件、完全 PAK 化的文件,或 cook-on-the-fly。Zen Store(UE5.4 实验性)想取代这些,卖点:
- 避开文件系统性能瓶颈。
- 用于增量 cook。
- 通过网络把 cook 好的资产流式传到目标平台(主机、移动设备)。
- 共享 cook 数据快照,之后只 cook 增量(delta)。
ZenServer 在这里扮演"流式传输到目标平台"的角色:不再把 cook 输出当松散文件存盘,而是通过本地网络把 cook 数据直接流给游戏客户端和服务端的目标平台,省掉耗时的完整复制部署,加快"在目标上运行"的迭代。
CookOnTheFly(对照)
CookOnTheFly(COTF)把 cook 推迟到游戏部署到平台之后。只安装可执行文件和少量基础文件,这些文件在需要内容时通过和 cook 服务器的网络通信按需请求。适合频繁改内容、或只看游戏部分内容的开发者快速迭代。
先在有完整项目的机器上启 cook 服务器(本地或远程都行):
UnrealEditor-Cmd.exe [项目.uproject的完整绝对路径] -run=cook -targetplatform=Windows -cookonthefly
客户端要知道从哪台机器加载内容,在命令行传 cook 服务器的 IP:
-filehostip=<cook-server-ip>
不指定 IP 的话,构建就从现有本地文件加载,不会去连 cook 服务器。
实测
按上面配好 DefaultGame.ini 后,会在 ...\Common\Zen\Data\cas 下生成文件。期间试过两件事:
- 打开编辑器时连 ZenShared 的 IP:走得通,但行为和预期不完全一致。
- 连本机 IP:在本机启 zenserver 后,可以在
...\Zen\Data\projects\下看到项目对应的目录(名字带哈希后缀,比如MyGame.<hash>)。