首页 > 其他分享 >Unity3D之资源管理——Addressables管理详解

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

时间:2023-04-10 15:02:11浏览次数:35  
标签:Unity3D handle resDic keyName Addressables 资源管理 资源 加载

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管理详解_Addressables_03

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

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

4.Setting面板设置

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

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

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

Addressables使用

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

Unity3D之资源管理——Addressables管理详解_Addressables_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管理详解_Unity3D_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则有同步和异步加载。





标签:Unity3D,handle,resDic,keyName,Addressables,资源管理,资源,加载
From: https://blog.51cto.com/myselfdream/6180916

相关文章

  • DevEco Device Tool 3.1 Release新版本发布,新增资源管理器、SFTP、HDC
     DevEcoDeviceTool是面向智能设备开发者提供的一站式集成开发环境,支持代码编辑、编译、烧录和调试、性能监测等功能,支持C/C++语言,以插件的形式部署在VisualStudioCode(简称VSCode)上,支持Windows1064位或Ubuntu18.04-21.10版本。本次为大家带来的是DevEcoDeviceTool3.1......
  • 优维CMDB:OneModel协助IT资源管理快速落地
    CMDB作为优维旗舰产品,一直备受客户好评。为了给广大客户带来更精益的CMDB产品,上周四,针对CMDB召开了一场上新发布会,主要介绍了IT资源管理微应用的新特性,有很多有价值的内容想和大家分享。话不多说,跟着鹿小U一起来看一下发布会上都说了什么吧!1.OneModel是什么?OneModel可以解决哪些问......
  • jsp+servlet实现的人力资源管理系统(实现了注册登录、部门管理、招聘管理、培训管理、
    @目录jsp+servlet实现的人力资源管理系统实现功能截图系统功能使用技术完整源码jsp+servlet实现的人力资源管理系统本系统是一个servlet原生框架实现的人力资源管理系统,实现了注册登录、部门管理、招聘管理、用户管理、薪资管理、职位管理等常用功能。(文末查看完整源码)实现......
  • 2、kubernetes资源管理
    四、资源管理介绍k8s本质上是一个集群系统,用户可以在集群中部署各种服务,部署服务(其实就是在k8s集群中运行一个个容器,并将指定的程序跑在容器中)k8s的最小管理单元是pod不是容器,所以只能将容器放在pod中,而k8s一般不会直接管理pod,而是通过pod控制器来管理的pod的pod可以提供服务之......
  • unity3d面试题及答案
    unity3d面试题及答案1.请描述游戏动画有哪几种,以及其原理。答:主要有关节动画、单一网格模型动画(关键帧动画)、骨骼动画。    关节动画把角色分成若干独立部分,一个部分对应一个网格模型,部分的动画连接成一个整体的动画,角色比较灵活Quake2中使用了这种动画;   单一网......
  • 分布式技术原理与算法解析 02 - 分布式资源管理与负载调度
    分布式体系结构之集中式结构集中式结构就是由一台或多台机器组成中央服务器,所有数据存储于此,所有业务也先由其处理。多节点与中央服务器连接,并将自己信息汇报给中央服务器......
  • unity3d Update()和FixedUpdate()的区别
    从字面上理解,它们都是在更新时会被调用,并且会循环的调用。但是Update会在每次渲染新的一帧时,被调用。而FixedUpdate会在每个固定的时间间隔被调用,那......
  • 【Unity3D】空间和变换
    1空间1.1左右手坐标系及其法则1.1.1左右手坐标系左手坐标系与右手坐标系​Unity局部空间、世界空间、裁剪空间、屏幕空间都采用左手坐标系,只有观察空间采用右......
  • 【Unity3D】激光灯、碰撞特效
    1需求描述​本文将模拟激光灯(或碰撞)特效,详细需求如下:从鼠标位置发射屏幕射线,检测是否与物体发生碰撞当与物体发生碰撞时,在物体表面覆盖一层激光灯(或碰撞)特效​......
  • 【Unity3D】基于模板测试和顶点膨胀的描边方法
    1前言​选中物体描边特效中介绍了基于模板纹理模糊膨胀的描边方法,该方法实现了软描边,效果较好,但是为了得到模糊纹理,对屏幕像素进行了多次渲染,效率欠佳。本文将介绍......