ainatipen 发表于 2022-4-16 11:43

Unity资源管理(AssetBundle & Addressables)

AssetBundle

概念

什么是AssetBundle?
AssetBundle就是一个资源捆绑包,一个AssetBundle里可以包含多种Asset,比如Prefab,Material,Audio等。
关于AssetBundle的使用,可以直接阅读官方文档
AssetBundle - Unity 手册
测试

先来看一组实验报告



实验一



实验二

从FrameDebug可以看到,被合成的大图





实验三

以下是实验一的过程分析:
这里,在记录Profiler之前,先执行一下Resources.UnloadUnusedAssets(),确保目前记录的数据是干净的。



UnloadUnusedAssets之前



UnloadUnusedAssets之后

可以看到,清理一些没有用到Assets之后,Textures少了1,Assets减少了44,Scene Objects减少了2

现在,加载一下我们的ab包。



加载ab后

可以看到,加载ab后,多了两个Asset。其他都是一样的。



加载的两个ab



加载所有assets之后

在把两个AssetBundle里的所有Assets都加载后,这里多了3个Texture,7个Material,49个Asset。



arts ab

可以看到,多出来的Texture和Material,正好是arts这个ab里面的资源。
其中49个Asset,这里占了11个,Arts文件夹也是一个Asset。

再来看另一个AssetBundle的内容



prefab ab

prefab的ab,总共只有2个Asset。

那剩下的多出来36个Assets是哪些呢?

首先来看我们的Prefab内容



节点树

这里的每个子节点,都包含4个Component和1个Material



子节点



根节点

而根节点多了一个Sphere的脚本,因此这里总的加起来正好是36个Assets。

那么到底什么是Asset呢?

Asset

对于Asset,初步理解,可以认为在项目Assets文件夹里面的所有东西,都可以认为是一个个的Asset,包括文件夹。
并且每个Asset都有对应的一个.meta文件,meta主要用来管理不同Asset之前的引用关系。
详细介绍可以看这篇博文

我们在加载了所有AssetBundle里的所有Asset之后,Profiler对应的Asset数目也增加了。但是GameObject的数量还是一样的。而Prefab里的每个GameObject挂载的Component也被算进Asset中,这是这里我们还没有对他们进行实例化放到场景中。所以GameObject的数量是没有变的,而Component正如我们在项目中创建C#的Compontent一样,也是一个Asset。

顺便,这里可以看一个简单的继承图



继承图

其中:
Object:Base class for all objects Unity can reference.
Component:Base class for everything attached to GameObjects.
GameObject:Base class for all entities in Unity Scenes.
Behaviour:Behaviours are Components that can be enabled or disabled.
MonoBehaviour:MonoBehaviour is the base class from which every Unity script derives.
这里再看一下代码



AssetBundle.cs

这里加载Assets,本质上就是加载Object的派生类或者Object本身。因此,上面的Assets的数量,其实就是Object的数量,而profiler中的GameObject数量,只有当我们实例化到场景中才会被算进去。

这里我们实例化一下prefab



实例化prefab



实例化之后

实例化之后,profiler的GameObjects增加了7个,正好是prefab中GameObject的个数。
除此之外,还多了36个Scene Objects,这里正好就是我们Prefab里面的36个Component。

Unload


AssetBundle的Unload可以传一个bool的参数进入,具体区别就是
False: 只卸载ab,不会写在已经load出来的Asset,也就是直接切断了asset和ab的联系
True:把ab和asset一起卸载



卸载ab

将加载的两个ab卸载后,AssetBundle.Unload(false),这里的Assets只少了2个。

UnloadUnusedAssets


这里再一次调用Resources.UnloadUnusedAssets();



Resources.UnloadUnusedAssets

在调用了Resources.UnloadUnusedAssets()之后,Textures数量少了1个,Assets也少了1个。
这是因为我们的Prefab里面的材质,只用了其中两张贴图,因此,有一个没有被引用的贴图就被卸载了。

查看我们初始化的测试代码



Instaniate

这里对prefab和实例化后的gameObject,都保留了引用。
这里,我们对_prefab置为null,然后再调用Resources.UnloadUnusedAssets()



去除prefab的引用

可以看到,Assets数量减少了36个,正好是Prefab里面的36个Component的Asset。

从这里可以推断出,Object内部有可能计算了引用计数,Resources.UnloadUnusedAssets会去遍历所有的Object,将没有被引用的Object销毁。

现在,将实例化的GameObject销毁掉,然后去除引用。



销毁



销毁GameObject

GameObjects的数量减少了7个,Scene Objects少了36个。但是其他的资源个数还是没变的。

最后调用Resources.UnloadUnusedAssets



UnloadUnusedAssets

Textures少了2,Materials少了7个,Assets少了12个。

再来看看最初的资源情况



最初的资源情况

正好和最初的资源情况吻合。



总共36个



AssetBundle内存模型

Addressable

概念

Addressables可以理解为是在AssetBundle的基础上,提供了一个管理系统可以让我们更好的组织和管理我们的资源。
以下是一些名词的解释


Asset address:资源的字符ID,可以通过这个ID去加载资源。
AssetReferences:可以通过定义这个类型的成员,在inspector中直接拖拽引用资源,包含加载资源的方法。
Label:资源标签,可以通过对不同资源打标签实现按标签加载。
Asset location:一个运行时的对象,描述了如何去加载资源和依赖,可以用作加载资源的key。
Key:用来标识一个或多个Adressables资源。例如地址(Asset address),标签(Label),资源引用(AssetReferences),资源位置(Asset location)。
Asset loading and unloading:在运行时的资源加载和卸载。
Dependencies:资源依赖。
Dependency and resource management:资源管理系统,通过引用计数来记录哪些资源正在被使用,哪些需要加载或者卸载依赖。
Group:资源的分组,通过分组的划分来决定哪些资源被打到一个AssetBundle里,以及如何加载。
Content catalogs:资产目录,类似Manifest,用来映射stringID(Asset address)跟实际资源存储位置。在通过address加载资源的时候,会通过这个目录去查找资源实际位置进行加载。
Content builds:内容打包,可以作为单独的步骤,和游戏打包分离开。
Multiple platform support:多平台支持,内部已经帮我们处理了多平台的路径支持。
Addressables tools:工具。

Asset addresses
这是Addressables系统的重要特性,就是我们可以通过自定义资源的地址(实际上就是一个字符串ID)来映射我们的资源,在运行时通过这个地址直接加载资源而不需要关注底层平台的不同造成的路径或者加载方式的不一样。
其中,加载的时候就是通过前面的Content catalogs去查找实际资源的存储位置。资源可以是打包进包体的,也可以是外部磁盘缓存或者远程资源。



三种不同的Addressables资源

通过Content catalogs来映射address和实际资源位置,使得我们可以不用关注实际资源在哪,在编辑器或者运行时都使用统一的加载方式。
这里提到,address并不需要唯一,比如当你有两份一样的资源,但是其中一个是高清资源,一个是标清资源。就可以给一样的address,然后设置不同的Label去区分。
· Asset 1: address: "plate_armor_rusty", label: "hd"
· Asset 2: address: "plate_armor_rusty", label: "sd"
LoadAssetAsync:只加载单个资源(第一个匹配的资源)
LoadAssetsAsync:可以加载多个资源。
tip:可以通过传入MergeMode来决定将不同的Keys加载出来的资源做合并或者交集。

Asset References
这是一个类型,可以用来引用任何的Adressables资源。Unity不会自动加载引用的资源,因此我们需要自己去控制加载和释放。这个类型可以在MonBehaviours和ScriptableObjects中使用,直接使用拖拽的方式。
另外,Addressables还提供了一些特定的引用类型,例如AssetReferenceGameObject和AssetReferenceTexture。也可以用AssetReferenceUILabelRestriction来通过Label去限制引用的资源范围。



定义



直接引用

Loading and releasing assets
要加载一个Addressables资源,可以通过地址或者别的Key来加载,例如Label或者AssetReferences。我们可以只加载主要资源,其他的依赖Addressables则会帮我们自动加载。
当我们不再需要这个资源的时候,需要调用Release去减少引用计数,当引用计数为0时,Addressables系统会帮我们去卸载这个资源。
这里我们也不需要去关注这个资源依赖了哪些资源,只需要在我们显示的去加载某个资源的之后,同样显示的去Release这个资源即可。

Dependency and resource management
在Unity中,有些资源是可以依赖别的资源的,例如场景资源可能依赖很多个Prefab,Prefab可能还依赖Materials或者Mesh资源。一个Material可能会被多个Prefab同时使用并且这些Prefab还分布在不同的AssetBundles里。当你加载资源的时候,系统会自动去查找和加载这个资源的所有依赖。当系统卸载一个资源的时候,也会自动卸载这些依赖,除非某些依赖还正在被别的资源使用。
当你加载和释放资源的时候,Addressables系统会自动的去帮你计算引用计数,来决定何时该卸载这些资源。

Addressables groups and labels
通过分组去组织资源内容。所有的资源有自己的分组,默认会有一个Default Group。
你可以配置Group Settings,去决定这个组内的资源如何被打进Bundles。例如可以配置组内的资源要不要被打进一个AssetBundle文件里。



分组下的打包设置

例如,当我选择第二个Pack Separately分开打包时



Arts分组

这个分组下的两个文件夹所包含的东西,就会被打成两个Bundles。



分开打包后的bundles

在分组里,还可以分别指定每个Asset的Label,在某些情况下可以根据Label批量加载资源。例如有三种Label:“Red”,“Hat”,“Feather”,可以通过一个运算直接加载所有“Red Feather Hat”的资源(通过前面说到的MergeMode)。也可以在打包的时候按照Label去决定要不要打到同一个Bundle里。

Group schemas
组模式可以定义一个分组内的资源如何被构建。例如用一个标准的模式来决定如何打包和压缩资源,另外一个模式来决定资源的类型。比如“发布后不可更改(静态资源)”或者“发布后可更改(动态资源)”。
你可以定义自己的Schemas在自定义的构建脚本中使用。

Content catalogs
Addressables系统提供一个资源目录(Content catalogs)文件去映射资源的地址(string ID)和实际的物理路径。Addressables每个工程只创建一个Content catalogs文件,但是你可以加载别的工程生成的Content catalogs。这就允许我们可以将资源构建的工作用另一个工程去做(也许对于大项目来说,确实是个不错的选择)。
当Addressables生成Content catalogs的时候,也同时创建了一个哈希文件,包含了这个Content catalogs的哈希(数字指纹)。如果我们把Addressables资源放在远程,系统会通过这个哈希来校验content catalogs是否有变更来决定要不要下载最新的。
构建的配置文件不同,也将决定Content catalogs的路径映射方式。
Content builds
Addressables的资源构建和工程打包是分开的,资源构建会生成资源Bundles,Content catalogs和哈希文件。因此,在工程打包之前,需要自己为每个平台先打包好Addressables。
Play mode scripts
这里其实就是Addressables提供了三种在编辑器下运行的模式:
· Use the Asset database:在该模式下Addressables直接加载Assets目录底下的资源。这是最快的一种方式,但是也是与生产环境区别最大的一种方式。当然这里不需要变更加载方式,Addressables已经帮我们做好了管理。
· Simulate groups:在这个模式下,Addressables会模拟groups的加载。虽然本质上还是直接从Assets去加载,但是在优化资源分组的时候,用这种模式会很方便,因为可以不需要每次都重新构建。
· Use existing build: 这种模式下,Addressables则是去通过上一次构建好的内容去加载。这个是最接近生产环境的方式。
Support for multiple platforms
Addressables在构建路径中包含了目标平台名称,来确保打包的时候能够正确的将平台文件复制到StreamingAssets。

配置

这里先对配置项做一些整体的介绍,详细的内容请跳转下面链接
配置Addressable主要包含以下几个方面:初始化,系统设置,分组设置,Profiles,托管,偏好,附加主题。
初始化
Addressables使用一个ScriptableObject assets的集合去存储你的配置,在工程的Assets/AddressableAssetsData 路径下。可以通过(menu:Window > Asset Management > Addressables > Groups)打开Groups窗口。第一次打开要点一下Create Addressables Settings去执行初始化命令来创建配置文件夹和存储配置的assets。



初始化配置



初始化之后

系统设置
AddressableAssetsSettings包含了这个项目全局的系统设置。可以通过上一步先打开Groups窗口,然后在Tools的tab下面查看系统设置的inspector面板,如下:



查看系统配置面板

分组设置
在概念介绍的时候,我们说过,Addressables通过分组去管理资源如何打包,比如打包到本地还是打包成远程资源包。每个分组都有自己独立的配置。当你创建一个新分组的时候,Addressables也会创建一个改组的配置文件。



组的配置

Profiles
通过Profiles,可以配置构建的变体集合。例如配置一个profile来作为开发环境的构建,另一个profile来作为生产环境的构建。Profile可以根据自己需要来创建。可以在Groups窗口下Profile的Tab下管理。



Profile

可以在Profiles管理窗口,创建自定义的打包输出路径和资源加载路径,例如默认的有Local和Remote。此外,还可以为所有的Profiles设置资源变体的入口。



变体

托管
Addressables提供了编辑器下的资源托管服务。可以通过http连接来测试远程资源。可以在Groups窗口下Tools的tab里找到Hosting的创建窗口,如下:



Hosting

偏好
Addressables将自己的首选项部分也加入到了Unity编辑器的Preferences中,如下图



Addressables Preferences

其中包括:
· Debug Build Layout
这个选项默认时false的,因为打开后会花更多的时间在构建上。如果打开,则构建系统会生成一个Build layout的报告。这个报告会包含一些对每个AssetBundle的细节的描述。
报告路径为:Library/com.unity.addressables/buildlayout.txt



buildlayout report

· Build Addressables on Player Build(Unity 2021.2+)
这个选项主要是用来决定,是否把资源的打包和工程的打包合并到一个步骤里(前面我们说过资源构建和工程构建是分开的步骤)
附加主题(TODO)
附加主题主要包括CI,构建脚本,自定义运行时初始化。

管理

Groups是主要的资产管理方式,所以在组内的设置上(每个分组都有自己独立的配置文件),是很重要的。除此之外,还有一些别的需要考虑的:

[*]Addressable Asset Settings:项目里全局的设置
[*]Profiles:定义构建路径的集合。
[*]Labels:资源的标签
[*]Play Mode Scripts:三种不同的运行方式(前面我们有介绍到)

AssetReferences提供了一个很界面友好的方式去使用Addressable资源。可以直接拖拽的方式去引用。除此之外,还提供了以下额外的工具:

[*]Analyze tool:提供了各种规则分析,可以在运行时检验是否按照我们想要的方式去组织资产。
[*]Event viewer:可以看到资产的事件,例如加载和卸载,来监视是否有内存泄露。
[*]Hosting Service:托管服务。
[*]Build layout report:提供构建报告。
[*]Build profile log:构建过程的日志,可以看哪个资源花费的时长最多。
关于如何组织资产,有以下几个方面需要考虑:

[*]按照逻辑分组
[*]运行时的性能表现
[*]运行时内存的管理
[*]考虑游戏的规模
[*]平台的特性
[*]资源的分配(远程和本地)
[*]资源的更新频率
[*]版本管理
通常的策略是:

[*]将需要同时使用的资产分到一组一起加载,例如某个关卡的所有资源
[*]按照逻辑实体划分,例如UI资源,纹理,音效,角色模型和动画。
[*]根据资源类别划分,例如音乐和纹理。
打包资源

资源的打包可以配置成和项目打包一起,也可以分开手动打包。
如果设置成和项目一起打包,可以直接通过调用BuildPipeline.BuildPlayer的api开始打包。Addressables的打包将会作为项目打包的预构建步骤。
资源的构建可以分为两种:

[*]本地:本地打包的资源,将会在项目构建的时候一起打进包体里,这里有一个比较需要注意的是,如果自定义了本地打包的路径,则需要手动将打包出来后的内容拷贝到Assets/StreamingAssets路径内再进行工程的构建。
[*]远程:远程资源的话,就是放到远程托管服务器,在运行时通过URL远程加载。
Group的配置决定这个的构建类型是哪种。使用的Profile将决定构建的路径。可以在Group窗口进行构建,也可以通过脚本。



资源构建

Addressables系统包含了以下几种构建脚本:

[*]Default Build Script:   根据group的配置,Profile,以及Addressables的系统设置执行完整的构建。
[*]Update a Previous Build:   执行差异内容构建来更新上一次构建的结果。
[*]Play Mode scripts:    这种构建方式是要根据Play Mode去做选择的。



Build

分配远程资源

将一些资源分配成远程资源,可以减少包体大小,并且还能远程更新而不需要重新发包。
当Enable构建远程catalog的选项,Addressables会通过这个远程的catalog去寻找远程资源的地址。
同时需要把Build & Load Paths也设置成Remote。



Remote Catalog

设置一个远程分组:



Group Setting

上面设置了远程的Build & Load Path,底下设置更新的限制,一种是如果资源有变更,直接重新打包整个bundle,一种是资源有变更,则把变更的资源移到新的组,再对这些资源进行打包,这种情况下,是有可能造成资源冗余的,详情看配置分析篇的Content Update Restriction部分。

使用Profile来辅助开发,Profile定义了Build & Load Paths的变体。
这里基于不同的开发环境和需求,可以定义不同的Profile来切换Build & Load Path。

关于AssetBundle的缓存
默认的AssetBundles会在下载后,在设备上缓存,这样下次使用就可以直接读取缓存。

一个更新后的catalog可以将不需要的旧资源排除在外,这样当更新完新的资源并且缓存后,旧的缓存就可以删除了。当不在需要缓存的时候,可以有以下几种选择:

[*]调用Caching.CLearCache清除所有缓存
[*]调用Addressables.CleanBundleCache来清理不再被引用的缓存
[*]可以在调用Addressables.UpdateCatalogs通过设置autoCleanBundleCache为true来让Addressables自动调用 Addressables.CleanBundleCache。



UpdateCatalogs

如果说对一个组内的资源Disable了Caching,则下载的时候只会存在内存中,而不会缓存到磁盘,每次应用加载的时候都会重新下载。



Group Settings

远程资源预下载
有时候我们希望可以提前下载好资源缓存起来,当需要使用的时候就能快速的从磁盘加载。可以使用Addressables.DownloadDependenciesAsync这个接口,这个接口会在后台静默下载整个资源和所有依赖。
这个接口返回的结构体包含一个PercentComplete属性,可以用来监控下载的进度。

关于这个进度百分比
有时候会线性有时候会不那么线性。比如当下载一个远程资源,但是依赖的资源已经在本地的时候,这个进度可能会在一开始的时候突变到50%,因为本地宝能够更快的加载。
如果需要询问玩家是否要下载的时候,可以调用Addressables.GetDownloadSize去获得这个资源的大小。需要注意的是,这个大小会把先前已经下载过的资源也算进去。
自定义的URL解析
可以使用签名后的URL或者动态端口。需要使用ID transform function。

关于Content catalogs
前面说到过,Content catalogs是一个资源地址和实际物理映射的文件,当项目打包的时候,会被放在StreamingAsset目录下,本地的Content catalog也可以用来处理远程资源,和处理本地资源是没有区别的。但是,如果我们希望发布后更新远程资源,就需要打开Build Remote Content catalogs的选项。



Build Remote Catalog



Content catalog

同一个项目下Addressables只会生成一份Content catalog,如果远程的Content catalog的哈希与本地的不一致,Addressables将会下载并缓存远程的版本来替换本地的Content catalog。

加载额外的catalogs
当然,你也可以加载额外的Content catalogs,通过不同的项目。例如可以构建一个只有艺术资源的项目,来给不同的项目使用。
可以使用LoadContentCatalogAsync这个接口去加载catalog,但是需要注意的是,这个接口不会缓存catalog,因此如果是远程的catalog,则每次都会使用WebRequest去加载。为了避免这种情况,可以提供一个和catalog同名的哈希,来正确的缓存catalog。

运行时使用Addresssables(TODO)


Addressables使用引用计数系统来管理资源。
以下有一些关于资源加载卸载的方式介绍:

[*]Loading an single asset
[*]Loading multiple assets
[*]Loading an AssetReference
[*]Loading Scenes
[*]Loading assets by location
[*]Instantiating objects from Addressables
[*]Releasing Addressable assets
[*]Using Addressables in a Scene
[*]Downloading dependencies in advance
Addressables关于加载的task几乎都是异步的,下面有关于如何处理的信息:

[*]Releasing AsyncOperationHandle instances
[*]Coroutine- and IEnumerator-based operation handling
[*]Event-based operation handling
[*]Task-based operation handling
[*]Using operations synchronously
[*]Custom operations
[*]Using typed versus untyped operation handles
[*]Reporting operation progress
最后还有一些关于其他运行时的话题:

[*]Customizing initialization
[*]Loading additional catalogs
[*]Updating catalogs
[*]Modifying resource URLs at runtime
[*]Getting the address of an asset at runtime

诊断工具

诊断工具主要包含以下四种:

[*]Analyze tool: 可以查看资源冗余以及系统如何打包资源,可以通自定义规则去生成额外的报告信息。

[*]检查资源的冗余依赖
[*]检查Resources到Addressables的冗余依赖
[*]检查Scene到Addressables的冗余依赖
[*]Bundle Layout 预览

[*]Event viewer:可以查看运行时的操作事件,例如加载和卸载
[*]Build layout report:描述Addressables如何将组内的资源打包到Bundles
[*]Build profile log:构建时的日志,可以在Chrome中查看。


样例(TODO)


升级

Upgrading to the Addressables system

参考:
曾志伟:【Unity游戏开发】AB学习(三)加载和实例化的内存变化
AssetBundle - Unity 手册
Addressables | Addressables | 1.19.19
页: [1]
查看完整版本: Unity资源管理(AssetBundle & Addressables)