【游戏开发探究】Unity Addressables资源管理方式用起来太爽了,资源打包、加载、热更变得如此轻松(Addressable Asset System |
您所在的位置:网站首页 › unity新版cinemachine在哪 › 【游戏开发探究】Unity Addressables资源管理方式用起来太爽了,资源打包、加载、热更变得如此轻松(Addressable Asset System |
文章目录
一、前言二、为什么推荐使用Addressables三、Addressables基础操作教程1、下载Addressables插件2、创建Addressables Settings3、给Group添加资源4、创建新的Group5、设置Build Path与Load Path6、修改RemoteBuildPath和RemoteLoadPath7、打Addressable资源包8、打Android APK9、加载Addressable资源9.1、方式一:通过Addressable Name来加载资源9.2、方式二:通过AssetReference来加载资源
10、Addressable资源三个加载模式10.1、Use Asset Database (fastest)10.2、Simulate Groups (advanced)10.3、Use Exising Build(requires built groups)
11、加载远程Addressable资源11.1、启用Hosting Services11.2、加载远程包的柯南图片11.3、Addressables是如何知道去哪里加载资源的11.4、打个APK包瞧瞧
12、如何把Group里的资源打成多个bundle文件13、使用Labels对Group内的资源进行二级分组13.1、新建Label13.2、给资源设置Label13.3、修改Bundle Mode为Pack Together By Label13.3、一个资源标记多个Label
14、批量加载同一个Label的所有资源(AssetLabelReference)15、打资源热更包15.1、开启Build Remote Catalog15.2、打包Addressable资源包15.3、加载小兰的图片15.4、打成APK15.5、替换小兰的图片15.6、打热更包,Update a Previous Build15.7、上传热更包15.8、热更测试
16、提前检测更新并下载(预下载)17、Addressable资源释放18、打包工具集成Addressable打包流程
四、答疑补充(Q&A)1、Addressables加载场景进度监听
五、完毕
一、前言
嗨,大家好,我是新发。 之前就有看过Unity的Addressable Asset System,简称AA,但那时候这个功能刚出来,出于稳定性考虑,所以暂时没有去使用它。现在,它已经迭代到1.16.19 Release版本了(中国版是1.19.16),经过了时间考验,可以拿出来讲讲啦,网上其实已经有不少讲Addressables系统的文章,不过很多不是最新版的教程,今天我就来写一下最新版的Addressables系统的使用教程吧~ 二、为什么推荐使用Addressables我在之前的好几篇文章中都介绍过Unity加载资源的几种方式,我还画过一个图,详细可以看我之前写的这篇文章:《Unity游戏开发——新发教你做游戏(三):3种资源加载方式》 点击菜单Window / Package Manager,打开插件包管理界面, 注:AssetStudio资源逆向工具开源地址:https://github.com/Perfare/AssetStudio Addressables.CN版本会对AssetBundle做加密处理, 注:实际项目中,建议大家下载Addressables.CN版本。 安装成功后,可以看到多出了一个Window / Asset Management / Addressables菜单, 点击Groups菜单, 我这里先随便制作两个预设, 接着我们把预设文件直接拖到对应的Group中即可,如下, 注:我演示的是预设资源,你也可以是其他任意的资源,比如声音、图集、动画、材质等等) 选中资源文件,在Inspector窗口中勾选Addressable,它也会自动添加到默认的Group中,如下 上面的默认Group一般是作为包内资源,现在我们创建一个新的Group作为包外资源的组(通过远程加载资源)。 如下,在Addressables Groups窗口中,点击左上角的Create按钮,点击Group / Packed Assets菜单, 我们选中RemoteGroup,在Inspector窗口中,将Build Path改为RemoteBuildPath,将Load Path改为RemoteLoadPath,如下, 如果你想修改RemoteBuildPath和RemoteLoadPath,可以在Addressables Groups窗口中点击Manage Profiles菜单, 在Addressables Groups窗口中,点击Build / New Build / Default Build Script,就会开始打Addressable资源包了,等它打包完毕即可, 同样使用AssetStudio对它进行逆向,可以看到我们的柯南就在里面~ 现在,我们打个Android的APK包看看, 我们创建一个C#脚本,我这里就创建一个Main.cs脚本吧, 我们加载资源的时候,并不需要知道目标资源到底是在哪个Group中,也不需要知道这个Group到底是本地资源包还是远程资源包,统一通过资源的Addressable Name来加载,资源的Addressable Name在哪里看呢? 比如Cube预设,在Inspector窗口中,可以看到它的Addressable Name为Assets/Prefabs/Cube.prefab,这个Addressable Name默认是资源被加入Group时的相对路径, 然后使用Addressables.LoadAssetAsync方法加载资源,监听Completed 回调,在回调中拿到资源然后进行操作,示例: using UnityEngine; using UnityEngine.AddressableAssets; public class Main : MonoBehaviour { void Start() { Addressables.LoadAssetAsync("Assets/Prefabs/Cube.prefab").Completed += (handle) => { // 预设物体 GameObject prefabObj = handle.Result; // 实例化 GameObject cubeObj = Instantiate(prefabObj); }; } }Addressables还提供了InstantiateAsync接口,方便直接一步到位实例化,示例: Addressables.InstantiateAsync("Assets/Prefabs/Cube.prefab").Completed += (handle) => { // 已实例化的物体 GameObject cubeObj = handle.Result; };有些人可能不喜欢使用回调的方式,喜欢使用async、await的方式,示例: using UnityEngine; using UnityEngine.AddressableAssets; using System.Threading.Tasks; public class Main : MonoBehaviour { void Start() { InstantiateCube(); } private async void InstantiateCube() { // 虽然这里使用了Task,但并没有使用多线程 GameObject prefabObj = await Addressables.LoadAssetAsync("Assets/Prefabs/Cube.prefab").Task; // 实例化 GameObject cubeObj = Instantiate(prefabObj); // 也可直接使用InstantiateAsync方法 // GameObject cubeObj = await Addressables.InstantiateAsync("Assets/Prefabs/Cube.prefab").Task; } }我们把Main.cs脚本挂到Main Camera相机上, 我们知道,脚本中如果声明了一个public变量,默认会进行序列化,可以在Inspector窗口中对它进行设置,我们声明一个public的AssetReference 成员,如下, // Asset弱引用 public AssetReference spherePrefabRef;我们把Sphere预设拖给这个spherePrefabRef成员, 同样,AssetReference 也提供了InstantiateAsync方法,方便一步到位进行实例化,例, spherePrefabRef.InstantiateAsync().Completed += (obj) => { // 已实例化的物体 GameObject sphereObj = obj.Result; };我们运行Unity,测试效果如下,可以看到球体预设正常被加载并实例化了, Addressables资源加载模式有三个,如下,默认情况下是Use Asset Database (fastest), Use Asset Database (fastest) 直接加载文件而不打包,快速但Profiler获取的信息较少 Simulate Groups (advanced) 在不打包的情况下模拟AssetBundle的操作 Use Exising Build(requires built groups) 实际上是从AssetBundle打包和加载 10.1、Use Asset Database (fastest)在这个模式下,Addressables系统会直接从AssetDatabase加载资源,我们 不需要 Build打Addressable资源包,这个加载速度最快,建议在项目开发阶段使用这个模式,加载速度快。 10.2、Simulate Groups (advanced)这个模式下,也是 不需要 Build打Addressable资源包的,那它与Use Asset Database (fastest)有什么区别呢? 让我先来操作一波,首先,我们选中AddressableAssetSettings,然后勾选Send Profiler Events,勾选之后,我们可以在Addressable的分析面板中查看到一些调试信息, 这个模式下,需要先执行Build打出Addressable资源包,它会根据Load Path去加载真正的AssetBundle文件并读取资源。如果不先Build,运行时会报错, Player content must be built before entering play mode with packed data. This can be done from the Addressables window in the Build->Build Player Content menu command. UnityEngine.GUIUtility:ProcessEvent (int,intptr,bool&)如下 如下: 我们上面的Remote Group是打成包外资源的,我们想要在Editor环境下测试远程加载,这个时候就需要先搭建一个Web服务器了,支持通过http请求来获取资源。Addressable系统已经帮我们做了一个Hosting Services工具,方便我们快速启动一个Web服务器。 11.1、启用Hosting Services点击菜单Window / Asset Management / Addressables / Hosting, 因为我们上面已经指向过Build打了资源包,所以这里我们就直接写加载资源的代码吧。 我们改一下Main.cs脚本,让它去加载柯南的图片,我们知道,柯南图片在Remote Group组里,它是在包外的,但我们代码上并不用管它到底是包内还是包外,我们使用Addressable Name来加载,柯南图片的Addressable Name为Assets/Textures/kenan.jpg, 使用UGUI创建一张RawImage,并赋值给Main脚本的img成员,如下, 注:如果你是win11系统,可能使用Hosting Services会无法访问,具体原因不明,win11各种恶心的问题,放弃治疗,把资源放到https服务器可以正常加载。 11.3、Addressables是如何知道去哪里加载资源的假设我现在把资源托管到GitCode上,我把RemoteLoadPath改为GitCode的地址,如下 我们发布成APK,在Android模拟器中去看看效果, 我们如果继续给RemoteGroup添加资源,比如我们再放一张小兰的图片, 我们重新执行Build,可以看到目录根据资源类型进行细化了,.bundle文件也细分成了两个,柯南和小兰分开了, 实际项目中,不会真的细到这么细的颗粒,会使用Labels来对Group内的资源进行二级分组,默认只有一个default的Label, 我们可以点击Tools / Labels菜单,新建一个Label, 接着我们把柯南和小兰的Label都设置为texture, 接着,我们把RemoteGroup的Bundle Mode改为Pack Together By Label, 事实上,一个资源可以同时标记多个Label,比如我把柯南同时标记为default和texture,如下, 我们给资源标记了Label之后,就可以批量加载指定了同一个Label的所有资源了,比如我想加载标记了texture的所有资源,就可以这样加载, using System.Linq.Expressions; using UnityEngine; using UnityEngine.AddressableAssets; public class Main : MonoBehaviour { public AssetLabelReference textureLabel; void Start() { Addressables.LoadAssetsAsync(textureLabel, (texture) => { // 没加载完一个资源,就回调一次 Debug.Log("加载了一个资源: " + texture.name); }); } }把Main.cs脚本的Texture Label成员设置为texture,如下, 想要支持热更新,需要先开启Catalog,选中AddressableAssetSettings,然后勾选Build Remote Catalog,如下, 然后执行Build打包Addressable资源包, 此时除了生成.bundle包,还生成了catalog的.hash和.json文件,如下 我们先写段代码去加载小兰的图片, using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.UI; public class Main : MonoBehaviour { public RawImage img; void Start() { Addressables.LoadAssetAsync("Assets/Textures/xiaolan.png").Completed += (obj) => { // 图片 Texture2D tex2D = obj.Result; img.texture = tex2D; img.GetComponent().sizeDelta = new Vector2(tex2D.width, tex2D.height); }; } }运行Unity,可以正常加载, 我们打成APK包,在Android模拟器上运行,可以正常加载到小兰的图片, 现在,我们想热更小兰的图片,换成这张, 我们点击Update a Previous Build菜单,如下 我们把热更包丢到服务器上, 现在,我们在Android模拟器上重新运行APP,可以看到,小兰的图片自动热更成新的图片了, 有时候我们希望启动后执行更新检测并下载所有资源后再进入游戏,可以使用CheckForCatalogUpdates -> UpdateCatalogs -> GetDownloadSizeAsync -> DownloadDependenciesAsync这个工作流。 这个过程中可能有强退、断网等异常,我们可以判断AsyncOperationHandle的Status状态码然后进行处理,并提示重试。 示例: using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.ResourceManagement.AsyncOperations; using UnityEngine.AddressableAssets.ResourceLocators; using UnityEngine.UI; // 检测更新并下载资源 public class CheckUpdateAndDownload : MonoBehaviour { /// /// 显示下载状态和进度 /// public Text updateText; /// /// 重试按钮 /// public Button retryBtn; void Start() { retryBtn.gameObject.SetActive(false); retryBtn.onClick.AddListener(() => { StartCoroutine(DoUpdateAddressadble()); }); // 默认自动执行一次更新检测 StartCoroutine(DoUpdateAddressadble()); } IEnumerator DoUpdateAddressadble() { AsyncOperationHandle initHandle = Addressables.InitializeAsync(); yield return initHandle; // 检测更新 var checkHandle = Addressables.CheckForCatalogUpdates(true); yield return checkHandle; if (checkHandle.Status != AsyncOperationStatus.Succeeded) { OnError("CheckForCatalogUpdates Error\n" + checkHandle.OperationException.ToString()); yield break; } if (checkHandle.Result.Count > 0) { var updateHandle = Addressables.UpdateCatalogs(checkHandle.Result, true); yield return updateHandle; if (updateHandle.Status != AsyncOperationStatus.Succeeded) { OnError("UpdateCatalogs Error\n" + updateHandle.OperationException.ToString()); yield break; } // 更新列表迭代器 List locators = updateHandle.Result; foreach (var locator in locators) { List keys = new List(); keys.AddRange(locator.Keys); // 获取待下载的文件总大小 var sizeHandle = Addressables.GetDownloadSizeAsync(keys.GetEnumerator()); yield return sizeHandle; if (sizeHandle.Status != AsyncOperationStatus.Succeeded) { OnError("GetDownloadSizeAsync Error\n" + sizeHandle.OperationException.ToString()); yield break; } long totalDownloadSize = sizeHandle.Result; updateText.text = updateText.text + "\ndownload size : " + totalDownloadSize; Debug.Log("download size : " + totalDownloadSize); if (totalDownloadSize > 0) { // 下载 var downloadHandle = Addressables.DownloadDependenciesAsync(keys, true); while (!downloadHandle.IsDone) { if (downloadHandle.Status == AsyncOperationStatus.Failed) { OnError("DownloadDependenciesAsync Error\n" + downloadHandle.OperationException.ToString()); yield break; } // 下载进度 float percentage = downloadHandle.PercentComplete; Debug.Log($"已下载: {percentage}"); updateText.text = updateText.text + $"\n已下载: {percentage}"; yield return null; } if (downloadHandle.Status == AsyncOperationStatus.Succeeded) { Debug.Log("下载完毕!"); updateText.text = updateText.text + "\n下载完毕"; } } } } else { updateText.text = updateText.text + "\n没有检测到更新"; } // 进入游戏 EnterGame(); } // 异常提示 private void OnError(string msg) { updateText.text = updateText.text + $"\n{msg}\n请重试! "; // 显示重试按钮 retryBtn.gameObject.SetActive(true); } // 进入游戏 void EnterGame() { // TODO } } 17、Addressable资源释放最后补充一下Addressable的资源释放。 就以加载小兰图片为例,我们加载完毕后,把RawImage销毁,并不会释放内存中的Texture2D对象,比如这样子 using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.UI; public class Main : MonoBehaviour { public RawImage img; public Button freeResBtn; void Start() { Addressables.LoadAssetAsync("Assets/Textures/xiaolan.png").Completed += (obj) => { // 图片 var tex2D = obj.Result; img.texture = tex2D; img.GetComponent().sizeDelta = new Vector2(tex2D.width, tex2D.height); }; freeResBtn.onClick.AddListener(() => { // 销毁RawImage if(null != img) Destroy(img.gameObject); }); } }我们通过Event Viewer可以看到,虽然我们销毁了RawImage,但是Texture2D还在内存中, 再次运行,可以看到,小兰的Texture2D在内存中释放了, 同理,释放预设资源也一样,如下 using UnityEngine; using UnityEngine.AddressableAssets; using UnityEngine.UI; public class Main : MonoBehaviour { public Button freeResBtn; private GameObject cubeObj; void Start() { var handle = Addressables.LoadAssetAsync("Assets/Prefabs/Cube.prefab"); handle.Completed += (obj) => { // 实例化Cube cubeObj = Instantiate(obj.Result); }; freeResBtn.onClick.AddListener(() => { if (null != cubeObj) Destroy(cubeObj); // 释放资源 Addressables.Release(handle); }); } } 18、打包工具集成Addressable打包流程实际项目中我们一般都会自己写一套打包工具,我们可以在打包工具中集成Addressable的打包流程,官方给出了示例,如下 详细参见官方文档:https://docs.unity.cn/cn/current/Manual/com.unity.addressables.html 评论区有一些同学的提问,我补充到这里统一进行答疑。 1、Addressables加载场景进度监听
好了,就写到这里吧。 我是新发,https://blog.csdn.net/linxinfa 一个在小公司默默奋斗的Unity开发者,希望可以帮助更多想学Unity的人,共勉~ 转载自CSDN-专业IT技术社区 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/linxinfa/article/details/122390621 |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |