Unity资源加载模块全解析

资源加载模块

资源加载模块的主要作用是

  1. 方便统一管理各种资源加载卸载方式(Resources、AB 包、UnityWebRequest、Editor)

  2. 方便异步加载资源(Resources、AB 包、UnityWebRequest)

资源加载模块的基本原理

将各资源加载模式模块化,比如 Resources 资源管理器、AB 包资源管理器等等,让他们分别管理不同加载方式加载的资源,最终再将他们整合在一起,方便我们在加载资源时根据自己的需求进行选择性使用

Resources资源加载模块
cs 复制代码
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// Resources资源加载模块管理器
/// </summary>
public class ResourcesMgr : BaseManager<ResourcesMgr>
{
    private ResourcesMgr() { }


    /// <summary>
    /// 同步加载资源方法
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="path">资源路径</param>
    /// <returns></returns>
    public T Load<T>(string path) where T : UnityEngine.Object
    { 
        return Resources.Load<T>(path);
    }
    /// <summary>
    /// 异步加载资源方法
    /// </summary>
    /// <typeparam name="T">资源类型</typeparam>
    /// <param name="path">资源路径</param>
    /// <param name="callBack">加载结束后的回调函数</param>
    public void LoadAsync<T>(string path,UnityAction<T> callBack) where T: UnityEngine.Object
    {
        //通过协同程序异步加载资源
        MonoMgr.Instance.StartCoroutine(ReallyLoadAsync<T>(path,callBack));
    }
    private IEnumerator ReallyLoadAsync<T>(string path, UnityAction<T> callBack) where T : UnityEngine.Object
    {
        //异步加载资源
        ResourceRequest resourceRequest = Resources.LoadAsync<T>(path);
        //等待资源加载结束后才会执行后面的代码
        yield return resourceRequest;
        //资源加载结束
        callBack?.Invoke(resourceRequest.asset as T);
    }
    /// <summary>
    /// 异步加载资源方法
    /// </summary>
    /// <param name="path">资源路径</param>
    /// <param name="type">资源类型</param>
    /// <param name="callBack">加载结束后的回调函数</param>
    public void LoadAsync(string path,Type type, UnityAction<object> callBack)
    {
        //通过协同程序异步加载资源
        MonoMgr.Instance.StartCoroutine(ReallyLoadAsync(path,type,callBack));
    }
    private IEnumerator ReallyLoadAsync(string path, Type type, UnityAction<object> callBack)
    {
        //异步加载资源
        ResourceRequest resourceRequest = Resources.LoadAsync(path,type);
        //等待资源加载结束后才会执行后面的代码
        yield return resourceRequest;
        //资源加载结束
        callBack?.Invoke(resourceRequest.asset);
    }


    /// <summary>
    /// 指定卸载资源
    /// </summary>
    /// <param name="assetToUnload"></param>
    public void UnloadAsset(UnityEngine.Object assetToUnload)
    {
        Resources.UnloadAsset(assetToUnload);
    }

    /// <summary>
    /// 异步卸载对应没有使用的Resources相关的资源
    /// </summary>
    /// <param name="callBack">回调函数</param>
    public void UnloadUnusedAssets(UnityAction callBack)
    {
        MonoMgr.Instance.StartCoroutine(ReallyUnloadUnusedAssets(callBack));    
    }
    private IEnumerator ReallyUnloadUnusedAssets(UnityAction callBack)
    {
        AsyncOperation asyncOperation = Resources.UnloadUnusedAssets();
        yield return asyncOperation;
        //卸载完毕后通知外部
        callBack?.Invoke();
    }
}
Resources资源加载模块优化
cs 复制代码
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public abstract class ResInfoBase { }
/// <summary>
/// 资源信息对象 主要用于存储资源信息 异步加载委托信息 异步加载协程信息
/// </summary>
/// <typeparam name="T"></typeparam>
public class ResInfo<T> : ResInfoBase
{
    //资源
    public T asset;
    //委托 主要用于异步加载结束后 传递资源到外部的委托
    public UnityAction<T> callback;
    //用于存储异步加载时 开启的协同程序
    public Coroutine coroutine;
    //判断是否需要移除
    public bool isDel = false;
}
/// <summary>
/// Resources资源加载模块管理器
/// </summary>
public class ResourcesMgr : BaseManager<ResourcesMgr>
{
    //用于存储加载过的资源或者加载中的资源容器
    private Dictionary<string, ResInfoBase> resDic = new Dictionary<string, ResInfoBase>();
    private ResourcesMgr() { }


    /// <summary>
    /// 同步加载资源方法
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="path">资源路径</param>
    /// <returns></returns>
    public T Load<T>(string path) where T : UnityEngine.Object
    {
        ResInfo<T> info;
        string resName = path + "_" + typeof(T).Name;
        if (!resDic.ContainsKey(resName))
        {
            //直接同步加载 并且记录资源信息到字典中 方便下次直接取出来用
            T res = Resources.Load<T>(path);
            info = new ResInfo<T>();
            info.asset = res;
            resDic.Add(resName, info);
            return res;
        }
        else
        { 
            //取出字典中的记录
            info = resDic[resName] as ResInfo<T>;
            //异步加载资源没有加载完成
            if (info.asset == null)
            {  //停止异步加载 直接采用同步的方式加载成功 记录并使用
                MonoMgr.Instance.StopCoroutine(info.coroutine);
                //直接采用同步的方式加载成功
                T res = Resources.Load<T>(path);
                //记录
                info.asset = res;
                //将异步加载结束的委托去执行
                info.callback?.Invoke(res);
                //加载完毕后清空
                info.callback = null;
                info.coroutine = null;
                //返回加载成功的对象
                return res;
            }
            else
            { 
                return info.asset;
            }
        }

        
    }
    /// <summary>
    /// 异步加载资源方法
    /// </summary>
    /// <typeparam name="T">资源类型</typeparam>
    /// <param name="path">资源路径</param>
    /// <param name="callBack">加载结束后的回调函数</param>
    public void LoadAsync<T>(string path,UnityAction<T> callBack) where T: UnityEngine.Object
    {
        ResInfo<T> info;
        //资源的唯一ID
        string resName = path + "_" + typeof(T).Name;
        if (!resDic.ContainsKey(resName))
        {
            //声明一个 资源信息对象
            info = new ResInfo<T>();
            //将资源记录添加到字典中
            resDic.Add(resName, info);
            //记录传入的委托函数 一会加载完成后使用
            info.callback += callBack;
            //通过协同程序异步加载资源
            info.coroutine = MonoMgr.Instance.StartCoroutine(ReallyLoadAsync<T>(path));
        }
        else
        { 
            //从字典中取出资源信息
            info = resDic[resName] as ResInfo<T>;
            //资源还没加载完
            if (info.asset == null)
                info.callback += callBack;
            else
                callBack?.Invoke(info.asset);
        }

    }
    private IEnumerator ReallyLoadAsync<T>(string path) where T : UnityEngine.Object
    {
        //异步加载资源
        ResourceRequest resourceRequest = Resources.LoadAsync<T>(path);
        //等待资源加载结束后才会执行后面的代码
        yield return resourceRequest;
        //资源加载结束
        string resName = path + "_" + typeof(T).Name;
        if (resDic.ContainsKey(resName))
        {
            ResInfo<T> info = (resDic[resName] as ResInfo<T>);
            //取出资源信息 并记录加载完成的资源
            info.asset = resourceRequest.asset as T;
            if (info.isDel)
                UnloadAsset<T>(path);
            else 
            {
                //将加载完成的资源传递出去
                info.callback?.Invoke(info.asset);
                //加载完毕后清空
                info.callback = null;
                info.coroutine = null;
            }

        }
    }
    /// <summary>
    /// 异步加载资源方法
    /// </summary>
    /// <param name="path">资源路径</param>
    /// <param name="type">资源类型</param>
    /// <param name="callBack">加载结束后的回调函数</param>
    [Obsolete("注意:建议使用泛型方式加载,如果必须得用Type的方式,请不要用泛型方法和type方法加载同类型同名资源")]
    public void LoadAsync(string path,Type type, UnityAction<UnityEngine.Object> callBack)
    {
        ResInfo<UnityEngine.Object> info;
        //资源的唯一ID
        string resName = path + "_" + type.Name;
        if (!resDic.ContainsKey(resName))
        {
            //声明一个 资源信息对象
            info = new ResInfo<UnityEngine.Object>();
            //将资源记录添加到字典中
            resDic.Add(resName, info);
            //记录传入的委托函数 一会加载完成后使用
            info.callback += callBack;
            //通过协同程序异步加载资源
            info.coroutine = MonoMgr.Instance.StartCoroutine(ReallyLoadAsync(path,type));
        }
        else
        {
            //从字典中取出资源信息
            info = resDic[resName] as ResInfo<UnityEngine.Object>;
            //资源还没加载完
            if (info.asset == null)
                info.callback += callBack;
            else
                callBack?.Invoke(info.asset);
        }

    }
    private IEnumerator ReallyLoadAsync(string path, Type type)
    {
        //异步加载资源
        ResourceRequest resourceRequest = Resources.LoadAsync(path,type);
        //等待资源加载结束后才会执行后面的代码
        yield return resourceRequest;
        //资源加载结束
        string resName = path + "_" + type.Name;
        if (resDic.ContainsKey(resName))
        {
            ResInfo<UnityEngine.Object> info = (resDic[resName] as ResInfo<UnityEngine.Object>);
            //取出资源信息 并记录加载完成的资源
            info.asset = resourceRequest.asset;
            if (info.isDel)
                UnloadAsset(path,type);
            else
            {
                //将加载完成的资源传递出去
                info.callback?.Invoke(info.asset);
                //加载完毕后清空
                info.callback = null;
                info.coroutine = null;
            }
        }
    }


    /// <summary>
    /// 指定卸载资源
    /// </summary>
    /// <param name="assetToUnload"></param>
    public void UnloadAsset<T>(string path)
    {
        //资源的唯一ID
        string resName = path + "_" + typeof(T).Name;
        //判断是否存在对应资源
        if (resDic.ContainsKey(resName))
        {
            ResInfo<T> resInfo = resDic[resName] as ResInfo<T>;
            //资源已经加载结束
            if (resInfo.asset != null)
            {
                resDic.Remove(resName);
                Resources.UnloadAsset(resInfo.asset as UnityEngine.Object);
            }
            else//资源在加载中
            { 
                resInfo.isDel = true;
            }
        }
    }
    public void UnloadAsset(string path,Type type)
    {
        //资源的唯一ID
        string resName = path + "_" + type.Name;
        //判断是否存在对应资源
        if (resDic.ContainsKey(resName))
        {
            ResInfo<UnityEngine.Object> resInfo = resDic[resName] as ResInfo<UnityEngine.Object>;
            //资源已经加载结束
            if (resInfo.asset != null)
            {
                resDic.Remove(resName);
                Resources.UnloadAsset(resInfo.asset as UnityEngine.Object);
            }
            else//资源在加载中
            {
                resInfo.isDel = true;
            }
        }
    }

    /// <summary>
    /// 异步卸载对应没有使用的Resources相关的资源
    /// </summary>
    /// <param name="callBack">回调函数</param>
    public void UnloadUnusedAssets(UnityAction callBack)
    {
        MonoMgr.Instance.StartCoroutine(ReallyUnloadUnusedAssets(callBack));    
    }
    private IEnumerator ReallyUnloadUnusedAssets(UnityAction callBack)
    {
        AsyncOperation asyncOperation = Resources.UnloadUnusedAssets();
        yield return asyncOperation;
        //卸载完毕后通知外部
        callBack?.Invoke();
    }
}
Resources资源加载模块加入引用计数
cs 复制代码
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public abstract class ResInfoBase 
{
    //引用计数
    public int refCount;
}
/// <summary>
/// 资源信息对象 主要用于存储资源信息 异步加载委托信息 异步加载协程信息
/// </summary>
/// <typeparam name="T"></typeparam>
public class ResInfo<T> : ResInfoBase
{
    //资源
    public T asset;
    //委托 主要用于异步加载结束后 传递资源到外部的委托
    public UnityAction<T> callback;
    //用于存储异步加载时 开启的协同程序
    public Coroutine coroutine;
    //判断是否需要移除
    public bool isDel = false;


    public void AddRefCount() { ++refCount; }
    public void SubRefCount() { 
        --refCount;
        if (refCount < 0)
            Debug.LogError("引用计数小于0了,请检查使用和卸载是否配对");
    }
}
/// <summary>
/// Resources资源加载模块管理器
/// </summary>
public class ResourcesMgr : BaseManager<ResourcesMgr>
{
    //用于存储加载过的资源或者加载中的资源容器
    private Dictionary<string, ResInfoBase> resDic = new Dictionary<string, ResInfoBase>();
    private ResourcesMgr() { }


    /// <summary>
    /// 同步加载资源方法
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="path">资源路径</param>
    /// <returns></returns>
    public T Load<T>(string path) where T : UnityEngine.Object
    {
        ResInfo<T> info;
        string resName = path + "_" + typeof(T).Name;
        if (!resDic.ContainsKey(resName))
        {
            //直接同步加载 并且记录资源信息到字典中 方便下次直接取出来用
            T res = Resources.Load<T>(path);
            info = new ResInfo<T>();
            info.asset = res;
            info.AddRefCount();
            resDic.Add(resName, info);
            return res;
        }
        else
        { 
            //取出字典中的记录
            info = resDic[resName] as ResInfo<T>;
            info.AddRefCount();
            //异步加载资源没有加载完成
            if (info.asset == null)
            {  //停止异步加载 直接采用同步的方式加载成功 记录并使用
                MonoMgr.Instance.StopCoroutine(info.coroutine);
                //直接采用同步的方式加载成功
                T res = Resources.Load<T>(path);
                //记录
                info.asset = res;
                //将异步加载结束的委托去执行
                info.callback?.Invoke(res);
                //加载完毕后清空
                info.callback = null;
                info.coroutine = null;
                //返回加载成功的对象
                return res;
            }
            else
            { 
                return info.asset;
            }
        }

        
    }
    /// <summary>
    /// 异步加载资源方法
    /// </summary>
    /// <typeparam name="T">资源类型</typeparam>
    /// <param name="path">资源路径</param>
    /// <param name="callBack">加载结束后的回调函数</param>
    public void LoadAsync<T>(string path,UnityAction<T> callBack) where T: UnityEngine.Object
    {
        ResInfo<T> info;
        //资源的唯一ID
        string resName = path + "_" + typeof(T).Name;
        if (!resDic.ContainsKey(resName))
        {
            //声明一个 资源信息对象
            info = new ResInfo<T>();
            info.AddRefCount();
            //将资源记录添加到字典中
            resDic.Add(resName, info);
            //记录传入的委托函数 一会加载完成后使用
            info.callback += callBack;
            //通过协同程序异步加载资源
            info.coroutine = MonoMgr.Instance.StartCoroutine(ReallyLoadAsync<T>(path));
        }
        else
        { 
            //从字典中取出资源信息
            info = resDic[resName] as ResInfo<T>;
            info.AddRefCount();
            //资源还没加载完
            if (info.asset == null)
                info.callback += callBack;
            else
                callBack?.Invoke(info.asset);
        }

    }
    private IEnumerator ReallyLoadAsync<T>(string path) where T : UnityEngine.Object
    {
        //异步加载资源
        ResourceRequest resourceRequest = Resources.LoadAsync<T>(path);
        //等待资源加载结束后才会执行后面的代码
        yield return resourceRequest;
        //资源加载结束
        string resName = path + "_" + typeof(T).Name;
        if (resDic.ContainsKey(resName))
        {
            ResInfo<T> info = (resDic[resName] as ResInfo<T>);
            //取出资源信息 并记录加载完成的资源
            info.asset = resourceRequest.asset as T;
            if (info.refCount == 0)
                UnloadAsset<T>(path,info.isDel,false);
            else 
            {
                //将加载完成的资源传递出去
                info.callback?.Invoke(info.asset);
                //加载完毕后清空
                info.callback = null;
                info.coroutine = null;
            }

        }
    }
    /// <summary>
    /// 异步加载资源方法
    /// </summary>
    /// <param name="path">资源路径</param>
    /// <param name="type">资源类型</param>
    /// <param name="callBack">加载结束后的回调函数</param>
    [Obsolete("注意:建议使用泛型方式加载,如果必须得用Type的方式,请不要用泛型方法和type方法加载同类型同名资源")]
    public void LoadAsync(string path,Type type, UnityAction<UnityEngine.Object> callBack)
    {
        ResInfo<UnityEngine.Object> info;
        //资源的唯一ID
        string resName = path + "_" + type.Name;
        if (!resDic.ContainsKey(resName))
        {
            //声明一个 资源信息对象
            info = new ResInfo<UnityEngine.Object>();
            info.AddRefCount();
            //将资源记录添加到字典中
            resDic.Add(resName, info);
            //记录传入的委托函数 一会加载完成后使用
            info.callback += callBack;
            //通过协同程序异步加载资源
            info.coroutine = MonoMgr.Instance.StartCoroutine(ReallyLoadAsync(path,type));
        }
        else
        {
            //从字典中取出资源信息
            info = resDic[resName] as ResInfo<UnityEngine.Object>;
            info.AddRefCount();
            //资源还没加载完
            if (info.asset == null)
                info.callback += callBack;
            else
                callBack?.Invoke(info.asset);
        }

    }
    private IEnumerator ReallyLoadAsync(string path, Type type)
    {
        //异步加载资源
        ResourceRequest resourceRequest = Resources.LoadAsync(path,type);
        //等待资源加载结束后才会执行后面的代码
        yield return resourceRequest;
        //资源加载结束
        string resName = path + "_" + type.Name;
        if (resDic.ContainsKey(resName))
        {
            ResInfo<UnityEngine.Object> info = (resDic[resName] as ResInfo<UnityEngine.Object>);
            //取出资源信息 并记录加载完成的资源
            info.asset = resourceRequest.asset;
            if (info.refCount == 0)
                UnloadAsset(path,type,info.isDel,false);
            else
            {
                //将加载完成的资源传递出去
                info.callback?.Invoke(info.asset);
                //加载完毕后清空
                info.callback = null;
                info.coroutine = null;
            }
        }
    }


    /// <summary>
    /// 指定卸载资源
    /// </summary>
    /// <param name="assetToUnload"></param>
    public void UnloadAsset<T>(string path,bool isDel = false,bool isSub = true, UnityAction<T> callback = null)
    {
        //资源的唯一ID
        string resName = path + "_" + typeof(T).Name;
        //判断是否存在对应资源
        if (resDic.ContainsKey(resName))
        {
            ResInfo<T> resInfo = resDic[resName] as ResInfo<T>;
            if(isSub)
            resInfo.SubRefCount();
            resInfo.isDel = isDel;
            //资源已经加载结束
            if (resInfo.asset != null && resInfo.refCount == 0 && resInfo.isDel)
            {
                resDic.Remove(resName);
                Resources.UnloadAsset(resInfo.asset as UnityEngine.Object);
            }
            else if(resInfo.asset == null)//资源在加载中
            {
                if(callback != null)
                resInfo.callback -= callback;
            }
        }
    }
    public void UnloadAsset(string path, Type type, bool isDel = false, bool isSub = true, UnityAction<UnityEngine.Object> callback = null)
    {
        //资源的唯一ID
        string resName = path + "_" + type.Name;
        //判断是否存在对应资源
        if (resDic.ContainsKey(resName))
        {
            ResInfo<UnityEngine.Object> resInfo = resDic[resName] as ResInfo<UnityEngine.Object>;
            if(isSub)
            resInfo.SubRefCount();
            resInfo.isDel = isDel;
            //资源已经加载结束
            if (resInfo.asset != null && resInfo.refCount == 0 && resInfo.isDel)
            {
                resDic.Remove(resName);
                Resources.UnloadAsset(resInfo.asset as UnityEngine.Object);
            }
            else if(resInfo.asset == null)//资源在加载中
            {
                if (callback != null)
                    resInfo.callback -= callback;
            }
        }
    }

    /// <summary>
    /// 异步卸载对应没有使用的Resources相关的资源
    /// </summary>
    /// <param name="callBack">回调函数</param>
    public void UnloadUnusedAssets(UnityAction callBack)
    {
        MonoMgr.Instance.StartCoroutine(ReallyUnloadUnusedAssets(callBack));    
    }
    private IEnumerator ReallyUnloadUnusedAssets(UnityAction callBack)
    {
        //就是在真正移除不使用资源之前 应该把我们自己记录的那些引用计数为0 并且没有被移除记录的资源移除掉
        List<string> list = new List<string>();
        foreach (string path in resDic.Keys)
        {
            if (resDic[path].refCount == 0)
                list.Add(path);
        }
        foreach (string path in list)
        {
            resDic.Remove(path);
        }
        AsyncOperation asyncOperation = Resources.UnloadUnusedAssets();
        yield return asyncOperation;
        //卸载完毕后通知外部
        callBack?.Invoke();
    }

    /// <summary>
    /// 获取某个资源的引用计数
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="path"></param>
    /// <returns></returns>
    public int GetRefCount<T>(string path)
    { 
        string resName = path + "_" + typeof(T).Name;
        if (resDic.ContainsKey(resName))
            return (resDic[resName] as ResInfo<T>).refCount;
        else
            return 0;

    }

    /// <summary>
    /// 清空字典
    /// </summary>
    /// <param name="callBack"></param>
    public void ClearDic(UnityAction callBack)
    {
        MonoMgr.Instance.StartCoroutine(ReallyClearDic(callBack));
    }

    private IEnumerator ReallyClearDic(UnityAction callBack)
    {
        resDic.Clear();
        AsyncOperation asyncOperation = Resources.UnloadUnusedAssets();
        yield return asyncOperation;
        //卸载完毕后通知外部
        callBack?.Invoke();
    }
}
Editor资源加载模块

Editor 资源加载的主要作用,在进行项目开发时,最终发布的项目一般为了达到以下两个目的

1.减小包体大小

2.热更新

最终会通过 AssetBundle 资源加载作为主要加载方式

那么也就是说最终我们发布项目时

几乎所有的的游戏资源都会在 AB 包中的

AB 包最终会放置在 StreamingAssets 中 或者 远程资源服务器中

但是在开发期间,如果进行频繁的资源打包(资源放入 AssetBundle 包),会大大降低我们的开发效率。

因此我们在开发功能时,可以不通过 AB 包加载资源,而是在最终要测试或发布时才把资源整合进 AB 包。但是为了方便资源管理,我们也不能将资源放在 Resources 中来进行加载,因为最终 Resources 中的资源会被打包出去。那么此时 Editor 资源加载的作用就体现出来了,我们可以将资源放进不会被打包的 Editor 文件夹中,开发时通过 Editor 资源加载,测试和发布时使用 AssetBundle 资源加载。

cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

/// <summary>
/// 编辑器资源管理
/// 只有开发时能使用该管理器加载资源
/// </summary>
public class EditorResMgr : BaseManager<EditorResMgr>
{
    //用于放置需要打包进AB包中的资源路径
    private string rootPath = "Assets/Editor/ArtRes/"; 
    private EditorResMgr() { }

    /// <summary>
    /// 加载单个资源
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="path"></param>
    /// <returns></returns>
    public T LoadEditorRes<T>(string path) where T : UnityEngine.Object
    {
#if UNITY_EDITOR
        string suffixName = "";
        if (typeof(T) == typeof(GameObject))
            suffixName = ".prefab";
        else if (typeof(T) == typeof(Material))
            suffixName = ".mat";
        return AssetDatabase.LoadAssetAtPath<T>(rootPath + path + suffixName);
#else
        return null;
#endif
    }

    /// <summary>
    /// 加载图集相关资源的方法
    /// </summary>
    /// <param name="path"></param>
    /// <param name="spriteName"></param>
    /// <returns></returns>
    public Sprite LoadSprite(string path, string spriteName)
    {
#if UNITY_EDITOR
        //加载图集中的所有子资源
        Object[] sprites = AssetDatabase.LoadAllAssetRepresentationsAtPath(rootPath + path);

        foreach (var item in sprites)
        {
            if (item.name == spriteName)
                return item as Sprite;
        }
        return null;
#else
        return null;
#endif
    }

    /// <summary>
    /// 获取整个图集
    /// </summary>
    /// <param name="path"></param>
    /// <returns></returns>
    public Dictionary<string, Sprite> LoadSprites(string path)
    {
#if UNITY_EDITOR
        Dictionary<string, Sprite> spDic = new Dictionary<string, Sprite>();
        //加载图集中的所有子资源
        Object[] sprites = AssetDatabase.LoadAllAssetRepresentationsAtPath(rootPath + path);
        foreach (var item in sprites)
        {
            spDic.Add(item.name, item as Sprite);
        }
        return spDic;
#else
        return null;
#endif
    }
}
AssetBundle资源管理模块
cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public class ABMgr : SingletonAutoMono<ABMgr>
{
    //主包
    private AssetBundle mainAB = null;
    //主包依赖获取配置文件
    private AssetBundleManifest manifest = null;

    //选择存储 AB包的容器
    //AB包不能够重复加载 否则会报错
    //字典知识 用来存储 AB包对象
    private Dictionary<string, AssetBundle> abDic = new Dictionary<string, AssetBundle>();

    /// <summary>
    /// 获取AB包加载路径
    /// </summary>
    private string PathUrl
    {
        get
        {
            return Application.streamingAssetsPath + "/";
        }
    }

    /// <summary>
    /// 主包名 根据平台不同 报名不同
    /// </summary>
    private string MainName
    {
        get
        {
#if UNITY_IOS
            return "IOS";
#elif UNITY_ANDROID
            return "Android";
#else
            return "PC";
#endif
        }
    }

    /// <summary>
    /// 加载主包 和 配置文件
    /// 因为加载所有包是 都得判断 通过它才能得到依赖信息
    /// 所以写一个方法
    /// </summary>
    private void LoadMainAB()
    {
        if( mainAB == null )
        {
            mainAB = AssetBundle.LoadFromFile( PathUrl + MainName);
            manifest = mainAB.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
        }
    }

    /// <summary>
    /// 泛型异步加载资源
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="abName"></param>
    /// <param name="resName"></param>
    /// <param name="callBack"></param>
    public void LoadResAsync<T>(string abName, string resName, UnityAction<T> callBack, bool isSync = false) where T:Object
    {
        StartCoroutine(ReallyLoadResAsync<T>(abName, resName, callBack,isSync));
    }
    //协程函数
    private IEnumerator ReallyLoadResAsync<T>(string abName, string resName, UnityAction<T> callBack, bool isSync) where T : Object
    {
        //加载主包
        LoadMainAB();
        //获取依赖包
        string[] strs = manifest.GetAllDependencies(abName);
        for (int i = 0; i < strs.Length; i++)
        {
            if (!abDic.ContainsKey(strs[i]))
            {
                if (isSync) //同步加载
                {
                    AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + strs[i]);
                    abDic.Add(strs[i], ab);
                }
                else  //异步加载
                {
                    //一开始异步加载就记录  如果值为null说明正在加载
                    abDic.Add(strs[i], null);
                    AssetBundleCreateRequest rep = AssetBundle.LoadFromFileAsync(PathUrl + strs[i]);
                    yield return rep;
                    //不为空加载结束
                    abDic[strs[i]] = rep.assetBundle;
                }
               
            }
            else//证明字典中已经记录了一个AB包信息
            {
                //如果字典中记录的是null 那就证明正在加载
                //我们需要等待加载结束  直接使用
                while (abDic[strs[i]] == null)
                { 
                    //只有发现正在加载中  就不停的等待一帧
                    yield return 0;
                }
            }
        }
        //加载目标包
        if (!abDic.ContainsKey(abName))
        {
            if (isSync) //同步加载
            {
                AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + abName);
                abDic.Add(abName, ab);
            }
            else
            {
                //一开始异步加载就记录  如果值为null说明正在加载
                abDic.Add(abName, null);
                AssetBundleCreateRequest rep = AssetBundle.LoadFromFileAsync(PathUrl + abName);
                yield return rep;
                abDic[abName] = rep.assetBundle;
            }
          
        }
        else
        {
            //如果字典中记录的是null 那就证明正在加载
            //我们需要等待加载结束  直接使用
            while (abDic[abName] == null)
            {
                //只有发现正在加载中  就不停的等待一帧
                yield return 0;
            }
        }
        if (isSync) //同步加载
        {
            //即使是同步加载也需要通过回调函数传结果给外部
            T res = abDic[abName].LoadAsset<T>(resName);
            callBack(res);
        }
        else
        {
            //异步加载包中资源
            AssetBundleRequest abq = abDic[abName].LoadAssetAsync<T>(resName);
            yield return abq;

            callBack(abq.asset as T);
        }

    }

    /// <summary>
    /// Type异步加载资源
    /// </summary>
    /// <param name="abName"></param>
    /// <param name="resName"></param>
    /// <param name="type"></param>
    /// <param name="callBack"></param>
    public void LoadResAsync(string abName, string resName, System.Type type, UnityAction<Object> callBack, bool isSync = false)
    {
        StartCoroutine(ReallyLoadResAsync(abName, resName, type, callBack, isSync));
    }

    private IEnumerator ReallyLoadResAsync(string abName, string resName, System.Type type, UnityAction<Object> callBack, bool isSync)
    {
        //加载主包
        LoadMainAB();
        //获取依赖包
        string[] strs = manifest.GetAllDependencies(abName);
        for (int i = 0; i < strs.Length; i++)
        {
            if (!abDic.ContainsKey(strs[i]))
            {
                if (isSync) //同步加载
                {
                    AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + strs[i]);
                    abDic.Add(strs[i], ab);
                }
                else  //异步加载
                {
                    //一开始异步加载就记录  如果值为null说明正在加载
                    abDic.Add(strs[i], null);
                    AssetBundleCreateRequest rep = AssetBundle.LoadFromFileAsync(PathUrl + strs[i]);
                    yield return rep;
                    //不为空加载结束
                    abDic[strs[i]] = rep.assetBundle;
                }

            }
            else//证明字典中已经记录了一个AB包信息
            {
                //如果字典中记录的是null 那就证明正在加载
                //我们需要等待加载结束  直接使用
                while (abDic[strs[i]] == null)
                {
                    //只有发现正在加载中  就不停的等待一帧
                    yield return 0;
                }
            }
        }
        //加载目标包
        if (!abDic.ContainsKey(abName))
        {
            if (isSync) //同步加载
            {
                AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + abName);
                abDic.Add(abName, ab);
            }
            else
            {
                //一开始异步加载就记录  如果值为null说明正在加载
                abDic.Add(abName, null);
                AssetBundleCreateRequest rep = AssetBundle.LoadFromFileAsync(PathUrl + abName);
                yield return rep;
                abDic[abName] = rep.assetBundle;
            }

        }
        else
        {
            //如果字典中记录的是null 那就证明正在加载
            //我们需要等待加载结束  直接使用
            while (abDic[abName] == null)
            {
                //只有发现正在加载中  就不停的等待一帧
                yield return 0;
            }
        }
        if (isSync)
        {
            Object res  =  abDic[abName].LoadAsset(resName, type);
            callBack(res);
        }
        else
        {
            //异步加载包中资源
            AssetBundleRequest abq = abDic[abName].LoadAssetAsync(resName, type);
            yield return abq;

            callBack(abq.asset);
        }

    }

    /// <summary>
    /// 名字 异步加载 指定资源
    /// </summary>
    /// <param name="abName"></param>
    /// <param name="resName"></param>
    /// <param name="callBack"></param>
    public void LoadResAsync(string abName, string resName, UnityAction<Object> callBack, bool isSync = false)
    {
        StartCoroutine(ReallyLoadResAsync(abName, resName, callBack, isSync));
    }

    private IEnumerator ReallyLoadResAsync(string abName, string resName, UnityAction<Object> callBack, bool isSync)
    {
        //加载主包
        LoadMainAB();
        //获取依赖包
        string[] strs = manifest.GetAllDependencies(abName);
        for (int i = 0; i < strs.Length; i++)
        {
            if (!abDic.ContainsKey(strs[i]))
            {
                if (isSync) //同步加载
                {
                    AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + strs[i]);
                    abDic.Add(strs[i], ab);
                }
                else  //异步加载
                {
                    //一开始异步加载就记录  如果值为null说明正在加载
                    abDic.Add(strs[i], null);
                    AssetBundleCreateRequest rep = AssetBundle.LoadFromFileAsync(PathUrl + strs[i]);
                    yield return rep;
                    //不为空加载结束
                    abDic[strs[i]] = rep.assetBundle;
                }

            }
            else//证明字典中已经记录了一个AB包信息
            {
                //如果字典中记录的是null 那就证明正在加载
                //我们需要等待加载结束  直接使用
                while (abDic[strs[i]] == null)
                {
                    //只有发现正在加载中  就不停的等待一帧
                    yield return 0;
                }
            }
        }
        //加载目标包
        if (!abDic.ContainsKey(abName))
        {
            if (isSync) //同步加载
            {
                AssetBundle ab = AssetBundle.LoadFromFile(PathUrl + abName);
                abDic.Add(abName, ab);
            }
            else
            {
                //一开始异步加载就记录  如果值为null说明正在加载
                abDic.Add(abName, null);
                AssetBundleCreateRequest rep = AssetBundle.LoadFromFileAsync(PathUrl + abName);
                yield return rep;
                abDic[abName] = rep.assetBundle;
            }

        }
        else
        {
            //如果字典中记录的是null 那就证明正在加载
            //我们需要等待加载结束  直接使用
            while (abDic[abName] == null)
            {
                //只有发现正在加载中  就不停的等待一帧
                yield return 0;
            }
        }
        if (isSync) //同步加载
        {
            Object res = abDic[abName].LoadAsset(resName);
            callBack(res);
        }
        else
        {
            //异步加载包中资源
            AssetBundleRequest abq = abDic[abName].LoadAssetAsync(resName);
            yield return abq;
            callBack(abq.asset);
        }

    }

    //卸载AB包的方法
    public void UnLoadAB(string name,UnityAction<bool> callBackResult)
    {
        if( abDic.ContainsKey(name) )
        {
            if (abDic[name] == null)
            {
                //代表正在异步加载,没有卸载成功
                callBackResult(false);
                return;
            }
            abDic[name].Unload(false);
            abDic.Remove(name);
            //卸载成功
            callBackResult(true);
        }
    }

    //清空AB包的方法
    public void ClearAB()
    {
        //清理之前 停止协同程序
        StopAllCoroutines();
        AssetBundle.UnloadAllAssetBundles(false);
        abDic.Clear();
        //卸载主包
        mainAB = null;
    }
}
UnityWebRequest类资源加载模块

WWW 和 UnityWebRequest 都是 Unity 提供给我们的简单的访问网页的类

我们可以利用它们来进行数据的上传和下载

除此之外,它们都支持 file:// 本地文件传输协议,我们可以利用该协议来异步加载本地文件

特别是在 Android 平台时,我们无法直接通过 C# 中的 File 公共类加载 StreamingAssets 文件夹中的内容,我们需要使用 WWW 或 UnityWebRequest 类来加载

cs 复制代码
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;

public class UnityWebRequestResMgr : BaseManager<UnityWebRequestResMgr>
{
    private UnityWebRequestResMgr() { }
    /// <summary>
    /// 利用UnityWebRequest去加载资源
    /// </summary>
    /// <typeparam name="T">资源类型只能是 string byte[] Texture AssetBundle</typeparam>
    /// <param name="path">路径需要自己加上协议 http:// ftp:// file://</param>
    /// <param name="callback">加载成功回调函数</param>
    /// <param name="failCallBack">加载失败回调函数</param>
    public void LoadRes<T>(string path, UnityAction<T> callback, UnityAction failCallBack) where T : class
    {
        MonoMgr.Instance.StartCoroutine(ReallyLoadRes<T>(path, callback, failCallBack));
    }

    private IEnumerator ReallyLoadRes<T>(string path, UnityAction<T> callback, UnityAction failCallBack) where T : class
    {  
        Type type = typeof(T);
        //用于加载的对象
        UnityWebRequest req = null;
        if(type == typeof(string) || type == typeof(byte[]))
            req = UnityWebRequest.Get(path);
        if(type == typeof(Texture))
            req = UnityWebRequestTexture.GetTexture(path);
        if (type == typeof(AssetBundle))
            req = UnityWebRequestAssetBundle.GetAssetBundle(path);
        else
        {
            failCallBack?.Invoke();
            yield break;
        }

        yield return req.SendWebRequest();
        //如果加载成功
        if (req.result == UnityWebRequest.Result.Success)
        {
            if (type == typeof(string))
                callback?.Invoke(req.downloadHandler.text as T);
            else if (type == typeof(byte[]))
                callback?.Invoke(req.downloadHandler.data as T);
            else if (type == typeof(Texture))
                callback?.Invoke(DownloadHandlerTexture.GetContent(req) as T);
            else if (type == typeof(AssetBundle))
                callback?.Invoke(DownloadHandlerAssetBundle.GetContent(req) as T);
        }
        else
            failCallBack?.Invoke();
        //释放对象
        req.Dispose();
    }
}
整合EditorResMgr和ABMgr
cs 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 用于进行加载AB包相关资源的整合,在开发中通过EditorResMgr去加载对应资源
/// </summary>
public class ABResMgr : BaseManager<ABResMgr>
{
    private bool isDebug = true;
    private ABResMgr() { }

    public void LoadResAsync<T>(string abName, string resName, UnityAction<T> callBack, bool isSync = false) where T : Object
    {
#if UNITY_EDITOR
        if (isDebug)
        {
            T res = EditorResMgr.Instance.LoadEditorRes<T>($"{abName}/{resName}");
            callBack?.Invoke(res);
        }
        else
        {
            ABMgr.Instance.LoadResAsync<T>(abName, resName, callBack, isSync);
        }
#else
        ABMgr.Instance.LoadResAsync<T>(abName, resName, callBack, isSync);
#endif
    }
}
相关推荐
时光追逐者4 小时前
全面的 C#/.NET 图表构建解决方案,助力快速实现图表开发需求!
微软·c#·.net·.net core·图表
m5655bj4 小时前
通过 C# 在 Word 文档中添加文字或图片水印
c#·word·visual studio
千里镜宵烛5 小时前
Lua-迭代器
开发语言·junit·lua
井队Tell5 小时前
打造高清3D虚拟世界|零基础学习Unity HDRP高清渲染管线(第九天)
学习·3d·unity
渡我白衣5 小时前
C++ 同名全局变量:当符号在链接器中“相遇”
开发语言·c++·人工智能·深度学习·microsoft·语言模型·人机交互
淮北4945 小时前
html + css +js
开发语言·前端·javascript·css·html
源码_V_saaskw6 小时前
JAVA国际版二手交易系统手机回收好物回收发布闲置商品系统源码支持APP+H5
java·开发语言·微信·智能手机·微信小程序·小程序
java1234_小锋6 小时前
PyTorch2 Python深度学习 - PyTorch2安装与环境配置
开发语言·python·深度学习·pytorch2