Unity3D之资源管理——Addressables管理详解
  8Z8YLacFlZ6x 2023年11月02日 28 0

Addressables是可寻址资源系统提供了一种简单的方法通过“地址”加载资源。简化资源包的创建和部署的管理开销。是在AssetBundle之后的另一种资源管理方式。

Addressables下载安装设置

1.用PackageManager进行安装Addressable System,我这里Unity3D是2019.3.11版本,Addressable最新已经到1.18.19版本。

Unity3D之资源管理——Addressables管理详解_Unity3D

2.window-->AssetManagement-->Addressables-->Groups

Unity3D之资源管理——Addressables管理详解_Addressables_02

3.点击Create Addressables Settings,创建资源组,

Unity3D之资源管理——Addressables管理详解_Unity3D_03

它会默认给你创建一个本地的Group,选中这个Group,可以在Inspector面板看到如下图示

Unity3D之资源管理——Addressables管理详解_Addressables_04

4.Setting面板设置

Unity3D之资源管理——Addressables管理详解_Addressables_05Unity3D之资源管理——Addressables管理详解_Unity3D_06

5.Profiles设置,修改打包和加载的路径,不同资源服务器的地址管理,点Create可创建

Unity3D之资源管理——Addressables管理详解_Addressables_07

Addressables使用

1.对于要打包资源在Inspector上勾选Addressable,拖入Addressables资源分组,例如

Unity3D之资源管理——Addressables管理详解_Unity3D_08

2.点击Build-->NewBuild-->Default Build Script资源打包,Build-->NewBuild-->Update a Previous Build 资源包更新。

Unity3D之资源管理——Addressables管理详解_Unity3D_09

注意:可寻址资产包有三个构建脚本,用于创建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)中构建此模式的内容。

打包出来的文件

Unity3D之资源管理——Addressables管理详解_Addressables_10

3.资源加载

Addressables.LoadAssetAsync<GameObject>("XXX")

知识点总结:

    AsyncOperationHandle<GameObject> handle;
  • 动态异步加载的使用方式

        handle = Addressables.LoadAssetAsync<GameObject>("Cube");
        //通过事件监听的方式 结束时使用资源
        handle.Completed += (obj) =>
        {
            if (handle.Status == AsyncOperationStatus.Succeeded)
            {
                Instantiate(obj.Result);
            }
        };
  • 协同程序加载资源的方式
//异步
IEnumerator LoadAsset()
    {
        handle = Addressables.LoadAssetAsync<GameObject>("xxx");
        //一定是没有加载成功 再去 yield return
        if(!handle.IsDone)
            yield return handle;
        //加载成功
        if (handle.Status == AsyncOperationStatus.Succeeded)
        {
            Instantiate(handle.Result);
        }
        else
            Addressables.Release(handle);
    }
  • 异步函数(async和await )
 async void Load()
    {
        handle = Addressables.LoadAssetAsync<GameObject>("xxx");
        AsyncOperationHandle<GameObject> handle2 = Addressables.LoadAssetAsync<GameObject>("xxx");
        //单任务等待
        //await handle.Task;
        //多任务等待
        await Task.WhenAll(handle.Task, handle2.Task);
        print("异步函数的形式加载的资源");
        Instantiate(handle.Result);
        Instantiate(handle2.Result);
    }

整体实现:


public class UpdateCatalogAndAssets : MonoBehaviour
{
	List<object> cusKeys = new List<object>();
	private void Start()
	{
		checkUpdate(() => { print("资源加载完事了,剩下的正常使用即可")});
        //加载资源的几个API
        //
	  AsyncOperationHandle res = Addressables.InstantiateAsync("xxx");
		AsyncOperationHandle res2 = Addressables.InstantiateAsync("xxx");
		Addressables.LoadAssetAsync<Sprite>("xxx");
    Addressables.Release(res);
	}
 
	void checkUpdate(System.Action pFinish)
	{
		StartCoroutine(Initialize(() =>{
			StartCoroutine(checkUpdateSize((oSize, oList) =>{
				if (oList.Count > 0){
					 StartCoroutine(DoUpdate(oList, () =>{
						pFinish();
					}));
				}
				else
				{
					pFinish();
				}
			}));
		}));
	}
	/// <summary>
	/// 初始化
	/// </summary>
	/// <param name="pOnFinish"></param>
	/// <returns></returns>
	IEnumerator Initialize(System.Action pOnFinish)
	{
		//初始化Addressable
		var init = Addressables.InitializeAsync();
		yield return init;
		//Caching.ClearCache();
		// Addressables.ClearResourceLocators();
		pOnFinish.Invoke();
	}
	/// <summary>
	/// 检查更新文件大小
	/// </summary>
	/// <param name="pOnFinish"></param>
	/// <returns></returns>
	IEnumerator checkUpdateSize(System.Action<long, List<string>> pOnFinish)
	{
		//Debug.LogError(" checkUpdateSize >>>");
		long sizeLong = 0;
		List<string> catalogs = new List<string>();
		AsyncOperationHandle<List<string>> checkHandle = Addressables.CheckForCatalogUpdates(false);
		yield return checkHandle;
		if (checkHandle.Status == AsyncOperationStatus.Succeeded)
		{
			catalogs = checkHandle.Result;
		}
		/*IEnumerable<IResourceLocator> locators = Addressables.ResourceLocators;
		List<object> keys = new List<object>();
		//暴力遍历所有的key
		foreach (var locator in locators)
		{
			foreach (var key in locator.Keys)
			{
				keys.Add(key);
			}
		}*/
		pOnFinish.Invoke(sizeLong, catalogs);
	}
	/// <summary>
	/// 下载更新逻辑
	/// </summary>
	/// <param name="catalogs"></param>
	/// <param name="pOnFinish"></param>
	/// <returns></returns>
	IEnumerator DoUpdate(List<string> catalogs, System.Action pOnFinish)
	{
		//Debug.LogError(" DocatalogUpdate >>>");
		var updateHandle = Addressables.UpdateCatalogs(catalogs, false);
		yield return updateHandle;
		foreach (var item in updateHandle.Result)
		{
			cusKeys.AddRange(item.Keys);
		}
		Addressables.Release(updateHandle);
		StartCoroutine(DownAssetImpl(pOnFinish));
	}
	public IEnumerator DownAssetImpl(Action pOnFinish)
	{
		var downloadsize = Addressables.GetDownloadSizeAsync(cusKeys);
		yield return downloadsize;
		Debug.Log("start download size :" + downloadsize.Result);
 
		if (downloadsize.Result > 0)
		{
			var download = Addressables.DownloadDependenciesAsync(cusKeys, Addressables.MergeMode.Union);
			yield return download;
 
			//await download.Task;
			Debug.Log("download result type " + download.Result.GetType());
			foreach (var item in download.Result as List<UnityEngine.ResourceManagement.ResourceProviders.IAssetBundleResource>)
			{
 
				var ab = item.GetAssetBundle();
				Debug.Log("ab name " + ab.name);
				foreach (var name in ab.GetAllAssetNames())
				{
					Debug.Log("asset name " + name);
				}
			}
			Addressables.Release(download);
		}
		Addressables.Release(downloadsize);
		pOnFinish?.Invoke();
	}
}
 


资源管理类

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.ResourceManagement.AsyncOperations;

//可寻址资源 信息
public class AddressablesInfo
{
    //记录 异步操作句柄
    public AsyncOperationHandle handle;
    //记录 引用计数
    public uint count;

    public AddressablesInfo(AsyncOperationHandle handle)
    {
        this.handle = handle;
        count += 1;
    }
}

public class AddressablesMgr
{
    private static AddressablesMgr instance = new AddressablesMgr();
    public static AddressablesMgr Instance => instance;
    //有一个容器 帮助我们存储 异步加载的返回值
    public Dictionary<string, AddressablesInfo> resDic = new Dictionary<string, AddressablesInfo>();
    private AddressablesMgr() { }
    //异步加载资源的方法
    public void LoadAssetAsync<T>(string name, Action<AsyncOperationHandle<T>> callBack)
    {
        //由于存在同名 不同类型资源的区分加载
        //所以我们通过名字和类型拼接作为 key
        string keyName = name + "_" + typeof(T).Name;
        AsyncOperationHandle<T> handle;
        //如果已经加载过该资源
        if (resDic.ContainsKey(keyName))
        {
            //获取异步加载返回的操作内容
            handle = resDic[keyName].handle.Convert<T>();
            //要使用资源了 那么引用计数+1
            resDic[keyName].count += 1;
            //判断 这个异步加载是否结束
            if(handle.IsDone)
            {
                //如果成功 就不需要异步了 直接相当于同步调用了 这个委托函数 传入对应的返回值
                callBack(handle);
            }
            //还没有加载完成
            else
            {
                //如果这个时候 还没有异步加载完成 那么我们只需要 告诉它 完成时做什么就行了
                handle.Completed += (obj) => {
                    if (obj.Status == AsyncOperationStatus.Succeeded)
                        callBack(obj);
                };
            }
            return;
        }
        
        //如果没有加载过该资源
        //直接进行异步加载 并且记录
        handle = Addressables.LoadAssetAsync<T>(name);
        handle.Completed += (obj)=> {
            if (obj.Status == AsyncOperationStatus.Succeeded)
                callBack(obj);
            else
            {
                Debug.LogWarning(keyName + "资源加载失败");
                if(resDic.ContainsKey(keyName))
                    resDic.Remove(keyName);
            }
        };
        AddressablesInfo info = new AddressablesInfo(handle);
        resDic.Add(keyName, info);
    }

    //释放资源的方法 
    public void Release<T>(string name)
    {
        //由于存在同名 不同类型资源的区分加载
        //所以我们通过名字和类型拼接作为 key
        string keyName = name + "_" + typeof(T).Name;
        if(resDic.ContainsKey(keyName))
        {
            //释放时 引用计数-1
            resDic[keyName].count -= 1;
            //如果引用计数为0  才真正的释放
            if(resDic[keyName].count == 0)
            {
                //取出对象 移除资源 并且从字典里面移除
                AsyncOperationHandle<T> handle = resDic[keyName].handle.Convert<T>();
                Addressables.Release(handle);
                resDic.Remove(keyName);
            }
        }
    }

    //异步加载多个资源 或者 加载指定资源
    public void LoadAssetAsync<T>(Addressables.MergeMode mode, Action<T> callBack, params string[] keys)
    {
        //1.构建一个keyName  之后用于存入到字典中
        List<string> list = new List<string>(keys);
        string keyName = "";
        foreach (string key in list)
            keyName += key + "_";
        keyName += typeof(T).Name;
        //2.判断是否存在已经加载过的内容 
        //存在做什么
        AsyncOperationHandle<IList<T>> handle;
        if (resDic.ContainsKey(keyName))
        {
            handle = resDic[keyName].handle.Convert<IList<T>>();
            //要使用资源了 那么引用计数+1
            resDic[keyName].count += 1;
            //异步加载是否结束
            if (handle.IsDone)
            {
                foreach (T item in handle.Result)
                    callBack(item);
            }
            else
            {
                handle.Completed += (obj) =>
                {
                    //加载成功才调用外部传入的委托函数
                    if(obj.Status == AsyncOperationStatus.Succeeded)
                    {
                        foreach (T item in handle.Result)
                            callBack(item);
                    }
                };
            }
            return;
        }
        //不存在做什么
        handle = Addressables.LoadAssetsAsync<T>(list, callBack, mode);
        handle.Completed += (obj) =>
        {
            if(obj.Status == AsyncOperationStatus.Failed)
            {
                Debug.LogError("资源加载失败" + keyName);
                if (resDic.ContainsKey(keyName))
                    resDic.Remove(keyName);
            }
        };
        AddressablesInfo info = new AddressablesInfo(handle);
        resDic.Add(keyName, info);
    }

    public void LoadAssetAsync<T>(Addressables.MergeMode mode, Action<AsyncOperationHandle<IList<T>>> callBack, params string[] keys)
    {
    }

    public void Release<T>(params string[] keys)
    {
        //1.构建一个keyName  之后用于存入到字典中
        List<string> list = new List<string>(keys);
        string keyName = "";
        foreach (string key in list)
            keyName += key + "_";
        keyName += typeof(T).Name;
        
        if(resDic.ContainsKey(keyName))
        {
            resDic[keyName].count -= 1;
            if(resDic[keyName].count == 0)
            {
                //取出字典里面的对象
                AsyncOperationHandle<IList<T>> handle = resDic[keyName].handle.Convert<IList<T>>();
                Addressables.Release(handle);
                resDic.Remove(keyName);
            }
        }
    }

    //清空资源
    public void Clear()
    {
        foreach (var item in resDic.Values)
        {
            Addressables.Release(item.handle);
        }
        resDic.Clear();
        AssetBundle.UnloadAllAssetBundles(true);
        Resources.UnloadUnusedAssets();
        GC.Collect();
    }
}

Addressables特点

  • 快速迭代:使用Addressable在开发前期就进入快速开发的阶段,使用任何你喜欢的资源管理技术,你都能快速的切换来Addressable系统中。几乎不需要修改代码。
  • 依赖管理:Addressable系统不仅仅会帮你管理、加载你指定的内容,同时它会自动管理并加载好该内容的全部依赖。在所有的依赖加载完成,你的内容彻底可用时,它才会告诉你加载完成。
  • 内存管理:Addressable不仅仅能记载资源,同时也能卸载资源。系统自动启用引用计数,并且有一个完善的Profiler帮助你指出潜在的内存问题。
  • 内容打包:Addressable系统自动管理了所有复杂的依赖连接,所以即使资源移动了或是重新命名了,系统依然能够高效地找到准确的依赖进行打包。当你需要将打包的资源从本地移到服务器上面,Addressable系统也能轻松做到,几乎不需要任何代价。

注意:

Addressables相比Assetbundle优点

1.Addressables只需要一个资源的地址就可以从任意地方进行加载,而AssetBundle需要从制定bundle中加载资源。

2.Addressables加载到内存中的bundle有引用计数,而AssetBundle加载到内存中的bundle需要自己进行管理。

3.Addressables会自己管理依赖关系,而AssetBundle需要自己管理依赖关系,维护起来比较困难。

4.Addressables默认的所有加载操作都是异步操作,可以添加事件监听,而AssetBundle则有同步和异步加载。





【版权声明】本文内容来自摩杜云社区用户原创、第三方投稿、转载,内容版权归原作者所有。本网站的目的在于传递更多信息,不拥有版权,亦不承担相应法律责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@moduyun.com

  1. 分享:
最后一次编辑于 2023年11月08日 0

暂无评论

8Z8YLacFlZ6x