Skip to content

Latest commit

 

History

History
222 lines (135 loc) · 17.5 KB

3.Addressable Assets development cycle.md

File metadata and controls

222 lines (135 loc) · 17.5 KB

Addressable Assets 开发周期

Addressable Assets最大的优势在于,资源放置位置、资源构建、资源加载都各不相干。而在以前,这几方面都紧紧地耦合在一起。

旧的资源管理方式

如果资源放在Resources目录下,那么这些资源会被打包到APP中,代码中必须使用Resources.Load和资源路径来加载资源。如果是访问其他地方的资源,就需要直接访问或者使用asset bundle。如果使用asset bundle,那还是需要用资源路径来加载,所以加载和资源组织就紧紧地绑定在一起。如果asset bundle被放在远端,或者还有其他依赖的bundle,那还需要写更多的代码来管理下载、加载、卸载。

Addressable Assets管理方式

只要给资源指定一个索引标识(address),就可以通过这个标识来加载它,无论它在项目的哪个位置,又是如何构建它的。而且资源的位置和名称可以随意修改。还可以把资源从Resources目录或本地构建目录中移动到其他的构建位置(包括远程构建位置)中,这些操作都不需要修改任何一行加载代码。

Asset group schemas

每一个Schema中都定义了很多数据。你可以在Inspector窗口中把多个Schemas添加到asset groups。这些schema决定group里的资源如何被构建。例如,在packed mode下构建,具有BundledAssetGroupSchema的group将会被打包成asset bundle。还可以把schema绑定到模板中,那样就可以用于以后新增的资源组。在AddressableAssetsSettings的属性Asset Group Templates中可以新增schema模板。

Build Scripts

Build scripts是一种ScriptableObject资源,它实现了IDataBuilder接口。用户可以创建自己特定的脚本,然后再Inspector窗口中添加到AddressableAssetsSettings的属性Build and Play Mode Scripts中。在Addressables Groups窗口(Window > Asset Management > Addressables > Groups)中通过选择Play Mode Script,并从下拉框中选择一项来应用build script。当前有三个脚本可以完全支持资源构建,还有三个用于编辑器下开发迭代的播放脚本。

Play mode scripts

Addressable Assets有三个构建脚本,可以创建不同运行数据来加速APP开发。

Use Asset Database(快速)

Use Asset Database模式(BuildScriptFastMode)可以让你在完成游戏流程开发的同时也能快速运行游戏。它直接从asset database中加载资源,以便于游戏快速迭代,在这个过程中没有任何资源分析和asset bundle构建。

Simulate Groups(高级)

Simulate Groups模式(BuildScriptVirtualMode)只会分析资源结构和依赖关系,但是不会创建asset bundle。资源是通过ResourceManager从asset database中加载而来,整个过程和从bundle中加载一样。所以可以在Addressables Event Viewer 窗口中查看资源的使用情况,以及bundle在游戏运行时的加载和卸载情况。

你可以通过Simulate Groups模式的模拟asset bundle加载来帮助你调整asset group,让整个资源结构在非常平衡状态下发布APP。

Use Existing Build(需要构建资源组)

Use Existing Build模式是非常接近发布版本加载方式。但这需要先执行资源构建。在你没有修改资源的前提下,这将是最快进入游戏的模式,因为在运行时不需要处理任何数据。所以在这个模式下运行APP前,你需要在Addressables Groups窗口(Window > Asset Management > Addressables > Groups)中,选择菜单 Build > New Build > Default Build Script或者使用APIAddressableAssetSettings.BuildPlayerContent()来构建资源。

数据分析和调试

默认下Addressables Assets的日志只会记录警告和错误。你可以在Player settings窗口(Edit > Project Settings... > Player>)中,导航至Other Settings > Configuration,在属性Scripting Define Symbols中添加宏定义ADDRESSABLES_LOG_ALL来开启所有详细信息日志。

你也可以在AddressableAssetSettingsInspector窗口中取消Log Runtime Exceptions勾选来关闭异常检测。当然,如果你有需要的话,你也可以实现 ResourceManager.ExceptionHandler属性来处理指定异常。但是这必须要在Addressables完成运行时初始化之后立即执行。

Initialization objects

你可以在AddressableAssetSettingsInspector窗口中的Initialization objects属性里添加其他的初始化对象。这些将在Addressables的初始化时被执行。CacheInitializationSettings对象在运行时控制Unity的缓存API。你可以通过实现接口 IObjectInitializationDataProvider的ScriptableObject来创建自定义的初始化对象。这是系统级的编辑器component,它会和runtime data一起序列化后创建ObjectInitializationData

URL定制

有一些情况下,是需要为资源(一般是AssetBundle)定制一个URL。最常见的就是签名URL,另外就是动态主机路径。

下面的代码展示了组装一个查询URL:

//Implement a method to transform the internal ids of locations
string MyCustomTransform(IResourceLocation location)
{
	if (location.ResourceType == typeof(IAssetBundleResource) && location.InternalId.StartsWith("http"))
		return location.InternalId + "?customQueryTag=customQueryValue";
	return location.InternalId;
}

//Override the Addressables transform method with your custom method.  This can be set to null to revert to default behavior.
[RuntimeInitializeOnLoadMethod]
static void SetInternalIdTransform()
{
	Addressables.InternalIdTransformFunc = MyCustomTransform;
}

注意:如果Addressables里打包了视频文件,并且需要在Android平台加载时,必须创建一个CacheInitializationSettings 对象,并禁用Compress Bundles属性。然后在AddressableAssetSettingsInspector窗口中,添加到Initialization objects属性里。

资源更新工作流

Unity推荐把游戏资源分成两部分:

  • Cannot Change Post Release :静态资源,你永远都不期望更新。
  • Can Change Post Release:动态资源,你期望可以更新。

在这种结构下,静态资源随APP一起发布(或者安装后立即下载),它们分散在少数非常大的bundle中。动态资源放在网络上,通常都是很小的bundle,以减少每次更新数量。所以Addressable Assets System的目标之一就是,更容易使用和修改,并且不需要修改你的脚本。

当然,当需要修改静态部分资源的时候,而且还不想发布一个全新版本,Addressable Assets System同样可以满足这种需求。

需要注意的是,在不允许更新的情况下(比如当前很多游戏机或视频游戏根本没有服务器),每次都需要进行完整的构建。

工作原理

Addressables采用catalog把address和每一个资源映射起来,并指定了资源应该在何处以及如何被加载。为了使APP能够修改这些映射关系,你的发布APP必须要加载和识别一个在网络上的catalog副本。在AddressableAssetSettingsInspector窗口中开启Build Remote Catalog属性来开启这个功能。这样在构建时,会创建一个catalog的副本,并且可以通过指定的路径来加载它。这个路径在你的APP发布之后就不能再修改。而在之后把内容更新的过程中产生的新版本catalog(具有相同文件名)覆盖掉托管位置的旧文件即可。

在APP构建的时候会产生一个唯一的版本串,所以每一个版本的APP都知道它自己应该加载哪一份catalog。所以也可以在服务器上放置多个版本的catalog。APP在构建的时候会把必要数据都写入在addressables_content_state.bin文件中,它包含版本串,还有每一个被标记为静态资源的hash数据。默认下,这个文件在Assets/AddressableAssetsData/{platform}目录下,{platform}是你的目标发布平台。

addressables_content_state.bin文件包含了每一个静态资源组的hash和依赖数据。这些静态资源都会被打包到StreamingAssets目录下。因为这种设定,那些大型远程资源组也会受益。在后面的步骤(准备内容更新)中,这些hash数据可以分辨出任何静态资源是否有修改,那些被更新过的资源将会被移动到别的资源组中。

唯一Bundle ID

Unity不能把两个具有同样名称的AssetBundle加载到内存中,这其实一定程度上限制运行时的资源更新。但是Addressables支持在初始化之后还可以更新catalog,所以即使资源已经被加载,也还是可以被更新。

要达到这个目的,有两种情况。一是在更新catalog之前把旧的Addressables数据卸载;另一种是确保被更新AssetBundle具有唯一的内部标识。这样就可以加载一个全新的AssetBundle,虽然旧的还在内存中。Addressables可以在AddressableAssetSettingsInspector窗口中开启Unique Bundle IDs属性来启用第二种情况。而它的缺点是需要重新构建和它有依赖关系的所有资源。这意味着,如果你修改了一个资源组里的material,默认情况下你只需要重新打包这一个资源组,但是当Unique Bundle IDs启用后,所有依赖这个material的资源都需要重新构建。

准备更新资源

如果你修改了任何被标记为Cannot Change Post Release资源,你需要执行Check for Content Update Restrictions,然后那些被修改的资源会从原来的静态组中移除,然后把它们放入到一个新的资源组中,步骤如下:

  1. 在Unity编辑器中打开Addressables Groups窗口(Window > Asset Management > Addressables > Groups)。
  2. Addressables Groups窗口中,点击Tools,在下拉菜单中选择Check for Content Update Restrictions
  3. 在打开的Build Data File对话框中,选择之前构建产生的addressables_content_state.bin(默认这个文件在Assets/AddressableAssetsData/{platform}目录下)文件

bin文件里的数据会检测出当前哪些资源和依赖项已经被修改过。系统会自动把这些资源移动到一个新的资源组,以便资源更新构建。

注意:如果修改的资源都是非静态资源,那么以上步骤将什么都不处理。

重点:在执行更新准备之前,推荐首先在版本控制中分出一个新的分支,因为更新准备会修改和新建资源组。当你下次需要发布一个新的版本时,你仍然可以使用之前已经规划好的资源分组。

构建更新资源

构建内容更新请执行如下操作:

  1. 在Unity编辑器中打开Addressables Groups窗口(Window > Asset Management > Addressables > Groups)。
  2. Addressables Groups窗口中,点击Tools,在下拉菜单中选择Check for Content Update Restrictions
  3. 在打开的Build Data File对话框中,选择之前构建产生的addressables_content_state.bin(默认这个文件在Assets/AddressableAssetsData/{platform}目录下)文件

构建完毕后会生成一个catalog,一个hash文件,和一些AssetBundles。

生成的catalog文件和之前构建产生的catalog文件同名,最新的数据会覆盖写入到旧的catalog和hash文件。APP会加载hash文件来决定是否有新的catalog需要加载。所以系统只会加载同APP一起发布的未修改过的bundle文件,以及下载下来的最新bundle文件。

系统会使用addressables_content_state.bin里的版本串和位置信息来生成AssetBundles。不需要更新的AssetBundles将会用同样的名字写入到bin文件中。如果一个AssetBundles需要更新,就会生成一个新的AssetBundles,它会有一个新的文件名,这样它就可以和原始的AssetBundles共存。所以需要把新产生的AssetBundles复制到你托管的服务器地址下以供下载。

系统也会为那些不可以修改的资源构建AssetBundles,但是不需要把他们上传到托管服务器,因为没有Addressables Asset引用他们。

需要注意的是,不要在在发布新版本和构建更新资源中间修改build script,这将会有无法预测的事情发生。

运行时检测可用更新

你可以添加一个自定义脚本来定期检查是否有新的资源需要更新,方法如下:

public static AsyncOperationHandle<List<string>> CheckForCatalogUpdates(bool autoReleaseHandle = true)

在返回结果List<string>中包含了所有需要更新的 locator IDs,你可以从中筛选出指定的ID进行更新,或者直接全部传参给APIUpdateCatalogs来更新所有。

如果有更新内容,你可以提供一个按钮给用户点击开始更新,或者在后台自动静默更新。需要注意的是,这需要开发人员确保旧的资源已经被释放。

UpdateCatalogs的参数如果为空列表的话,将更新所有需要更新的内容。

public static AsyncOperationHandle<List<IResourceLocator>> UpdateCatalogs(IEnumerable<string> catalogs = null, bool autoReleaseHandle = true)

API返回一个被更新过的资源Locator列表

更新示例

在这个例子中,已发布的APP中有如下几个资源组:

Local_Static Remote_Static Remote_NonStatic
AssetA AssetL AssetX
AssetB AssetM AssetY
AssetC AssetN AssetZ

Local_StaticRemote_Static组有被标记为 Cannot Change Post Release

当前版本正在运行中,用户设备上有Local_Static资源,可能已经下载了一到两个远程包。

如果你修改每一个组的资源(比如AssetAAssetL,和AssetX),然后执行Check for Content Update Restrictions,然后资源组会变成如下:

Local_Static Remote_Static Remote_NonStatic content_update_group (non-static)
AssetX AssetA
AssetB AssetM AssetY AssetL
AssetC AssetN AssetZ

需要注意的是,预处理会修改静态资源组,这可能有些奇怪。然而系统确实是会把资源组调整为以上表格所展示的样子。并丢弃所有静态组的构建结果,因此我们从玩家的角度再来分析:

Local_Static:
Local_Static
AssetA
AssetB
AssetC

Local_Static资源已经安装在用户设备上了,并无法修改他们。只是旧版本AssetA资源不会再被使用,变成了无效资源。

Remote_Static:
Remote_Static
AssetL
AssetM
AssetN

Remote_Static没有变化,如果这些资源还没有缓存在玩家设备上,但是在AssetMAssetN被使用时,它会被下载到本地。只是其中旧版本的AssetL就不会再被使用了。

Remote_NonStatic:
Remote_NonStatic(old) Remote_NonStatic(new)
AssetX AssetX
AssetY AssetY
AssetZ AssetZ
旧版本是可以从服务器上删除掉,它再也不会被下载。如果玩家设备已经缓存了,它也不会被使用。 旧的Remote_NonStatic被新版本替换,通过hash文件来区分。修改后的AssetX被放在了新的bundle中
content_update_group:
content_update_group
AssetA
AssetL

content_update_group资源包包含了所有被修改的静态资源。它会在以后的运行中被使用到。

从上面的示例中我们得出一些结论:

  1. 在用户设备上,任何被修改过的本地资源将再也不会被使用。
  2. 如果用户设备上已经缓存了非静态资源包,用户需要重新下载资源包,并且这个资源包也包含了未修改的资源(比如示例中的AssetYAssetZ)。理想的情况下,用户没有缓存这个资源包,当需要时只需要下载最新的Remote_NonStatic资源包即可。
  3. 如果用户已经缓存了Remote_Static资源包,那只需要下载content_update_group资源包,当然这是理想状况。如果用户没有缓存Remote_Static资源包,那Remote_Static资源包和content_update_group资源包都会被下载到本地。无论初始状态如何,最后用户设备上都会拥有已失效的AssetL,即使永远没有被使用,但它永远都在。

所以远程资源包的最佳配置,取决于你如何去使用。