学习如何通过改进资产捆绑包的使用方式来节省内存使用量

无论您的应用程序是从内容交付网络(CDN)流式传输资产,还是将它们打包成一个大的二进制文件,您都可能听说过AssetBundles。AssetBundle 是一个包含一个或多个序列化资产(纹理、网格、音频剪辑、着色器等)的文件,可在运行时加载。
AssetBundles 可以直接使用,也可以通过Unity 可寻址资产系统(又称寻址系统)等系统使用。Addressables 系统是一个软件包,它提供了一种更方便、更受支持的方式来管理项目中的资产。它是资产捆绑包之上的一个抽象概念。虽然 Addressables 可以最大限度地减少开发人员与 AssetBundles 的直接交互,但了解 AssetBundles 的使用如何影响内存使用还是很有帮助的。有关可寻址系统的概述,请参阅本博文和2019 年哥本哈根联合会议的本场会议。
开发新项目的开发人员应考虑使用 Addressables,而不是直接使用 AssetBundles。如果你正在开发的项目已经建立了 AssetBundles 方法,这里关于 AssetBundles 如何影响运行时内存的信息将帮助你获得尽可能好的结果。
当 Unity 使用WWW 类(现已废弃)或UnityWebRequestAssetBundle(UWR) 下载LZMA AssetBundle时,Unity 会使用两个缓存(内存缓存和磁盘缓存)优化AssetBundle的获取、重新压缩和版本管理。
加载到内存缓存中的资产包会消耗大量内存。除非你特别想频繁、快速地访问 AssetBundle 的内容,否则内存缓存可能不值得你花费这么多内存。取而代之的是使用磁盘缓存。
如果向UnityWebRequestAssetBundleAPI 提供版本或哈希值参数,Unity 会将 AssetBundle 数据存储到磁盘缓存中。如果不提供这些参数,Unity 将使用内存缓存。请注意,Addressables 默认使用磁盘缓存。这种行为可以通过UseAssetBundleCache 字段来控制。
AssetBundle. LoadFromFile ()和AssetBundle.LoadFromFileAsync()始终使用 LZMA 资产包的内存缓存。因此,我们建议使用 UnityWebRequestAssetBundle API。如果无法使用UnityWebRequestAssetBundleAPI,可以使用AssetBundle.RecompressAssetBundleAsync()在磁盘上重写 LZMAAssetBundle。
内部测试显示,使用磁盘缓存和使用内存缓存在 RAM 方面至少有一个数量级的差别。您必须权衡内存成本与增加的磁盘空间需求以及应用程序的资产实例化时间之间的得失。
要确定 AssetBundle 内存缓存对应用程序内存使用的影响,请使用本机剖析器(我们选择的工具是Xcode 的 Allocations Instrument)检查 ArchiveStorageConverter 类的分配情况。如果该类使用的内存超过 10MB,则可能使用了内存缓存。

在为大型项目创建资产包时,不要认为 Unity 默认情况下会尽量减少资产包中的重复信息量。要在生成的AssetBundle 中识别重复数据,可以使用便捷的AssetBundle 分析器,它是由我们咨询与开发部的一位同事用 Python 编写的。通过命令行使用,该工具可从生成的 AssetBundles 中提取信息,然后将其存储到一个 SQLite 数据库中,该数据库具有多个有用的视图。然后,您可以使用DB Browser for SQLite 等工具查询数据库。无论您是手动创建捆绑包还是通过可寻址工具创建捆绑包,该工具都能帮助您发现并解决构建管道中的任何低效问题。

或者,也可以查看AssetBundle Browser 工具,您可以下载该工具并直接将其集成到您的项目中。请注意,该工具提供的功能与可寻址工具类似,因此如果您正在使用可寻址工具,则与该工具无关。
通过 AssetBundle Browser 工具,您可以查看和编辑给定 Unity 项目中 AssetBundle 的配置,并提供构建功能。它还提供了一些非常实用的功能,例如通知用户由于纹理等依赖关系而被提取的重复 Assets。

在决定如何将资产组织到 AssetBundles 中时,需要注意依赖关系。无论您的 AssetBundle 拓扑如何,Unity 都会区分应用程序二进制文件中(资源文件夹中或涉及资源文件夹)的 Assets 和需要从 AssetBundle 中加载的 Assets。你可以把这两类资产视为生活在不同的世界里。不可能创建一个 AssetBundle,它对资源文件夹世界中的资产实例有硬引用。为了引用这些 Assets,Unity 会复制 AssetBundle 世界中使用的 Assets。


以游戏徽标为例。游戏启动时,徽标可能会显示在加载场景的用户界面中。由于该加载屏幕必须在远程 Asset 流式传输到磁盘之前显示,因此可以在构建中包含徽标 Asset,以便立即使用。
用户界面的选项面板上也使用了同样的徽标,用户可以在这里选择语言、声音偏好和其他设置。如果该 UI 面板是从 AssetBundle 中加载的,那么该 AssetBundle 将创建自己的徽标资产副本。
如果同时加载加载屏幕和选项面板,就会同时加载两份徽标 Asset,这种重复操作会占用内存。
解决这个问题的办法是断开一个或两个屏幕之间的硬连接。如果徽标位于一个 AssetBundle 中,那么在获得对 Asset 的引用之前需要进行一定的流处理。如果徽标存在于二进制文件中(例如在 Resources 文件夹中),那么用户界面面板将需要对徽标资产进行弱引用,并通过Resources.Load 等 API 加载。

用户脚本需要在运行时使用字符串加载图片,并将其分配给适当的组件。一个令人满意的中间方案可能是在应用程序的StreamingAssets 目录中包含包含徽标资产的 AssetBundle。您仍将从 AssetBundle 中加载 Asset,但由于您是在本地托管该捆绑包,因此无需支付下载内容所需的时间成本。
使用字符串、路径或 GUID 引用资产并不直观,但您可能希望创建自定义检查器,在弱引用字段上启用 Unity 的默认拖放引用功能。不要忘记使用 Unity 的MemoryProfiler 软件包来识别内存中重复的资产。请注意,Addressables 系统有自己的机制来检查依赖关系中的重复内容(更多信息,请参阅文档)。
尽管 Addressables 系统在 AssetBundles 的基础上提供了一个抽象概念,但了解其内部的工作原理可以帮助你避免像本文所述的代价高昂的性能问题。
如果您目前正在使用 Addressables,我们希望通过这份简短的调查听取您的意见。
我们正在规划本系列未来的路线图。您希望我们重点关注哪个领域?请留言告诉我们!
