[转载/熟肉] 鸣潮的存储膨胀:UE4 的限制、技术债,以及这款游戏为什么这么大

4044 字
20 分钟
[转载/熟肉] 鸣潮的存储膨胀:UE4 的限制、技术债,以及这款游戏为什么这么大
AIGC 通告

本文使用 AI 辅助原文翻译工作,如有翻译不当之处请在评论区/私信至电子邮箱 justpureh2o@outlook.com 指出。

Note

转载信息

本文转载并翻译自 ToonXD 的个人博客,原文标题为《Wuthering Waves Storage Bloat: UE4 Limitations, Technical Debt, and Why the Game Is So Large》,原文链接:https://pqmlmaoxd.github.io/gamedev/analysis/2026/03/15/wuthering-waves-memory-bloat.html,原文发表于 2026 年 3 月 15 日(作者后续在 2026 年 4 月 27 日补充了存储占用的最新进展)。

本文为中文翻译,仅供学习交流,版权归原作者 ToonXD 所有;文中数据、观点与结论均来自原作者,不代表本站立场。原文标题中的 “memory bloat” 实指安装包与存储占用,为符合中文习惯,译文统一将 “memory/storage” 译为“存储/包体”。

在鸣潮的近期版本里,尤其是3.0 与 3.1两个大版本之后,我在许多社区反复看到同样的抱怨:

  • “这游戏的存储优化太差了。”
  • “为什么光更新就要在手机上占120GB?”
  • “官方说他们在听反馈,可包体还是一次比一次大。”

作为一个计算机专业出身、同时又很喜欢鸣潮的人,我决定把这件事挖深一点。

本文不是要盲目替库洛游戏辩护。它的目的是解释为什么存储问题远比“开发者没优化”要复杂得多。

很多问题来自几方面的叠加:

  • 虚幻引擎 4 的架构限制
  • 早期开发阶段的决策
  • 以及一个长线运营项目不断累积的技术债

如果你觉得技术部分太密,可以直接跳到文末的 TL;DR。


1. UE4 里的硬引用问题#

虚幻引擎的文档这样描述资源管理:

“主资源(primary asset)是可以被资源管理器直接操作的资源;次资源(secondary asset)是主资源引用时会自动加载的资源。主资源就是在烘焙(cooking)与分包(chunking)流程中被引用的那种类型。”

来源:https://dev.epicgames.com/documentation/en-us/unreal-engine/cooking-and-chunking

简单说:

像今州或黎那汐塔这样的地图被当作主资源,在构建过程中被打进各自独立的 chunk(分包)。

如果两张地图硬引用了同一个资源(例如 TreeA),那么取决于共享内容的打包方式,引擎在烘焙过程中就可能把它复制成两份。

构建产物看起来会是这样:

地图 A(Chunk 10)
- TreeA 纹理
地图 B(Chunk 11)
- TreeA 纹理(重复)
结果:
.pak 文件里存在多份压缩副本

这种复制行为在 UE4 的分包系统里是正常的。上面这个小例子看似无伤大雅,但在一个大型开放世界游戏的规模上,你可以想象当上千个纹理与网格被多张地图引用时会发生什么。

那为什么不干脆全都塞进一个 chunk?#

一个可能的方案是把所有共享资源都放进 Chunk 0,也就是默认分包。

但这会引入另一个取舍。如果 Chunk 0 变得过大:

  • 首次加载时间会急剧拉长
  • 内存压力上升,因为引擎必须在启动时加载一个巨大的分包

于是开发者面对的是一个根本性权衡:更小的存储占用,还是运行时性能与加载速度。

来自数据挖掘的资源配置证据#

数据挖掘出的配置文件强烈暗示,鸣潮围绕 PakName 标识符构建了一套分包体系来组织资源与地区。

虽然我们目前拿不到完整的 AssetRegistry 或 chunk 清单,但这些结构说明内容是以离散的 pak 包为单位被挂载与引用的。在这种体系下,跨 pak 边界的硬引用会带来结构性风险:要么必须在运行时挂载更多包,要么取决于项目的打包策略,资源在烘焙过程中被复制。

以下是支撑证据:

证据一——按地区划分的 Pak 包#

一个配置文件把世界区域映射到特定的 pak 包:

来源:https://github.com/Arikatsu/WutheringWaves_Data/blob/main/BinData/DownLoad/mapblockinfo.json

{
"BlockId": 2,
"MapId": 900,
"PakName": "Pack_HHA_Underground",
"RegionName": "HHA_Underground"
}

解读:这说明游戏在地区层级挂载 pak 包,不同的世界区块绑定特定的包标识符。在实践中,这类似于一套分包体系,世界不同部分可以被独立流送或下载。

证据二——玩法系统直接引用 Pak 包#

另一个配置文件把任务数据直接关联到 pak 标识符:

来源:https://github.com/Arikatsu/WutheringWaves_Data/blob/main/BinData/QuestRefVideo/questrefvideoconfig.json

{
"QuestId": 114000020,
"PakName": "114_2"
}

解读:这里的玩法逻辑通过 PakName 标识符引用资源。这说明打包后的资源在游戏内容管线里被当作一等数据引用暴露出来。

证据三——打包规则与地图路径#

第三个配置文件展示了地图路径与打包规则:

来源:https://github.com/Arikatsu/WutheringWaves_Data/blob/main/BinData/instance_dungeon/akimapsource.json

{
"MapId": 32,
"PakRule": 0,
"MapPath": "/Game/Aki/UniverseEditor/Test/QuestTest"
}

解读:PakRule 这类字段的存在说明打包逻辑被提升到了配置层。有意思的是,一些地图路径仍然指向测试或 QA 环境。这不一定意味着那些地图被打进了发行版本,但它说明了除非 cook 管线显式排除,否则资源索引是可以把开发路径一并包含进去的。

综合来看,这些结构强烈暗示内容管线是以 pak 为分发单位组织的。在这种架构下,跨包边界的硬引用会导致资源复制,或者强制额外的 pak 挂载——除非项目精心设计过共享 chunk。

本分析的局限#

由于实际的游戏工程并不公开,本分析依赖的是数据挖掘出的配置文件,而非完整的虚幻引擎资源注册表。

在没有完整 AssetRegistry.bin 或 chunk 清单的情况下,无法确凿地证明跨 pak 包的资源复制。不过,这里观察到的配置结构与虚幻引擎烘焙与分包管线所记载的打包模式是一致的。

更新(2026-04-12): 自 v2.8 起,C# 迁移的基础设施已通过多个独立证据源得到确认——数据挖掘条目(v2.8 出现 LevelEntityForCSharpConfig,v3.0 出现 PrefabForCSharpConfig)、FModel 的 pak 检查(ScriptAssemblies 目录中包含 CSharpScript.dll、mscorlib.dll 及相关程序集),以及显示 V8/Puerts 仍作为主运行时与已部署的 C# 运行时并存的客户端日志。迁移是真实且持续的,但是渐进的——截至当前版本,V8/Puerts 仍是主要脚本运行时。完整分析见性能后续文章。

译者注

鸣潮已确认在 3.6 版本中将脚本编程语言由 TypeScript 切换为 C#,并于 2026 年 8 月 21 日开放了限量测试。

更多信息请转到本文姊妹篇《鸣潮的性能与卡顿:引擎架构、硬件误区与长线运营的真实代价》文首处。

2. 依赖地狱#

另一个常见的问题是:

库洛为什么不能直接删掉旧活动和旧任务内容来省空间?

要理解这件事,可以设想在玩叠叠乐。旧活动与遗留任务系统就像塔底的那些木块。抽掉一块,上面的一切都可能塌下来。

在软件架构里,这种情况通常被称为“依赖地狱”。许多系统依赖同一批共享数据库或配置文件。

例子:追月节活动(1.2 版本活动)#

在检查了 3.1 版本的数据挖掘文件之后:

https://github.com/Arikatsu/WutheringWaves_Data/tree/3.1

追月节活动展示了旧内容可以嵌得多深。

在 aki_base.csv 中:

  • TrackMoonActivity
  • TrackMoonActivityReward
  • TrackMoonPhaseActivity

这些条目存在于一个名为 db_activity 的共享数据库里。这个数据库被游戏里许多其他系统使用。只有一小部分追月节数据存放在专属数据库(db_MoonChasing.db)中。这意味着这个活动不能简单删除,否则可能弄坏其他功能。

另一个例子出现在 commonparam.json 里。在成千上万个互不相关的参数之间,夹着好几个追月节相关的键,例如:

  • TowerRecordMaxLevel
  • MoveCheckSpeed

其中一些条目还引用了 Sequence 系统,而那是虚幻引擎共享的过场动画管线的一部分。要安全地移除这些条目,需要逐一确认代码库里再也没有任何地方引用它们——在一款持续演进的在线服务游戏中,这极其困难。

而且即便这种清理真的可行,它多半也不会显著减小包体,因为最大的存储成本仍然来自前面讨论的资源架构。

3. UE4 中的预下载与更新体积#

虚幻引擎使用的补丁系统,在存储上可能相当“重”。

按 Epic 自己的文档:

“这种方法最终可能占用大量存储空间,因为它保留所有旧文件,只是把游戏指向现存的最新文件。”

来源:https://dev.epicgames.com/documentation/en-us/unreal-engine/how-to-create-a-patch-platform-agnostic

预下载在实践中是怎么发生的#

设想这样一个情形:

  • 你手机上的版本 3.0:约 55GB
  • 服务器上的版本 3.1:约 60GB

在预下载期间,客户端会把整个新版本下载到一个临时目录。

于是有一段时间,你的设备上同时存着:55GB(旧版本)+ 60GB(新版本)= 约 115GB

旧版本会保持挂载,直到更新被校验完成、准备切换。校验结束后,系统替换掉旧版本并删除它。这就是为什么预下载可能临时需要接近最终体积两倍的存储空间。

一个乐高类比#

一个简单的类比是搭一座乐高城市。

城市搭完后,你把所有东西粘死,放进一个密封的盒子(.pak 文件)。现在你想加一栋新楼。你没法在破坏现有粘死模型的前提下轻易修改它。

于是你只能:

  • 把新楼一起放进去,重建整座城市
  • 把新版本密封起来
  • 丢掉旧的那份

大体上,大的 .pak 更新就是这么工作的。

那为什么不用更小的模块化文件?#

UE4 确实支持更模块化的资源结构,但这里有取舍。

如果游戏把资源拆成成百上千个小文件,系统就必须管理:

  • 多得多的文件句柄
  • 更高的 I/O 开销
  • 更严重的磁盘碎片

对于一个本来就在大城市里挣扎于 CPU 瓶颈的引擎(比如 UE4)来说,增加 I/O 复杂度可能让运行时性能更糟。在存储较慢的移动设备上,这尤其成问题。

此外,鸣潮还会轮换 AES 加密密钥,这又给补丁与资源管理增加了一层复杂度。

Unity 的做法:AssetBundle#

Unity 用的是完全不同的体系。

按 Unity 文档:

“AssetBundle 是一种归档文件,你可以用它把资源分组,以便制作可下载内容(DLC),或减少应用的初始安装体积。”

来源:https://docs.unity3d.com/Manual/AssetBundlesIntro.html

借助 AssetBundle 与 Addressables,Unity 以模块化结构组织资源。加一栋房子时不必重建整座城市,只要下载一个新模块并替换旧的即可。这让打补丁更灵活,缓存清理也更容易。

不过,这种模块化方式也带来了运行时复杂度,而虚幻引擎在历史上是用另一种方式处理这个问题的。

关于压缩率的现实说明#

一些社区分析已经表明,鸣潮的 .pak 文件本身已经被高度压缩。

有 Reddit 帖子估计,游戏 .pak 数据的平均熵约为每字节 7.6 比特,非常接近 8 比特/字节的理论压缩极限。作为参照:完全随机的数据熵就是 8 比特/字节,也就是无损压缩的理论极限。在信息论意义上,这说明数据已经高度随机化,几乎没有冗余信息可供再压缩。

来源:https://www.reddit.com/r/WutheringWaves/comments/1rtfrlg/lets_talk_about_storage_compression_and_how_well/

换句话说,问题很可能不是压缩算法太差。

关键在于:压缩效率与资源架构是两个独立的问题。即便 pak 文件压缩得完美无缺,只要底层的资源图存在重复或紧耦合的依赖,安装体积依然会很大。

4. 结论#

玩家看到手机上 120GB 的更新,很容易得出“开发者就是懒、就是在无视反馈”的结论。然而软件工程的现实往往要严酷得多。

库洛游戏的工程团队正在悄悄进行一场规模庞大、风险很高的架构改造,同时还要维持一款全球在线服务游戏的运转。把玩法系统从 TypeScript 迁走、重构深植于底层的遗留代码,是一个极其昂贵且不讨好任何人的过程——做得完美时,玩家不会注意到任何新东西;但只要犯一个错误,游戏就会崩溃。

这些都是无声的努力。他们在偿还多年累积的技术债,好让这款游戏能长期活下去。作为玩家,虽然包体膨胀确实令人沮丧,但我希望我们也能看见并理解他们在幕后扛着的那些巨大的工程挑战。

就他们目前展现出的方向而言,我个人保持谨慎乐观。

一点架构层面的补充#

除了资源管线与补丁机制的约束,鸣潮在运行时层面很可能还有额外的架构限制,例如可能影响性能的脚本系统。

那篇后续文章已经发布:《鸣潮的性能问题——GameThread 瓶颈、脚本层与长线运营技术债》,涵盖 GameThread 协调瓶颈、模式 A/B 的区分、Puerts/V8 运行时的证据、硬件缩放数据,以及来自 UE 专家与移动端性能剖析者的同行评议。

本文之后的存储进展(2026-04-27 更新)#

本文写于 v3.1 时期,当时官方给出的存储需求大约是PC 端 150GB、移动端 70GB。值得记录一下此后发生了什么变化:

平台v3.1(本文发布时)当前(v3.3)备注
PC约 150GB约 130GB逐步下降
移动端(完整)约 70GB实际约 60GB官方口径约 50GB
移动端(最小可玩)—约 37GBv3.3 资源清理功能之后

v3.3 的“资源清理”功能尤其值得注意——它允许玩家移除自己并不需要的地图环境资源,使移动端最小可玩体积降到约 37GB。这是有意义的进步:它并没有解决本文描述的底层硬引用架构,但表明库洛在主动降低那套架构对用户的实际影响。

PC 端从约 150GB 降到约 130GB 是渐进式的——与“逐步偿还资源管线债务、而非一次性大清理”的模式一致。底层的分包与依赖约束仍然存在,但方向是对的。

TL;DR#

鸣潮之所以这么大,是几个因素叠加的结果:

  • UE4 的硬引用风险导致资源在 chunk 之间被重复打包
  • 老的长线运营内容留下的依赖链条
  • 以及一套会在更新期间临时保存多个版本的游戏补丁管线

问题不在压缩,而在架构。库洛很可能是在运行时性能、稳定性与存储占用之间做平衡,而在大型虚幻引擎项目里,这种平衡常常被迫做出艰难取舍。但在幕后,他们的工程团队正在悄悄进行大规模的架构调整,以偿还技术债并保障这款游戏的未来。

更新(v3.3): 在 v3.3 的资源清理功能之后,PC 端存储需求已从约 150GB 降到约 130GB,移动端最小可玩体积从约 70GB 降到约 37GB。本文描述的底层架构依然存在,但它对用户的实际影响正在被主动削弱。

ToonXD#

  • ToonXD

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

[转载/熟肉] 鸣潮的存储膨胀:UE4 的限制、技术债,以及这款游戏为什么这么大
https://justpureh2o.cn/articles/32708/
作者
JustPureH2O
发布于
2026-10-17
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
JustPureH2O
穷方圆平直之情,尽规矩准绳之用
公告
JustPureH2O 的博客现已正式迁移至 Astro!原 Hexo 网站将移至 https://hexo.justpureh2o.cn/
音乐
封面

音乐

暂未播放

0:000:00
暂无歌词
分类
标签
站点统计
文章
108
分类
17
标签
64
总字数
418,721
运行时长
0 天
最后活动
0 天前
站点信息
构建平台
Vercel
博客版本
Firefly vunknown
文章许可
CC BY-NC-SA 4.0

文章目录