Content directories: Beyond the AssetBundle
George Ing - Unity Technologies
Senior Engineering Manager
今天位聊聊Unity的内容创作。
自Unity 2.1 以来,不起眼的AssetBundle就一直是Unity内容(在 Player 二进制文件之外发布)的基础。在过去的二十年里,数量惊人的游戏都使用了AssetBundles作为其数据表示形式,其中包括一些世界上规模最大的游戏。
从根本关卡,每个AssetBundle都是一个不可分割的单元,用于分发、存储和加载。AssetBundles在包关卡轨其依赖项,形成一个更大的单体单元,为了提高效率,需要一起下载、加载和卸载。
由于这一规范,游戏如何定义其AssetBundle布局是影响其运行时性能和下载大小等一切因素的主要因素。无论直接使用AssetBundles ,还是通过Addressables包,情况都是如此。
今天,我们要谈谈Unity运行时的一些不同之处。
介绍内容目录
内容目录是AssetBundles的基础性、高性能、细粒度替代方案。它们目前在Unity 6.6 中可用,可作为 Player 自带AssetBundles的替代方案。在Unity 7 版本中,技术栈将扩展以处理完整的、精细的空中传输(稍后会详细介绍——这非常酷)。
内容目录并非将资源烘焙成大型可加载单位,而是使Unity运行时能够独立识别、加载和卸载单个资源。(网格、纹理等)充分利用硬件资源并隐式进行内容去重。

现在,管理Unity项目的内容比以往任何时候都更加容易,构建速度更快,对资源位置和AssetBundle布局的担忧也更少。
你编译版本的游戏体积更小、速度更快、隐式去重,并且可以通过新的可加载引用类型访问完整的动态内存管理。对于使用Addressables来存储播放器捆绑内容的用户,您可以切换到内容目录。无需编写任何代码即可进行更改。
我们是如何make这一切的?让我们深入了解一下管线的每个阶段。
熟悉的基础:Build
早在 2019 年,我们就讨论过我们希望Unity中一个严肃的编译版本基础具备哪些特性——在每个核心上并行运行、确定性、完全缓存,并且能够在机器之间共享其编译版本数据。结果发现,我们的一些同事多年来一直在发布一个具有这些特性的API ,即资产导入框架。
因此,内容目录编译版本过程是在标准资源导入器框架内运行的。每个资产都是由资产导入器单独构建的,在沙盒环境中且在进程外的。构建过程是确定性的,具有资产数据库缓存、所有硬件核心的完全饱和以及原生加速器支持,可实现快速共享构建。
这可能不是你第一次听说这种编译版本系统。你们中的一些人可能还记得我们在Unity 2023.1 中预告的多进程编译版本管线。这是同一个基础。

输出结果是一组松散的、粒度很高的工件,一个跟踪它们之间依赖关系的清单,以及一设置新的诊断文件,make理解您的编译版本比以往任何时候都更容易。
这非常简单。
熟悉的基础:解决
如果你打开一个内容目录编译版本并查看其中的工件,你会注意到每个瑕疵都有一个非常奇怪的哈希名称:
c0152db4dd710be51b2decb997325f34.cff0a44ad4a4babd121543fd44032928e7.resS4226b5c16a50dab6eff0f08dd1253d4b.resource
酷的是——这不是随机生成的哈希值。相反,内容目录系统使用与 git 等技术相同的内容寻址存储模式。每个内容文件都通过其内容的哈希值进行命名和引用。这种模式对Unity运行时非常有用,因为它允许 Unity 以原生功能的形式隐式地对内容进行去重。
也就是说,具有真正依赖关系图的内容寻址存储存在高流失的风险。考虑两个物体之间最简单的关系:
A → B
UpdateB,以及 B 的哈希值变化。不幸的是,因为 A 引用了 B,所以 A 的哈希值也发生了变化。更糟糕的是,这种影响会一路向上蔓延。
相反,工件之间不通过内容哈希相互引用,而是通过稳定id进行引用。编译版本清单维护着一个小型查找表,将稳定 ID 映射到内容哈希值。
这样一来,运行时就拥有了加载所需的一切!
熟悉的基础:加载
在内容目录中,我们引入了一个性能极其优异的动态加载系统。
当内容目录挂载时,加载系统会读取清单并解析构件之间的稳定id依赖关系。它允许每个瑕疵完全独立地加载和卸载,不会受到相邻工件的干扰。 (还记得AssetBundles的问题吗——一个单一的、不可分割的单元?)已消失。)
对于 DOTS 用户来说,工件的底层文件格式可能看起来也相当熟悉。内容目录生成并加载我们在 2022 年 DOTS 中引入的下一代内容文件格式,将四年来一直支持多线程Entities加载的技术带到所有资产。
这是一个完全异步的加载系统,读取和反序列化操作都是异步的。这意味着更高的加载带宽和平台指定的异步读取 API 的利用。

更令人兴奋的是,内容目录基础架构使我们能够首次将现代可加载引用类型引入Unity ,称为Loadable 。
可加载的<网格> bodyMesh;bodyMesh.Load();
这是一个真正的可加载引用,直接内置于基于内容目录的项目引擎中。可加载对象引用的对象将被拉入编译版本中,但只有在调用该可加载对象后才会加载。将 Loadables 与(非常)熟悉的ScriptableObject接口结合使用,可以让你以惊人的速度组织动态加载的内容。
我们对此原则感到非常兴奋。以内容目录和可加载项为基础,任何资产在Unity中,从角色创建器的各个部分到流式地形的各个部分,都可以成为独立加载和卸载的单元——这是引擎直接内置的功能——无需围绕AssetBundle布局或组定义进行设计。
使用Unity缩放构建游戏从未如此简单!
对内容目录进行测试:Slime牧场2
在过去的几个月里,我们的一些合作伙伴非常慷慨地允许我们用他们的游戏测试内容目录。
例如,让我们来看看 Monomi Park 的优秀游戏《Slime牧场 2》,以及它从切换到内容目录中获得的一些好处。(《Slime牧场》现已在Steam上架!)Slime牧场,Slime牧场2 )
此数据基于运行在MacBook Pro (M5 最大 ) 上的Unity 编辑器版本Unity 6.6 Beta (6000.6.0b10)。
最棒的是,用户能够立即感受到新加载系统带来的好处。更酷的是, 《Slime牧场2》是一个现有的Addressables项目,它在没有任何代码更改的情况下切换到了内容目录。
坦白说,我们迫不及待地想看看Unity生态系统中的游戏在 6.6 版本发布内容目录后会获得哪些好处。但这引出了一个问题,远程内容呢?
接下来是什么:远程内容分发
参加今年Unite Seoul 路线图演示的各位可能还记得,Jason Mann 曾透露,我们新的内容目录基础架构将使即将到来的Unity 7 世代的远程内容交付make更加容易。让我们简要地谈谈这究竟意味着什么,以及它如何与我们目前讨论的内容联系起来。
总而言之,有了内容目录,我们就有了一个小型的哈希驱动清单,能够唯一地标识工件及其依赖项。问题是,所有这些物品都必须随玩家一起运送吗?
答案是绝对的否定。

得益于现代 HTTP 标准, Unity现在可以复用大量资源请求。
再加上内容目录基础,运行时可以准确地确定设备缺少哪些工件,只将这些工件下载到本地存储中,然后加载它们——而无需担心共存位置、数据布局或依赖的AssetBundles。更新在单个瑕疵关卡传播,并且由于清单使用内容哈希进行操作,运行时可以低成本地判断哪些工件已过时,并仅获取差异。
这种近乎未来的Unity运行时只会下载所需的内容,从而缩短开发时间,让玩家更快地进入游戏,并大幅降低CDN成本。
我们将在 2027 年分享更多关于该项目的信息。