Addressable Assets System

Addressable Assets System

本篇包含了Addressable基础篇系列的第五节至第九节,主要内容为Addressable Assets System的简介,Addressable Assets的开发周期、托管服务、内存管理和Addressables分析器。

五、Addressable Assets System简介

在Addressable基础篇系列的第二节《Resource的优势与痛点》中,我们分别讲了Resource和AssetBundles在开发中的优劣势以及各自的痛点,也讲了开发中的替代方案AssetDataBase。那总的来说其实就是开发和发布用不一样的加载方式,并且每个项目需要单独为这套东西去分别实现。

Unity通过这么多年开发人员的反馈,似乎才意识到他们的资源管理有多难用,并且在18版本推出了Addressable Assets System的预览版本。19的版本已经成为正式版本,可以用于生产了。但其实我们是尝鲜的小白鼠,在过程中还是遇到了不少的坑,入坑的时候,Addressable Assets System还在0.8版本,现在已经是1.3.3了。所以后续的内容都是基于最新版本1.3.3来阐述的。

总的来说,Addressable Assets System按照字面意思就是可寻址资源系统。目的就是给资源绑定可标识的寻址标识,然后通过内部的GUID进行资源管理。它的灵感应该是来自于5.X之后的资源标签(其实很早以前 资源商店就有类似的Bundles插件了),并且最终管理和生成的还是AssetBundles,这点上面其实没有本质区别。

Addressable Assets System由两个部分组成,即:
1、Addressable Assets窗口。
2、脚本化构建。

官方的角度阐述,Addressable Assets窗口是主要的,脚本化构建是辅助的。但其实实际用下来之后,描述可能稍有偏差。应该说Addressable Assets窗口是开发期间管理和调整资源的,而脚本化构建则用来做自动化构建的(Addressable Assets窗口也可以提供按钮操作)。

5.1 安装Addressable

Addressable可以通过Package Manager来下载,如下:

注意,Addressable Asset System需要Unity版本2018.3或更高,如果对PackageManager
不熟悉的同学可以参照官方文档

5.2 特有名词

在进行Addressable Assets System系统阐述之前,我们需要先了解一下这个系统的一些术语或者叫概念:

  • Address:便于运行时索引的Asset位置标识符。
  • AddressableAssetData目录:存储你工程里所有Addressable Asset的元数据。位置在工程的Assets目录。
  • Asset group:一组可以在构建时处理的Addressable Assets.
  • Asset group schema:定义一组数据,这些数据可以在生成期间分配给Group并使用。
  • AssetReference:类似于直接引用但会延迟初始化的对象。AssetReference对象将GUID存储为可以按需加载的Addressable的GUID。
  • Asynchronous loading:允许在开发过程中更改asset 及其依赖项的位置,而无需修改游戏代码。异步加载是Addressable Asset System的基础实现。
  • Build script:运行asset group处理器来打包assets,并为资源管理器提供地址和资源位置之间的映射。
  • Label:提供用于运行时加载类似项的附加 Addressable Asset标识符。比如(Addressables.DownloadDependenciesAsync("spaceHazards");)。

5.3 如何准备Addressable Assets

准备可寻址资产大概分为两个部分,第一标识资产,第二构建资产。

5.3.1 标识可寻址资产
Unity Editor下有两种方式可以标记Assets为Addressable:

  • object's Inspector
  • Addressables窗口

5.3.1.1 使用Inspector窗口
在“Project”窗口中,选择一个asset,查看它的Inspector。在Inspector中,单击“Address”复选框,并输入想要的名字。

5.3.1.2 使用Addressables窗口
选择Window > Asset Management > Addressables来打开Addressables窗口。把想要的标识的asset从Project窗口里拖动到Addressables里已经建好的Addressables Groups里即可。

5.3.2指定地址
默认的Address名称是这个asset到Assets目录的相对路径(比如:Assets/images/myImage.png)。如果你要从Addressables改变Asset的地址名字,可以右键Asset并且选择Rename。

如果你是第一次使用Addressable Assets,系统会为你的工程自动创建一些编辑时和运行时的配置Asset,目录为Assets/AddressableAssetsData。你最好把这些目录加入到版本控制里去。

5.3.3 构建可寻址内容
在你构建APP之前,Addressables Asset System需要先把配置好的asset内容打包成运行时能用的格式。但这个步骤不是自动的,你可以通过Editor的面板操作或者系统提供的API。

  • Editor打开Addressables窗口,选择select Build > Build Player Content。
  • API使用AddressableAssetSettings.BuildPlayerContent()。

5.4 如何使用Addressable Assets

5.4.1通过地址加载或者实例化
你可以在运行时加载或者实例化一个Addressable Asset。当加载一个asset的时候,会连它的所有依赖项一起加入到内存中,以便你可以正常使用这个asset。但这并不会自动把asset放入场景中,所以必须自己去实例化。调用Addressables提供的实例化接口,会加载这些asset并且将它们立即添加到场景里去。

如果要从脚本访问一个asset,需要提供一个string类型的地址。然后调用UnityEngine.AddressableAssets命名空间下的下列方法:

  • Addressables.LoadAssetAsync ("AssetAddress");这个加载指定地址的asset。
  • Addressables.InstantiateAsync("AssetAddress");这个实例化指定地址的asset,并且添加到场景中

注意:LoadAssetAsync and InstantiateAsync都是异步操作。你需要提供一个回调函数给调用资源加载的逻辑。当加载结束的时候,会调用这个传入的回调函数。

5.4.1.1 ###Sub-assets和Components
Sub-assets和components是asset加载的特殊情况。

  • Components :目前还不能通过Addressable直接加载GameObject的Components。必须加载或实例化GameObject,然后从其中查找组件引用。要了解如何扩展Addressable以支持组件加载,可以参阅ComponentReference示例
  • Sub-assets:这套系统支持加载Sub-assets,但需要特殊的语法。Sub-assets的例子包括sprite sheet中的sprite ,或者FBX文件中的animation clips等。有关直接加载sprites的示例,可以参考sprite加载示例

从asset里加载sub-objects可以使用下面的API:
Addressables.LoadAssetAsync <IList >("MySpriteSheetAddress");

如果加载单个的sub-objects,可以使用:
Addressables.LoadAssetAsync ("MySpriteSheetAddress[MySpriteName]");
assets中可用的名称可以在主Addressables group编辑器窗口中查看。此外,还可以使用AssetReference访问assets的子对象。

5.4.2 使用##AssetReference类
AssetReference类提供了一种访问Addressable Assets的方法,并且不需要知道它们的地址。可以通过下列步骤:
1、从Scene的hierarchy界面或者Project 界面选择一个GameObject。
2、在Inspector界面,点击 Add Component按钮,然后选择component类型。任何可序列化的Component 都可以支持AssetReference变量(例如,游戏脚本、ScriptableObject或其他可序列化的类)。
3、在Component 中添加一个public AssetReference 变量(例如,public AssetReference explosion;)。
4、在Inspector中,选择要链接到对象的 Addressable Asset,方法是将Asset从Project窗口拖到public AssetReference字段,或者从项目中先前定义的Addressable Asset的下拉列表中选择(如下图所示)。

若要加载或实例化AssetReference ,请调用其相应的方法。例如:
AssetRefMember.LoadAssetAsync ();
或者
AssetRefMember.InstantiateAsync(pos, rot);

注意:与普通Addressable Assets一样,LoadAssetAsync和InstantiateAsync是异步操作。你可以提供一个回调,以便在Assets完成加载时调用它(Async operation handling部分可以获得更多信息)。

如果将包含sub-assets (如SpriteAtlas或FBX)的assets添加到AssetReference中,则可以选择引用Assets本身或sub-assets。原本看到的一个的下拉列表会变成两个。第一选择Assets本身,第二选择sub-assets.如果在第二个下拉列表中选择了sub-assets,这将被视为对主Assets的引用。

5.5 构建需要的考量

5.5.1 StreamingAssets目录里的本地资源
Addressable Asset System在运行时需要一些文件来了解要加载什么和如何去加载它。这些文件会生成Addressables数据并存放在StreamingAsset文件夹中,这个文件夹,前面章节已经介绍过,是Unity中的一个特殊文件夹,它包含构建中的所有文件。生成Addressable内容时,系统将在库中分阶段处理这些文件。然后,当构建应用程序时,系统会将所需的文件复制到StreamingAsset,生成并从源文件夹中删除它们。这样,你可以为多个平台构建数据,但同时只包含在每个构建平台中的一份相关数据。

除了特定于Addressable的数据外,任何构建供本地使用的数据的组也将使用特定于库平台的缓存位置。要验证这是否有效,需要将构建路径和加载路径分别设置为配置文件变量,即[UnityEngine.AddressableAssets.Addressables.BuildPath]和{UnityEngine.AddressableAssets.Addressables.RuntimePath}开始。可以在AddressableAssettings的Inspector 中指定这些设置(默认情况下,此对象位于项目的Assets/AddressableAssetsData目录中)。

5.5.2 提前下载
调用Addressables.DownloadDependenciesAsync()方法会加载传入的地址或标签的依赖项。通常,依赖项是指assetBundle。

此调用返回的AsyncOperationHandle结构包含一个PercentComplete属性,您可以使用该属性监视和显示下载进度。你也可以让应用程序等到内容加载完成。

如果希望在下载前征得用户的同意,请使用Addressables.GetDownloadSize()返回从给定地址或标签下载内容所需的空间。但要注意,这会排查到任何以前下载过的bundles ,因为它们仍然在Unity的assetBundle缓存里。

尽管提前下载应用程序的asset 大多数时候是有利的,但在某些情况下,你也可能选择不这样做。例如:

  • 如果你的应用程序有大量的在线内容,而且你通常希望用户只与其中的一部分进行交互。
  • 你有一个应用程序,必须连接到网上才能发挥作用。如果应用程序的所有内容都是小包,您可以选择根据需要下载内容。

5.5.3 多平台构建
Addressable Asset System在构建应用程序内容时生成包含可Addressable Asset的AssetBundles。AssetBundles依赖于平台,因此必须为你想要支持的每个独特平台重新构建。

默认情况下,在构建Addressables应用程序数据时,给定平台的数据存储在Addressable构建路径的特定平台子目录中。运行时路径对这些平台文件夹进行了说明,并指向适用的应用程序数据。
注意:如果在Editor Play模式中使用Addressables BuildScriptPackedPlayMode脚本,Addressable将尝试加载当前激活的构建目标的数据。因此,如果当前构建目标数据与当前编辑器平台不兼容,就可能会出现问题。

5.6 升级到Addressables system

那么既然这套系统这么好用了,已经有的工程能否升级?现有的AssetBundles改如何处理呢?不要急,有套餐。

5.6.1 直接引用
要从这种方法迁移,请执行以下步骤:

  • 用asset references替换对对象的直接引用(例如,public GameObject DirectRefMenger;变成public AssetReference AssetRefMember;)。
  • 将资产拖到适当组件的Inspector上,就像直接引用一样。
  • 如果希望基于对象而不是字符串名称加载Asset,则直接从在设置中创建的AssetReference对象(例如,AssetRefMember.LoadAssetAsync ();或AssetRefMember.InstantiateAsync(pos,ROT);)实例化它。注意:Addressable Asset system是异步加载Asset。在更新对资产引用的直接引用时,还必须更新代码以适配异步操作。

5.6.2 Resource文件夹
当将Resources资料文件夹中的Asset标记为Addressable时,系统会自动将Resources文件夹中的Asset移动到项目中名为Resources_Move的新文件夹。移动Asset的默认地址是旧路径,省略文件夹名。例如,加载代码可能会从Resources.LoadAsync (“desert/tank.prefab”);更改为Addressables.LoadAssetAsync (“desert/tank.prefab”);。

5.6.3 AssetBundles
打开Addressables Groups窗口时,Unity提供将所有AssetBundles转换为Addressable Asset groups。这是迁移代码的最简单方法。

如果选择手动转换Asset,请单击“Ignore”按钮。然后,使用前面描述的直接引用或Resource文件夹方法。
Asset地址的默认路径是其文件路径。如果你使用路径作为Asset的地址,你将以与从包加载相同的方式加载asset。Addressable Asset System处理包及其所有依赖项的加载。


六、Addressable Assets开发周期

上一节简介了Addressable Assets System。我们大致可以了解到它的优势就是可以把资源的规划、构建和加载进行分离,而以往这些都是揉在一起的。

6.1 传统的资产管理

如果你把资源规划在Resources目录下,那就表示它们会被构建在你应用的初始包里,并且你只能使用 Resources.Load的API,并且传递一个路径参数去加载你要的内容。要想在任意地方引用资源,你需要将对资源进行直接引用,或者打成AssetBundles。如果使用了AssetBundles,你还是需要传递资源路径,以及在加载的时候将它们根据一些策略组合起来。如果你的AssetBundles在远程或者有些依赖项在其它的AssetBundles里面,你还需要自己写代码去管理下载,加载和卸载它们。

6.2 #Addressable资产管理

给Asset绑定一个地址,然后你就可以通过这个地址去加载它,而不用关注它的位置、目录或者你怎么构建这个资源。你可以任意的更换一个可寻址资源的名称、并且不会出问题。你也可以将一个可寻址资源移动至Resources目录,或者从一个本地的构建策略转为远程,或者其它的构建策略而不用修改你的构建或者加载代码。

6.2.1 Asset group schemas
Schemas定义了一组数据。你可以在Inspector上把一个Schemas绑定到一个Asset Groups上。这组绑定的Schemas会决定怎么构建它下面的资源内容。例如,在Packed mode模式下构建时,带有BundledAssetGroupSchema模式的组充当AssetBundles的源。还可以将一组Schemas组合成用于定义新Group的模板。通过AddressableAssetsSettings的Inspector面板可以添加Schemas模板。

6.3 构建脚本

构建脚本呈现为项目中实现IDataBuilder接口的ScriptableObject assets。用户可以创建自己的构建脚本,并通过Inspector将它们添加到AddressableAssetSettings对象中。要在Addressables Groups窗口(Window>AssetManagement>Addressables>group)中应用构建脚本,请选择Play Mode Script,然后选择下拉选项。目前,实现了三个脚本以支持完整的应用程序构建,以及三个用于在编辑器中迭代的播放模式脚本。

6.3.1 Play Mode Scripts
可寻址资产包有三个构建脚本,用于创建Play Mode数据,以帮助用户加速应用程序开发。
Use Asset Database(faster)
Use Asset Database(BuildScriptFastMode)允许你在游戏流程中快速运行游戏。它直接通过Asset Database加载Asset ,这会在不需要分析器或assetBundle创建的情况下进行快速迭代。

Simulate Groups(advanced)
Simulate Groups mode(BuildScriptVirtualMode)在不创建assetBundles的情况下分析布局和依赖项的内容。Asset通过ResourceManager从assetDataBase加载,就假装它们是通过包加载的一样。若要查看游戏期间Bundles加载或卸载的时间,请在Addressables事件查看器窗口(Window>Asset Management>Addressable>Event Viewer)中查看Asset使用情况。Simulate Groups mode可以帮助您模拟加载策略,并调整内容Groups以找到生产和发行版的适当平衡。

Use Existing Build(requires built groups)
Use Existing Build最接近于已部署的应用程序生成,但它要求你将数据作为单独的步骤进行构建。如果不修改Asset,则此模式是最快的,因为它在进入Play模式时不处理任何数据。必须通过选择Build>New Build>Default Build Script,或者在游戏脚本中使用AddressableAssetSettings.BuildPlayerContent()方法,在Addressables组窗口(Window>Asset Management>Addressable>group>group)中构建此模式的内容。

Choosing the right script
要应用Play Mode Script,请从Addressables Groups窗口菜单(Window>AssetManagement>Addressable>group)中选择Play Mode Script并从下拉选项中选择。在开发和部署过程中,每种模式都有不同的时间阶段和资源布置。下表说明了特定模式有用的开发周期的各个阶段。

6.4 分析和调试

默认情况下,Addressable Assets只记录warnings and errors日志。但可以通过打开Player settings窗口(Edit > Project Settings... > Player)来启用详细的日志记录。>Player),导航到Other Settings > Configuration部分,并在 Scripting Define Symbols字段中添加“ADDRESSABLES_LOG_ALL”。

还可以通过取消选中AddressableAssetSettings对象Inspector中的LogRuntimeException选项来禁用异常。如果有需要,可以使用自己的异常处理程序来实现ResourceManager.ExceptionHandler属性,但这需要在Addressables完成runtime initialization之后(参见下面)。

6.4.1 初始化对象
可以将对象绑定到Addressable Assets settings,并在运行时将它们传递给初始化程序进行处理。CacheInitializationSettings对象在运行时控制Unity的缓存API。若要创建自己的初始化对象,请创建一个实现IObjectInitializationDataProvider接口的ScriptableObject。这是负责创建与运行时数据序列化的ObjectInitializationData的系统的Editor组件。

6.5 内容更新工作流

Unity建议将游戏内容分为两类:

  • Static内容,不会更新的静态内容。
  • Dynamic内容,希望更新的动态内容。

在这种结构中,静态内容附带在应用程序(或安装后很快下载),并且驻留在很少的并且较大的Bundles中。动态内容驻留在网上,最好是在较小的包中,以尽量减少每次更新所需的数据量。Addressable Assets System的目标之一是使该结构易于使用和修改,并且无需更改脚本。

但是,Addressable Assets System也可以适应需要更改“静态”内容的情况,比如你不想发布一个全新的APP构建。

请注意,在不允许远程更新的情况下(如许多当前的视频游戏控制台,或没有服务器的游戏),每次都应该进行完整的、全新的构建。

6.5.1 它是如何工作的
Addressables使用内容目录将地址映射到每个Assets,并指定加载地址的位置和方式。为了给你的应用程序提供修改映射的能力,你的原始应用程序必须知道这个目录的线上副本。若要设置该设置,请在AddressableAssetSettings检查器上启用“Build Remote Catalog”设置。这将确保将目录副本构建到指定路径并从指定路径加载。一旦应用程序发布,这个加载路径就不能再改变了。内容更新过程会创建目录的新版本(具有相同的文件名),以便在先前指定的加载路径上覆盖对应的文件。

构建应用程序时,会生成一个唯一的应用程序内容版本字符串,该字符串标识每个应用程序应该加载哪些内容目录。给定的服务器可以包含多个版本的目录,它们之间不会冲突。我们会将需要的数据存储在addressables_content_state.bin文件中。这包括版本字符串,以及包含在标记为StaticContent的组中的任何Asset的散列信息。默认情况下,此文件与AddressableAssetSettings.Asset文件位于同一个文件夹中。

addressables_content_state.bin文件包含Addressables系统中每个StaticContent asset group的散列和依赖信息。构建到StreamingAsset文件夹的所有Groups都应标记为StaticContent,某些大型远程Group也可以这样指定。在进行下一步(准备内容更新,如下所述)期间,此散列信息会确定所有的StaticContent组是否包含需要更改的Assets,以及是否需要将这些Assets转移到其它地方。

6.5.2 准备内容更新
如果已经修改了任何StaticContent组中的assets,则需要运行“Check for Content Update Restrictions”命令。这将从静态组中得到任何修改后的asset,并将其移动到一个新Groups中。生成新的Asset Groups:
1、打开Unity的Addressables Groups窗口。(Window > Asset Management > Addressables > Groups)
2、Addressables Groups窗口上选择菜单Tools,然后点击Check for Content Update Restrictions。
3、在打开的BuildDataFile对话框中,选择addressables_content_state.bin文件(默认情况下,该文件位于Asset/AddressableAssetsData Project目录中。

此数据用于确定自上次构建应用程序以来有哪些Assets或依赖项已被修改。为了准备更新内容的生成,系统将将这些Assets移动到一个新的Group中。

注意:如果您的所有更改仅限于non-static groups,则此命令将不起任何作用。

重要:在开始prepare operation之前,Unity建议你使用版本控制系统打个分支,并在分支上进行操作。prepare operation会以适合的updating content的方式对Asset Groups进行重新排列。分支会确保下次再发布一个新player时,可以快速回滚到你自定义的设定。

6.5.3 构建内容更新
构建一个内容更新:
1、Unity打开Addressables Groups窗口。(Window > Asset Management > Addressables > Groups)
2、Addressables Groups窗口,选择上部的菜单Build,然后选择Update a Previous Build.
3、在Build Data File 对话框打开的时候,选择一个已经存在的APP构建的构建目录,这个目录下必须要包含contain an addressables_content_state.bin 文件。

构建会生成一个内容目录、一个Hash文件和asset bundles。

生成的内容目录具有与选定应用程序生成中的目录相同的名称,并且会覆盖旧目录和hash文件。应用程序会加载hash文件来确定新目录是否可用。系统从应用程序附带的或已经下载的现有AssetBundles中加载未经修改的Assets。

系统使用addressables_content_state.bin文件中的内容、版本、字符串和位置信息来创建AssetBundles。不包含更新内容的AssetBundles的文件名和原来的一样。如果AssetBundles包含更新的内容,则生成包含更新内容的新AssetBundles,该包会具有新的文件名,以便与原始文件共存。带有新文件名的AssetBundles必须复制到指定的内容托管位置。

系统还可以为静态内容构建AssetBundles,但是并不需要将它们上传到内容托管位置,因为没有Addressableasset entries引用它们。

6.5.4内容更新样例
在本例中,需要了解以下组概念:

当此版本处于活动状态时,有些玩家的设备上有Local_Static,并且可能还在本地缓存了其中一个或两个远程包。

如果你从每个Group (AssetA、AssetL和AssetX)修改了一个Asset ,那么运行Check for Content Update Restrictions,本地Addressable设置中的结果现在是:

请注意,prepare operation实际上就是编辑静态组,这可能会与正常的理解有冲突。系统会自动构建上述布局,但是任何静态Groups都会丢弃生成结果。因此,您将从玩家的角度得出以下结论:

LocalStatic bundle已经在玩家的设备上,所以不能更改它。这个旧版本的AssetA不再被引用。相反,它被残留在玩家设备上,作为垃圾数据。

Remote_Staticbundle保持不变。如果它还没有缓存在玩家的设备上,当请求AssetM或AssetN时,它将会被下载。像AssetA一样,这个旧版本的AssetL不再被引用。

Remote_non-Static包现在已经过时了。您可以从服务器中删除它,但无论是哪种方式,都不会再从这里下载了。如果已经缓存了,那么它会一直留在缓存。像AssetA和AssetL一样,这个旧版本的AssetX也不再被引用。

旧的Remote_non-Staticbundle 被替换为一个新版本,该版本由hash文件来区分。修改后的AssetX版本将使用这个新Bundle进行更新。

content_update_group的Bundle将由被引用的、修改后的Assets组成。
注意,上面的示例具有以下意义:
1、任何更改后的本地Assets都会永久残存在在用户的设备上。并且保持未使用状态。
2、如果用户已经缓存了一个非静态Bundle,他们将需要重新下载包,包括未更改的Assets(例如,AssetY和Assetz)。理想情况下,用户没有缓存Bundle,在这种情况下,他们只需要下载新的Remote_non-Static包。
3、如果用户已经缓存了Static_Remote bundle,他们只需要下载更新的Asset(在本例中,通过content_update_group下载AssetL)。但这种情况是很理想的。如果用户没有缓存过Bundle,则必须通过content_update_group下载新的AssetL,并通过未引用的Remote_Static bundle下载现已失效的AssetL。不管初始的缓存状态如何,在完成的某个时候,用户都会在他们的设备上拥有已停用的AssetL,尽管从未被访问或者引用过,但却会被永久的缓存。

远程内容的最佳设置将取决于你怎么去使用。


七、Addressable Assets托管服务

托管服务提供了一个集成的工具,用于使用Addressable Assets配置数据,为本地或网络连接的应用程序构建提供打包的内容。托管服务旨在提高测试打包内容时的迭代速度,还可以用于在本地和远程网络上为连接的客户端提供内容服务。

7.1 Packed mode测试与迭代

从Editor Play mode测试到平台应用程序构建测试,会给开发过程带来复杂性和时间成本。托管服务提供可扩展编辑器-嵌入内容传递服务,直接映射到你的Addressables组配置。使用自定义Addressables配置文件,你可以快速配置应用程序,以便从Unity Editor本身加载所有内容。这包括部署到移动设备或任何其他平台的、对你的开发系统具有网络访问权限的构建。

还可以将Asset托管服务部署到服务器环境中,通过batch mode(headless)运行,为内网和外网的Unity客户端应用程序提供内容托管。

7.2 设置

这节会详细介绍Asset托管服务的初始设置。虽然侧重于编辑器的工作流,但可以通过设置AddressableAssetSettings类的HostingServicesManager属性来使用API配置托管服务。

7.2.1 配置托管服务器
使用Hosting window窗口添加、配置和启用新的托管服务器。

在编辑器中,选择Window>AssetManagement>Addressables>host,或者单击Addressables组窗口菜单中的Tools>HostingServices按钮来访问Hosting窗口。

若要添加新的本地托管服务,请单击“Create > Local Hosting”按钮。

注意:有关实现自定义托管服务类型的更多信息,请参见关于自定义services部分。
新添加的服务会出现在 Addressables Hosting 窗口的HostingServices部分。使用“Service Name”字段,为服务输入名称。

新服务默认为禁用状态。若要启动服务,请选择“Enable”按钮。

HTTP宿主服务在启动时自动分配端口号。端口号在Unity sessions之间保存和重用。若要选择不同的端口,请在“Port”字段中分配特定的端口号,或使用“Reset ”按钮随机分配不同的端口。

注意:如果重置端口号,则必须执行完整的应用程序生成,以生成和嵌入正确的URL。
HTTP宿主服务现在已经启用服务了,可以从每个asset group的BuildPath中指定的目录中提供内容了。

7.2.2 Profile设置
在开发期间使用托管服务时,Unity建议创建一个配置文件,该配置文件将所有Asset Groups配置为使用专门创建的一个或多个目录来从托管服务加载内容。

在编辑器中,选择Windows>AssetManagement>Addressables>Profiles,或者单击Addressables组窗口菜单中的Tools>Profiles按钮来访问Profiles窗口。还可以通过AddressableAssetSettings Inspector访问这些设置。

接下来,创建一个新的配置文件。在下面的示例中,新的配置文件名为“Editor Hosted”。

修改加载路径字段,以替代从托管服务加载。HttpHostingService是一个URL,它使用分配给服务的本地IP地址和端口。在Addressables托管窗口中,可以使用名为PrivateIpAddress和HostingServicePort的配置文件变量来构造URL。(例如,http://[PrivateIpAddress]:[HostingServicePort]).)

此外,你应该修改所有构建路径变量,以指向Assets folder之外的公共目录。

验证每个组的配置是否正确。确保将BuildPath和LoadPath路径设置为各自的配置文件键,这些配置文件键已被修改以用于托管服务。在本例中,您可以看到如何展开LoadPath中的配置文件变量,以构建从托管服务加载的正确的基本URL。

最后,从Addressables Groups 窗口中选择新的配置文件,创建一个构建,然后部署到目标设备。Unity Editor现在通过HttpHostingService服务处理来自应用程序的所有加载请求。你现在可以在不重新部署的情况下对内容进行添加和更改。重新生成Addressable内容,并重新启动已部署的应用程序以刷新内容。

7.2.3 Batch mode
还可以从Unity Editor下运行批处理模式来使用托管服务。使用以下选项从命令行启动Unity:

  • batchMode -executeMethod UnityEditor.AddressableAssets.HostingServicesManager.BatchMode
    这将从默认的AddressableAssetSettings对象加载托管服务配置,并启动所有已配置的服务。
    若要使用替代的AddressableAssetSettings配置,请创建自己的静态方法入口点,通过UnityEditor.AddressableAssets.HostingServicesManager.BatchMode(AddressableAssetSettings设置调用)重载。

7.3 自定义服务

托管服务设计为可扩展的,允许你实现自己的自定义逻辑,用以服务Addressable Assets System的内容加载请求。例如:

  • 支持使用非HTTP协议下载内容的自定义IResourceProvider。
  • 管理用于服务与生产CDN解决方案相匹配的内容的外部流程(例如ApacheHTTP服务器)。

7.3.1实现自定义服务
HostingServicesManager可以管理实现IHostingService接口的任何类(有关方法参数和返回值的详细信息,请参阅API文档。

若要创建新的自定义服务:
1、按照上面的配置新主机服务部分中列出的步骤,但不要选择Create>Localhost按钮,而是选择Create>CustomService按钮。
2、将适用的脚本拖放到其字段中,或从对象选择器中选择它。该对话框验证所选脚本是否实现了IHostingService接口。
3、完成添加服务,单击“添加”按钮。
接下来,你的自定义服务就会出现在ServiceType下拉选项中。


八、Addressable Assets内存管理

8.1 镜像加载和卸载

在使用Addressable Assets时,确保正确管理内存的主要方法就是正确管理加载和卸载的调用。怎么做则取决于Asset类型和加载方法。但是,在所有情况下,释放方法都可以接受加载的Asset,也可以接受Load返回的操作句柄。例如,在场景创建期间(下面描述),Load返回一个AsyncOperationHandle ,您可以通过这个返回的句柄或通过保持句柄同步释放AsyncOperationHandle

8.1.1 Asset加载
要加载Asset,请使用Addressables.LoadAssetAsync或Addressables.LoadAssetsAsync。
这会将Asset加载到内存中,而不会实例化它。每次加载调用执行时,它都会为每个加载的asset添加一个引用计数。如果您使用相同的地址三次调用LoadAssetAsync,您将得到AsyncOperationHandle结构的三个不同实例,所有这些实例都引用相同的底层操作。对于相应的Asset,该操作的引用计数为3。如果加载成功,则生成的AsyncOperationHandle结构将包含.Result属性中的asset。您可以使用Unity的内置实例化方法使用加载的Asset来实例化,这不会增加Addressable的引用计数。

若要卸载Asset,请使用Addressables.Releases方法,该方法将减少ref计数。当给定Asset的引用计数为零时,该Asset就会被卸载,并减少任何依赖项的引用计数。

注意:Asset可能会被卸载,也可能不会立即卸载,这取决于现有的依赖关系。

8.1.2 Scene加载
要加载场景,请使用Addressables.LoadSceneAsync。您可以使用此方法以Single模式加载场景,这将关闭所有打开的场景,或以Additive模式加载场景,有关详细信息,请参阅场景模式加载的文档
若要卸载场景,请使用Addressables.UnloadSceneAsync,或在Single模式下打开新场景。您可以使用Addressables接口或使用SceneManager.LoadScene或SceneManager.LoadSceneAsync方法打开一个新场景。打开一个新场景将关闭当前场景,并适当地减少引用计数。

8.1.3 GameObject 实例化
要加载和实例化GameObject asset,请使用Addressables.InstantiateAsync。这将实例化由指定地址参数定位的Prefab。Addressable系统将加载Prefab及其依赖项,增加所有相关Asset的引用数。
在同一个地址上三次调用InstantiateAsync将导致所有相关Asset的引用数为3。但是,与三次调用LoadAssetAsync不同,每个InstantiateAsync调用都返回指向唯一操作的AsyncOperationHandle。这是因为每个InstantiateAsync的结果都是唯一的实例。InstantiateAsync和其他Load调用之间的另一个区别是可选的trackHandle参数。当设置为false时,必须在释放实例时保留要使用的AsyncOperationHandle。这更有效率,但需要更多的开发精力。

若要销毁实例化的GameObject,请使用Addressables.ReleaseInstance,或关闭包含实例化对象的场景。这个场景可以是加载(从而关闭)在Additive模式或Single模式。这个场景也可以使用Addressable或SceneManagementAPI加载。如上所述,如果将trackHandle设置为false,则只能使用句柄调用Addressables.ReleaseInstance,而不能使用实际的GameObject调用。

注意:如果在未使用Addressables API创建的实例上调用Addressables.ReleaseInstance,或使用trackHandle==false创建的实例,则系统将检测到该实例并返回false,以指示该方法无法释放指定的实例。在这种情况下,实例不会被销毁。

InstantiateAsync有一些相关的开销,因此如果你需要每帧实例化相同的对象数百次,可以考虑通过Addressables API加载,然后通过其他方法实例化。在本例中,可以调用Addressables.LoadAssetAsync,然后保存结果并为该结果调用GameObject.Instantiate()。这允许灵活地以同步方式调用实例化。缺点是Addressable系统不知道你创建了多少实例,如果管理不当,可能会导致内存问题。例如,引用纹理的Prefab将不再具有要引用的有效加载纹理,从而导致BUG出现(或闪退)。这些问题很难跟踪,因为你可能不会立即触发内存GC(请参阅下面关于清除内存的部分)。

8.1.4 Data加载
Data loading不需要它们的AsyncOperationHandle.Result发布的接口,仍然需要释放操作本身。例如Addressables.LoadResourceLocationsAsync和Addressables.GetDownloadSizeAsync。它们加载你可以访问的数据,直到释放操作为止。这个版本可以通过Addressables.Releases完成。

8.1.5 后台交互
在AsyncOperationHandle.Result字段中不返回任何内容的操作具有一个可选参数,可在完成时自动释放操作句柄。如果在这些操作句柄完成后不再需要其中一个操作句柄,请将autoReleaseHandle参数设置为true,以确保清除操作句柄。你希望autoReleaseHandle为false的场景是,你需要在操作句柄完成后检查它的状态。这些接口的例子是Addressables.DownloadDependenciesAsync和Addressables.UnloadScene。

8.2 Addressables事件查看器

使用Addressables Event Viewer窗口监视所有Addressable system 操作的ref计数。要访问编辑器中的窗口,请选择Window>AssetManagement>Addressables>Event Viewer。

注意:为了查看事件查看器中的数据,必须在AddressableAssetSettings对象的检查器中启用SendProfilerEvents设置。

事件查看器中有以下信息:

  • 白色垂直行指示发生加载请求的帧。
  • 蓝色背景表示当前已加载的asset。
  • 图的绿色部分表示asset的当前参考计数。

8.3 内存何时清除

不再引用的Asset(由分析器中蓝色部分的末尾指示)并不一定意味着Asset已被卸载。一个常见的适用场景涉及一个AssetBundle中的多个Asset。例如:
1、在一个AssetBundle中有三个Asset(树、坦克和牛)。
2、当树加载时,分析器为树显示一个引用计数,AssetBundle显示一个引用计数。
3、稍后,当坦克装载时,分析器会为树和坦克各显示一个单一的引用计数,但为这个AssetBundle显示两个参考计数。
4、如果你释放树,它的参考数变成零,蓝色的条消失了。

在本例中,树Asset在这一点上实际上没有被卸载。你可以加载AssetBundle或它的部分内容,但不能部分卸载AssetBundle。在AssetBundle本身完全卸载之前,不会卸载任何Bundle中的Asset。此规则的例外是调用引擎接口Resources.UnusedAsset。在上述场景中执行此方法将导致树卸载。因为Addressables系统无法知道这些事件,分析器图只反映Addressable的参考计数(而不是内存容量)。注意,如果您选择使用Resources.UnusedAsset,这是一个非常慢的操作,应该只在不影响正常显示的屏幕上调用(例如loading屏幕)。

8.4 AsyncOperationHandle

Addressables API中的几个方法都会返回AsyncOperationHandle结构。此句柄的主要目的是允许访问操作的状态和结果。操作的结果在调用Addressables.Relace或Addressables.ReleaseInstance之前回保持有效。

当操作完成时,AsyncOperationHandle.Status属性要么是AsyncOperationStatus.Succeeded ,要么是AsyncOperationStatus.Failed。如果成功,可以通过AsyncOperationHandle.Result属性访问结果。

你可以定期检查操作状态,也可以使用AsyncOperationHandle.Complete事件注册已完成的回调。当不再需要返回的AsyncOperationHandle结构提供的assets时,应该使用Addressables.Releases方法来释放它。

8.4.1 Type和.typeless句柄
大多数Addressables API方法返回一个泛型AsyncOperationHandle struct,保障AsyncOperationHandle.Completed事件和AsyncOperationHandle.Result对象的类型安全性。还有一个非泛型的AsyncOperationHandle结构,您可以根据需要在这两个句柄之间进行转换。
注意,如果尝试将非泛型句柄强制转换为不正确类型的泛型句柄,则会发生运行时异常。例如:

8.4.2 AsyncOperationHandle使用范例
使用AsyncOperationHandle.Completed回调为完成事件注册侦听器:

AsyncOperationHandle实现了IEnumerator,因此可以在coroutines中生成:

Addressable还通过AsyncOperationHandle.Task属性支持异步await :

在WebGL上无法使用AsyncOperationHandle.Task属性,因为该平台上不支持多线程操作。请注意,使用SceneManager.LoadSceneAsync加载场景,并将allowSceneActivation设置为false或使用Addressables.LoadSceneAsync并为activateOnLoad参数设置false可能会导致随后的异步操作被阻塞,无法完成加载。请参阅lowSceneActivation文档

8.5 创建自定义的operations

IResourceProviderAPI允你通过以数据驱动的方式定义位置和依赖关系来扩展加载过程。

在某些情况下,你可能希望创建自定义操作。IResourceProviderAPI内部构建在这些自定义操作之上。
要实现自定义操作,可以通过从AsyncOperationBase类派生并重写所需的虚拟方法来创建自定义操作。可以将派生操作传递给ResourceManager.StartOperation方法,以启动操作并接收AsyncOperationHandle结构。以这种方式启动的操作将在ResourceManager中注册并显示在Addressables Event Viewer中。

  • Executing the operationResourceManager将为你的自定义操作调用AsyncOperationBase.Execute方法。
  • Completion handling当你的自定义操作完成后,调用AsyncOperationBase.Complete你的自定义操作对象。你可以在Execute 方法中调用它,也可以将其推迟到调用外部。调用AsyncOperationBase.Complete通知ResourceManager操作已经完成,并将调用关联的AsyncOperationHandle.Completed事件。
  • Terminating the operation当你发布引用AsyncOperationHandle的AsyncOperationHandle时,ResourceManager会为你的自定义操作调用AsyncOperationBase.Destroy 方法。这是你需要释放与自定义操作关联的任何内存或资源的地方。

九、Addressables分析器

Analyze是一个收集项目地址布局信息的工具。在某些情况下,分析器可能会采取适当的行为来清理项目的状态。在其他情况下,分析纯粹是一种信息工具,它允许你对Addressable布局做出更明智的决定。

9.1 使用Analyze

在编辑器中,打开Addressables Analyze窗口(Window>Asset Management>Addressables>Analyze),或者通过Addressables Groups 窗口单击Tools>Analyze 按钮打开它。

Analyze窗口显示分析规则列表,以及下列操作:

  • Analyze Selected Rules
  • Clear Selected Rules
  • Fix Selected Rules

9.1.1 Analyze Operation
Analyze Operation是规则的信息收集步骤。在规则或规则集上运行此操作将收集有关生成、依赖映射等方面的数据。每个规则负责收集所需的数据,并将其报告为AnalyzeResult对象的列表。

在分析步骤期间,不要采取任何操作来修改任何数据或项目状态。根据在此步骤中收集的数据,修复操作可能是适当的操作过程。然而,有些规则只包含一个分析步骤,因为不能根据收集到的信息采取合理、合适和正常的行动。 Check Scene to Addressable Duplicate Dependencies和Check Resources to Addressable Duplicate Dependencies是此类规则的示例(见下面)。

纯信息性且不包含修复操作的规则被归类为Unfixable Rules。那些有固定操作的被归类为Fixable Rules。

9.1.2 Clear step
此操作将删除分析收集的所有数据,并相应地更新TreeView。

9.1.3 Fix operation
对于Fixable Rules,你可以选择运行修复操作。这会使用在分析步骤中收集到的数据来执行任何必要的修改并解决问题。

Check Duplicate Bundle Dependencies是fixable Rule的一个示例,因为可以采取合理的、适当的操作来解决分析中检测到的问题。

9.2 已提供的分析规则

9.2.1 可修复Rules

9.2.1.1 检查重复的Bundle依赖项
此规则通过扫描所有使用BundledAssetGroupSchemas的组并投影asset组布局来检查可能冗余的asset。这实际上会触发一个完整的构建,所以这个检查是非常耗时和耗费性能的。

问题:冗余的asset是由于不同组中的Asset共享依赖关系。例如,两个Prefabs共享存在于不同Addressable Group中的一个Material 。这些Material(以及它的任何依赖)都会被分成两个组,每组都有Prefabs 。为了防止这种情况发生,必须将Material标记为Addressable,或者放在其中一个Prefabs中,或者放在它自己的空间中,从而将该Material及其依赖项归在一个单独的Addressable Group中。

解决方案:如果此检查发现任何问题,则根据此规则运行FIX操作将创建一个新的Addressable group,并且移动所有依赖的Assets。

例外情况:如果有包含多个对象的Assets,则不同的Group可能只提取Assets的一部分,而不是产生副本。有许多网格的FBX就是一个例子。如果一个网格位于“GroupA”中,另一个网格位于“GroupB”中,则此规则将认为FBX是共享的,并在运行修复操作时将其解压到单独的组中。在这种情况下,运行FIX操作实际上是有害的,因为两个组都没有完整的FBX Assets。

还要注意,冗余的Assets并不一定总是问题。如果Assets永远不会被同一Group用户请求(例如,特定于区域的Assets),那么就可能需要冗余的依赖关系,或者至少来说冗余是无关紧要的。每个项目都是唯一的、独特的。因此,修复冗余的Assets依赖关系应该根据具体情况进行评估。

9.2.2 不可修复Rules

9.2.2.1 Check Resources to Addressable Duplicate Dependencies
此规则检测在构建的Addressable数据和驻留在Resources文件夹中的Assets之间是否存在任何Assets或Assets依赖项冗余。

问题:这些冗余意味着数据将同时包含在应用程序构建和Addressable构建中。

解决方案:这条规则是不可修复的,因为不存在适当的操作。这是纯粹的信息,提醒你注意冗余。如果分析出了,你必须手动解决。一个可能的手动修复示例是将违规Assets移出Resources文件夹,并使其变为Addressable。

9.2.2.2 Check Scene to Addressable Duplicate Dependencies
此规则检测编辑器场景列表中的场景和Addressable中的场景之间共享的任何Assets或Assets依赖项。

问题:这些冗余意味着数据将同时包含在应用程序构建和Addressable构建中。

解决方案:这纯粹是信息,提醒您注意冗余。如果分析出了,你必须手动解决。一个可能的手动修复的例子是将具有重复引用的内置场景从BuildSettings中提取出来,并使其成为一个Addressable场景。

9.2.2.3 Check Sprite Atlas to Addressable Duplicate Dependencies
给定Addressable sprite atlas,此规则将检测atlas中是否有任何sprites在其他任何地方被标记为Addressable 。

问题:这些冗余意味着sprite数据将在Addressable构建的多个区域中重复。
解决方案:这纯粹是信息,提醒你注意冗余。如果分析出了,你必须手动解决。一个可能的手动修复的例子是从Addressable中删除重复的sprite,并让你的Assets引用可寻址的sprite atlas中的sprite,而不是直接引用sprite。

9.2.2.4 Build Bundle Layout
此规则将显示如何在Addressable构建中显式标记为Addressable的Assets。对于这些显式Assets,我们还将给出哪些Assets被构建隐式的引用了,并且最终会被拉进构建中。

根据这一规则收集的数据并不表明任何特定问题。这纯粹是信息。

9.3 Analyze扩展

每个独特的项目可能需要额外的分析规则,而不仅仅是预先打包好的规则。Addressable Assets System允许你创建自己的自定义规则类。

9.3.1 AnalyzeRule objects
创建一个AnalyzeRule类的子类,重写以下属性:

  • CanFix: 告诉分析规则是否被认为是可修正的。
  • ruleName 分析窗口上用来展示规则的名字。

你还需要重写以下方法,具体如下:

  • List RefreshAnalysis(AddressableAssetSettings settings)
  • void FixIssues(AddressableAssetSettings settings)
  • void ClearAnalysis()注意:如果您的规则被指定为不可修复,则不必重写FixIssues方法。

9.3.1.1 RefreshAnalysis
这是你的分析操作。在此方法中,执行任何您想要的计算,并缓存可能的修复所需的任何数据。返回值是List list。收集数据后,为分析中的每个条目创建一个新的AnalyzeResult,将数据作为第一个参数的字符串,将MessageType作为第二个参数(可选的,将消息类型指定为警告或错误)。返回所创建对象的列表。

如果需要为特定的AnalyzeResult对象在TreeView中创建子元素,则可以用kDelimiter来描述父项和任何子元素。包括父项和子项之间的分隔符。

9.3.1.2 FixIssues
这是你的修复操作。如果为了响应分析步骤需要采取适当的操作,请在这里执行它。

9.3.1.3 ClearAnalysis
这是您的清理操作。在分析步骤中缓存的任何数据都可以在此函数中清除或删除。TreeView会同时更新以展示缺乏必要数据。

9.3.2添加自定义规则到GUI上
为了在分析窗口中显示,自定义规则必须使用AnalyzeWindow.RegisterNewRule (),向GUI类注册。例如:


感谢作者放牛的星星供稿。欢迎转发分享,未经作者授权请勿转载。如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ群:793972859)

作者主页:https://www.zhihu.com/people/niuxingxing,作者也是U Sparkle活动参与者,UWA欢迎更多开发朋友加入U Sparkle开发者计划,这个舞台有你更精彩!