使用Unity Addressables可寻址资源系统
Rubén Torres Bonet是一名优秀的游戏开发者,参与开发的游戏包括:《Time Stall》、《Catan Universe》、《Diamond Dash》、《Jelly Splash》等,这些游戏面向Oculus Quest、Android、iOS、PS4、Xbox One等多个平台,全球的累计安装量超过3000万次。
本文,Rubén Torres Bonet将分享使用Unity Addressables可寻址资源系统的方法。
引言
如果你正带领着一支由程序和美术组成的团队,在6个月内要完成将精美的PS4 VR游戏移植到Oculus Quest,并且面临只有1个月的时间处理内存减半的问题。
你会怎么做?我们可以考虑使用Unity Addressables可寻址资源系统。
在移植的过程中,我们必须同时处理多项复杂的任务,不同的开发者会有不同的考虑方向。但哪项任务会最耗费时间呢?
我猜超过70%的人会说:在把游戏移植到Oculus Quest平台时,CPU和GPU的性能是开发者面临最大的问题。
性能是VR游戏中最难提升的方面之一。对于这类优化,我们需要对产品有深入的了解。但这是一个极为耗时的过程,有时我们甚至无法进一步优化,只能牺牲大量游戏内容和图形效果。但这样导致移植出来的成品不符合玩家的期待,这也是非常棘手的问题。
“性能”这个词往往让开发者感到不寒而栗。Oculus Quest平台的性能是怎样的?项目在Oculus Quest的运行效果如何?
实际上,如果对Oculus Quest有一定开发经验,你可能知道,其实把Oculus Quest作为移动硬件来看,它的功能是非常强大的。Oculus Quest的主要区别在于它的主动冷却系统,该系统对可用CPU/GPU硬件级别提供了其它移动平台没有的大幅提升效果。
此外对于Oculus Quest的软件方面,和通用的Android变体相比,专用操作系统更加适合对虚拟现实渲染进行优化。
在过去几年中,移动硬件正在快速赶上独立平台。但要实现持续在72fps下渲染依旧非常困难,特别是从高端平台移植的VR项目。更确切地说,谈到Oculus Quest的移植时,我们必须把该平台设想为使用带有屏幕、电池、四个摄像头和一个风扇的高通骁龙835处理器的设备。
这款设备的缺点从其它角度看也可以是优点。该移动平台是经过大量研究的硬件,如今有很多已知的技巧可以快速把其CPU和GPU的负载降低到可以接受的程度。
我们任务重点是:和PS4相比,Oculus Quest减少了一半RAM内存。PS4的RAM内存是8GB,而Oculus Quest是4GB。
这是粗略估算的结果,因为在两个平台上,操作系统都不会允许使用所有内存,这样它可以跟踪一些子系统,让生态系统正常工作。在Oculus Quest上,我们最多可以使用约2.2 GB的RAM内存,否则会出现问题。
合适的内存处理对游戏来说非常重要,因为有两个限制:
严格的内存限制:如果超过特定使用量阈值,操作系统会直接关闭游戏。
缓冲的内存限制:除了特定限制外,在用户最小化游戏窗口,脱下VR设备,或走出Oculus Guardian区域时,操作系统也可能会关闭游戏,
我们不希望这些问题出现在游戏中,否则可以想想一名玩家在丢失2小时游戏进度后的愤怒。
2.2 GB的可用RAM内存其实并不多。对于从一开始就跟踪统计数据的新项目,这并不是问题,但如果要移植项目到性能大幅降低的硬件,这会是一个大问题。
如果过去处理过类似的移植过程,你一定明白减半游戏的RAM内存预算有多么困难。这项任务取决于游戏架构是否对这种改动做好准备。
减小内存压力的最常用方法包括:调整资源压缩设置、优化脚本、减少着色器变体等。
根据项目的具体情况,调整纹理导入设置是我们的首选方法,但如果需要的话,我们也可以压缩网格、动画和音频。但这些方法本身非常复杂,而且有一定限制。
不是所有平台都支持相同的导入设置,面向多个设备构建会大幅提升构建管线的开销,更不用说QA阶段、美术、设计和编程方面的复杂性。开发者要考虑:某款Android设备是否支持ASTC或ETC2?
我们也希望构建64位的版本,同时在32位版本上留住玩家。我们要想好到底要创建多少个APK版本,更糟糕的是,游戏的每次更新都要对每个版本分别管理和测试。我们希望让工作变得轻松,所以我们不应该只依赖这些方法。
我们希望更进一步,让事情尽可能简单,特别是在进行移植的时候。为了性能而重新设计游戏是最坏的选择,不如不进行移植。
由于以上这些原因,我们将介绍传统资源管理方法和可寻址资源管理系统。它将帮助你在数小时内减半项目的内存预算。
为了展示这个过程,我们会把简单的旧项目移植到全新的Unity Addressables可寻址资源系统。
下图是使用Unity Addressables的最终效果图。
使用Unity Addressables的原因
我们的目标是分析如何轻松改进内存,快速实现改进效果。最有效最简单的实现方法是:加载初始场景,打开Profiler性能分析器。
由于未优化的游戏架构可以在游戏中任意时间点频繁进行分析,所以最快捷的检查方法是:对初始场景进行性能分析。因为不少游戏经常有开销很大的脚本,它会造成资源引用过度消耗性能。
无论在特定时间是否使用某些资源,这类脚本组件会一直独立加载所有资源。如果游戏容易受到内存容量的限制,那么这是风险很大的使用方法,因为游戏无法随着开发者添加资源而扩展,例如:未来加入的DLC内容。
如果开发者要面向不同的设备开发,每种设备会提供不同的内存预算,我们必须考虑最糟糕的内存情况。
另一方面,是否有传统资源管理方法可以提供很好使用效果的情况呢?答案是肯定的。
如果你正在为PS4等同类平台开发项目,大多数要求已经在一开始决定好,使用全局对象的优点可能会胜过新内存管理系统的额外复杂度。
如果有足够好的使用效果,使用传统全局对象满足所有需求是一种很好的解决方案。这样可以简化代码,预加载所有引用的资源。但在任何情况下,传统的内存管理方法不适合打算挑战硬件极限的开发者。
现在,我们开始使用Unity Addressables可寻址资源系统吧。
学习准备
我们需要使用Unity 2019.2.0f1或更高版本,然后从GitHub上下载的Level 1项目:
https://github.com/The-Gamedev-Guru/Unity-Addressables-Level-1/archive/master.zip
Unity Addressables - 红色天空盒
Level 1开发者:传统资源管理方法
我们从最简单的传统资源管理方法开始。在本示例中,需要在我们的组件中有天空盒材质的直接引用列表。
设置过程只需简单的三步:
通过Git下载Level 1项目。
打开Unity Hub,添加Add按钮。找到下载目录,打开项目文件。
在Windows系统,按下Ctrl+P,在Mac系统,按下CMD+P。
我们可以按下按钮修改天空盒。这种方法很原始,也非常乏味。
我们的项目结构围绕着两个主要系统。我们有SkyboxManager游戏对象,其中的组件是保存天空盒材质引用的主要脚本,可以根据UI事件来切换材质。
using UnityEngine;
public class SkyboxManager : MonoBehaviour
{
[SerializeField] private Material[] _skyboxMaterials;
public void SetSkybox(int skyboxIndex)
{
RenderSettings.skybox = _skyboxMaterials[skyboxIndex];
}
}
SkyboxManager对象为UI系统提供功能,可以通过使用RenderSettings API,应用特定材质到场景设置。
其次,我们有CanvasSkyboxSelector。该游戏对象包含一个画布组件,用于渲染一组垂直分布的按钮。
点击每个按钮时,它会调用之前提到的Manager函数,根据按钮ID交换渲染的天空盒。也就是,每个按钮的OnClick事件会在Manager对象调用SetSkybox函数。
场景的层级窗口
我们按下Ctrl+7或Cmd+7,或者点击Window > Analysis > Profiler,打开性能分析器,点击顶部的“录制”按钮。
几秒后停止录制,查看各项指标信息:CPU,内存等。项目的性能结果很好。我们会专注于内存部分,简单的视图模式会展示以下内容。
简单的内存性能分析结果
考虑到我们只会在一段时间展示一个天空盒,纹理大小的数值非常夸张,这是我们会在很多未优化项目中发现的情况。
在示例场景中,我们只使用了几个天空盒而已。而在其它项目中往往会有角色、星球、音效、音乐等更多内容。
现在我们要把内存分析部分切换为细节模式,然后进行查看。
详细的内存分析结果
我们发现所有天空盒纹理都加载到内存中,但每次只会显示一个天空盒。但这个简单的架构使用了400MB左右的内存。
这样的结果无法令人满意,特别这仅仅是游戏的一小部分。解决这个问题的方法是我们下一部分所谈到的使用Unity Addressables可寻址资源系统。
Level 2开发者:使用Unity Addressables
开发游戏时,我们往往会从Level 1部分的情况开始,现在我们将学习使用Unity Addressables可寻址资源系统来进行处理。
访问GitHub获取Level 2项目文件:
https://github.com/The-Gamedev-Guru/Unity-Addressables-Level-2/archive/master.zip
通过对性能分析器的观察,我们知道,虽然每次只会使用一个天空盒,但是项目中所有天空盒都会加载到内存中。因为我们可能会遇到不同资源变体数量的限制,所以这并不是有可扩展性的解决方案。
现在,我们来学习使用Unity Addressables可寻址资源系统摆脱传统资源管理方法的束缚。
首先,添加新工具Unity Addressables可寻址资源系统的API。我们要安装Addressables资源包,打开Window > Package Manager,窗口如下图所示。
Unity资源包管理器
安装好Addressables资源包后,我们要把材质标记为可寻址资源,在检视窗口勾选Addressable。
Level 2资源管理方法:使用Unity可寻址资源
这可以让Unity在可寻址资源数据库包含这些材质和纹理依赖。该数据库会在我们的构建中用来打包资源为多个部分,从而在游戏中任意时候轻松加载。
打开Window > Asset Management > Addressables,此时会打开我们的数据库。
我们查看之前的SkyboxManager脚本,注意到该脚本仍旧保存着资源的直接引用。我们不希望这样,所以必须告诉脚本如何使用间接引用,即使用AssetReference。
下面,我们要优化脚本组件。
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;
public class SkyboxManager : MonoBehaviour
{
[SerializeField] private List<AssetReference> _skyboxMaterials;
private AsyncOperationHandle _currentSkyboxMaterialOperationHandle;
public void SetSkybox(int skyboxIndex)
{
StartCoroutine(SetSkyboxInternal(skyboxIndex));
}
private IEnumerator SetSkyboxInternal(int skyboxIndex)
{
if (_currentSkyboxMaterialOperationHandle.IsValid())
{
Addressables.Release(_currentSkyboxMaterialOperationHandle);
}
var skyboxMaterialReference = _skyboxMaterials[skyboxIndex];
_currentSkyboxMaterialOperationHandle = skyboxMaterialReference.LoadAssetAsync();
yield return _currentSkyboxMaterialOperationHandle;
RenderSettings.skybox = _currentSkyboxMaterialOperationHandle.Result;
}
}
我们对以上代码进行一些解释。
主要改动发生在第7行,我们在此利用AssetReference保存了一组间接引用。修改后,材质在被引用时不会自动加载。我们必须明显指定加载操作,它们才会进行加载。之后,请在编辑器中重新指定该字段。
第13行:由于现在使用异步工作流程,所以我们倾向于使用协程。我们会开启一个新的协程,用于处理天空盒材质的变化。
在第18-20行,检查是否有天空盒材质的句柄,如果有,我们会释放之前渲染的天空盒。每次使用Addressables API执行这类加载操作时,我们都会得到为之后操作保存的句柄。句柄是一种数据结构,包含有关特定可寻址资源管理的数据。
在第23行,我们将特定可寻址引用解析到天空盒材质,然后调用它的LoadAssetAsync函数。我们在第25行使用yield关键字,因此我们会在进一步处理前等待这项操作。由于使用了泛型,所以不需要开销较大的调用。
最后,在材质和依赖加载后,我们会在第26行修改场景的天空盒。材质会在Result字段提供,该字段属于我们用来加载材质的句柄。
Level 2资源管理方法:AssetReference列表
现在我们查看该方法的实际效果,执行的步骤如下:
在Addressables窗口中,单击Build Player Content,构建内容。
然后为所选择的平台构建版本。
运行版本,将它和内存分析器连接起来。
Build Player Content选项
如下图所示,我们看到性能分析结果非常出色。
内存分析器
优秀的性能分析结果可以改善多个方面,这意味着玩家可以在低端设备上玩你的游戏,也会为游戏开发者带来更多收入。这就是Addressables可寻址资源系统的强大功能。
Addressables系统会给团队带来少量成本。程序员必须支持异步工作流程,使用协程很容易实现该工作流程。设计师需要学习Addressables系统的功能,例如:可寻址分组,积累经验来作出聪明的决策。
使用Unity Addressables可寻址资源系统,我们实现了以下效果:
适当的内存管理功能。
更快的初始加载速度。
更快的安装速度,减少应用的存储大小。
更好的设备兼容性。
异步架构。
能够在网上存储内容,把数据和代码互相分离。
进阶内容:实例化和引用计数
我们将Unity Addressables工作流程应用到了天空盒,由于每次只会启用一个天空盒,所以过程非常简单,但我们在游戏中要管理的东西不只是天空盒。
例如,我们可能会给游戏角色添加变体。这个角色可能是玩家角色,也可能是敌人角色,这样难度就变大了。
现在我们不只是要卸载资源并加载新资源,我们可能打算卸载的资源正在被其它实例使用,这种情况下如何进行内存管理呢?
Unity可以使用自带的集成引用计数器来处理这种情况。这意味着:每次我们实例化某个资源时,它会自动增加引用数量。每次我们卸载资源时,引用计数会减小。如果引用数量减小为0,即不存在任何实例,那么该资源便可从内存卸载。
这些操作可以使用下面的结构完成。
var operationHandle = prefabMaterialReference.InstantiateAsync(transform, true);
yield return operationHandle;
// ...
Addressables.ReleaseInstance(operationHandle);
请注意:低于Unity 2019的版本中Instantiate和Destroy不会更新引用计数,这些计数只受到Addressables系统的InstantiateAsync和ReleaseInstance变体的影响。因此,我们要避免在使用Addressables系统时,使用到Instantiate和Destroy。
其它加载方式
如果你希望使用硬编码的资源字符串标识符,而不是使用AssetReferences,我们可以在Manager类中使用其它结构。
它们的行为是相似的,该结构会返回开发者可以命令的async操作句柄。由于它有泛型形式,Result字段会设为我们最初传入方法调用的类型。
_currentSkyboxMaterialOperationHandle = Addressables.LoadAssetAsync<Material>("Skybox" + skyboxIndex);
我们传入函数的字符串标识符不仅可以在资源检视窗口设置,也可以在Addressables主窗口设置。此外,我们也可以加载某个标签的所有资源,这样有助于预加载所有会在后面关卡生成的敌人。
在网络提供内容
Level 3 开发者: 进阶使用Addressables
在前面两部分内容中,我们通过从传统资源管理系统转为Addressables系统的工作流程,实现了很好的性能提升。只需要少量的时间成本,我们就可以更好扩展项目中的资源,同时保持较低的内存使用量。
我们目前只探索了Addressables系统的部分功能,我们仍可以使用这个不同寻常的系统通过多种方法改进项目。
对于Addressables系统,我们不必记住所有详细功能,但建议开发者对所有功能进行大致的了解,因为在开发过程中,我们可能会遇到更多挑战。
了解Addressables的全部功能会给我们提供帮助,因此我们准备了额外的指南,你可以在指南中了解以下内容:
Addressables窗口。
Addressables系统的分析:内存泄漏会产生很大问题。
通过网络供给减少用户等待的时间。
构建管线集成。
实际方法:加速工作流程,不必花10分钟以上的时间等待。
更重要的是,该指南可以回答下列问题:
Send Profiler Events有什么潜在影响?
AddressableAssetSettings API的实用性如何?
如何把所有内容和BuildPlayerWindow API集成?
Fast Mode,Virtual Mode和Packed Mode的区别是什么?
请发送“可寻址指南”到微信公众号后台,获取指南文档下载地址。
小结
本文,我们介绍了项目挑战:在一个月内,为面向Oculus Quest开发的项目减少一半的内存预算。Unity Addressables可寻址资源系统帮助我们按时完成耗费时间的任务,避免对大量游戏内容进行重新设计,通过导入设置几乎无法实现这些目标。
Addressables可寻址资源系统是一个很有前景的系统,Unity开发人员正在努力改进该系统。它适用于正式制作,而且提供完善的文档。
我们可以把使用Addressables可寻址资源系统看做一项中期投入。投入一些时间,让项目在未来数月或来年得到提升。不仅我们从中受益,由于游戏可以支持更多设备,更多玩家也将能够玩你的游戏。
下载Unity Connect APP,请点击此处。 观看更多Unity官方精彩视频,请关注“Unity官方”B站账户。
你可以访问Unity答疑专区留下你的问题,Unity社区和官方团队帮你解答:
Connect.unity.com/g/discussion
推荐阅读
Unity Labs新一代AR/MR工具:Project MARS
喜欢本文,请点击“在看”