Addressables学习笔记3: 实际操作实现资源热更新
本篇基本上是官方演示的东西,介绍一下如何快速使用Addressables实现资源热更。为了能够热更资源,你需要有一个资源服务器使你能下载资源。
一、资源准备
首先打开Unity编辑器菜单 -> Window -> Asset Management -> Addressable Assets。最好将它固定到Editor里面,便于随时操作。然后随便制作一个prefab备用。
将该prefab拖拽到Addressables窗口的"Default Local Group"组中,结果如下图:
为了便于载入,使用如图的右键菜单项将其名字进行简化,它变成了成 "MyRemotePrefab"。
左键点击以选中 "Default Local Group"这个条目,在其Inspector窗口中我们可以看到其各种Schema的属性:
要保证这个Group中一定有BundledAssetGroupSchema与ContentUpdateGroupSchema,其中的资源才能正常被热更。按如下的方式设置各种属性:
1.将ContentUpdateGroupSchema中的StaticContent设置为false;
2.将BundledAssetGroupSchema中的BuildPath选中为 "RemoteBuildPath";
3.将BundledAssetGroupSchema中的LoadPath选中为"RemoteLoadPath";
4.检查AssetBundleProviderType,确认其值是 AssetBundleProvider;
5.此时它的LoadPath应该还不是你实际上的资源服上的路径。选中Project面板中的 Assets/AddressableAssetsData/AddressableAssetSettings, 在"Profiles"这一节,编辑ProfileEntries中的RemoteLoadPath条目,改成你的资源服务器地址:
如果想要修改其他条目的路径,也可以一并修改。
二、准备测试场景
创建一个MonoBehaviour,名为LoadPrefab,代码如下:
using UnityEngine; using UnityEngine.AddressableAssets; public class LoadPrefab : MonoBehaviour { public string address; // Start is called before the first frame update void Start() { Load(); } void Load() { Addressables.LoadAsset<GameObject>(address).Completed += (op) => { if (op.Status == UnityEngine.ResourceManagement.AsyncOperationStatus.Succeeded) { Debug.Log(op.Result as GameObject); var go = Instantiate(op.Result as GameObject, Vector3.zero, Quaternion.identity, transform); go.transform.localPosition = Vector3.zero; } }; } }
在场景中创建一个空的GameObject,然后将该MonoBehaviour添加到它上面。由于我创建的是一个UI,因此我创建了一个Canvas而不是空的GO.
我们将该Component中的Address修改为 MyRemotePrefab ,使之能被载入。
三、添加日志输出
这部分只是为了查看日志,可以直接跳过。
1.在Assets文件夹中创建一个文本文件,改名为 csc.rsp。该文本文件中的内容为 "-define:ADDRESSABLES_LOG_ALL"(不包括引号)。其作用是让Addressables输出的日志更详细。
2.在Packages(注意它不在Assets这个文件夹中)中找到Addressables System,选中它其中任意一个cs文件,然后Reimport之。这是为了让Unity重新编译以使第1步创建的 预编译指令文件生效。
四、打包
在Addressables窗口中,点击Build菜单执行"Build Player Content"操作:
然后将RemoteBuildPath(如果没有修改过,它应该是 ProjectDir/ServerData/StandaloneWindows64)中生成的文件全部上传到你的资源服务器对应路径中。
最后,打一个游戏包出来。
运行之后,可以看到在稍微等待零点几秒后,加载出了对应的Prefab(一个UI界面):
哦豁,由于我忘了给右上角的文本添加锚点,导致它显示不全。
五、热更新
将原有的UI进行修改,给文本添加了锚点并将它们移到右上角,更换了背景图片的颜色。
然后,我们要使用Addressables窗口的Build/Build For Content Update来进行更新资源的打包。
结果它弹出了一个文件选择窗口,让我们选择 Build Data File。这个文件实际上是上次打包时生成的、用于记录那时候资源相关的各种状态数据的一个文件。它存放在 ProjectDir/Library/com.unity.addressables 这个文件夹之中。
我们选中addressables_content_state.bin 这个文件,点击 "打开(O)"按钮以确定。
之后Unity进行打包,完成后可以看到在 RemoteBuildPath文件夹下已经生成了新的文件:
我们将这些新文件上传到资源服务器,然后重新启动程序,可以看到锚点问题已经修正了,背景颜色也已被修改,说明我们已经用新的prefab替换掉了老的:
六、一些细节问题
1.一般来说,我们是想要将资源先放在StreamingAssets中,等到发现有问题了才进行热更。但是如果你的所有Group都是本地的,Addresssables将不会尝试到服务器上下载最新的数据,也就无法进行资源更新了。因此这里只能一开始就将资源弄成Remote的。
2.Unity更新该资源的过程,简单来说就是:
首先,实际上所有的Address都有一个对应的实际路径,通过加载时传入的Address,它经过一系列操作将它转换为一个实际地址,然后从该地址加载得到对应的对象。对于默认的情况,这个Address与实际路径对应的表是一个叫做catalog的JSON文件。
如果你的资源需要更新,则"Build For Content Update"之后,你将得到与原先同名但是已被修改过的catalog文件,然后你将该catalog文件(以及其他文件)上传到服务器上。
程序启动后,将首先加载catalog表。而实际上此时本地的catalog表已经是过时的了。程序如何知道它本地的catalog是过时的呢?
对于每一个Group,如果它上面有BundledAssetGroupSchema,则你可以看到其中有一个属性是"Content Catalog Load Order",默认是0。其注释说
///The order in which the content catalog from this group will be loaded. Usually remote catalogs are tried first and would have a lower value. If this value is less than 0, no catalog will be generated.
好像比较费解。经过实际测试,看起来是这么回事:由于每个Group都可以对应一套加载路径,那么catalog的加载路径究竟使用哪个Group的LoadPath呢?这个Order就指定了尝试的次序。它将从该值为0的Group之LoadPath开始尝试,如果加载不成功就去找Order为1的……(存疑,也可能是这样的逻辑:按照加载的次序,依次加载每个catalog,并且合并加载出的所有内容)在加载catalog的时候,它会先比较该catalog的hash值与Application.persistentData中保存的catalog.hash值,如果不一致则说明该catalog较本地缓存中的要新,则使用这个版本并且将hash和json文件都保存到本地缓存中;否则就直接使用本地缓存中的数据。关于这个尝试列表,它是存放在 StreamingAssets/com.unity.addressables/settings.json 这个文件中的。
由于catalog已被更新,因此实际加载的时候,程序就能知道本地的资源已经过期,将自动去指定的资源服务器上下载并得到对应的新内容。
3.这个Addressables系统扩展性是很高的,熟悉了之后可以玩出很多花样;比起自己从Unity原有的资源加载与管理方式基础上弄一整套资源管理系统,Unity官方的方案应该是更稳定、扩展性更高、适用性更广的。当然现在不管是文档还是功能都不太完善,要实际应用还可以稍微等等。据论坛官方人员说,将在数周内放出一个示例项目,希望到时候能看到完整的、有趣的应用。
接下来应该是详细地看一看它的实现代码,把细节弄清楚,然后看怎样去进行自己的扩展。