【unity进阶知识6】Resources的使用,如何封装一个Resources资源管理器

文章目录

一、Unity资源加载的几种方式

1、Inspector窗口拖拽

  • 在脚本中用public声明变量,然后在Inspector窗口把要加载的资源拖拽给该脚本的变量。
  • 不建议在大型项目使用。在公司的项目也不要用。如果你是独立游戏开发者,则可以用。
  • 不支持热更新。

2、Resources

  • 用Resources.Load方法、Resources.LoadAsync方法、Resources.LoadAll方法来加载资源。
  • 可以在商业项目使用,包括公司的项目。但是Resources文件夹中可以存放的资源有限,大概只能存储2G左右的资源,因此要谨慎使用。
  • 不支持热更新。

3、AssetBundle

  • 用AssetBundle.LoadFromXXX方法来加载资源。
  • 商业项目常用的资源加载方案,如果你在公司做项目,则推荐用这种方式来加载资源。
  • 效率比Resources加载高,占用内存小,正式发布游戏后,资源所占用的空间也小。
  • 支持热更新。

4、Addressables(可寻址资源系统)

  • 可以理解为高级的AssetBundle,资源管理由Unity内部自动完成。
  • 但是目前还在发展中,可能有bug。主流的商业游戏都是使用AssetBundle来做资源加载的。
  • 支持热更新。

5、AssetDatabase

  • 用AssetDatabase.LoadAssetAtPath方法来加载资源。
  • 仅限于编辑器模式,主要用于在编辑器模式下用代码更改本地文件。
  • 游戏运行时不会用这种方案加载资源。
  • 不支持热更新。

二、准备

使用前必须在项目中创建一个名叫Resources的文件夹,这个名字是固定的。

三、同步加载Resources资源

1、Resources.Load同步加载单个资源

1.1、基本加载

Resources.Load(string 要加载的资源的路径)

返回值是Object型。

如果有多个相同路径的资源,则只会返回找到的第一个资源。

调用案例

csharp 复制代码
GameObject go = Resources.Load("Prefabs/Cube") as GameObject;

1.2、加载指定类型的资源

Resources.Load(string 要加载的资源的路径, System.Type 要加载的资源的类型的Type对象)

返回值是Object型。

如果有多个相同类型且相同路径的资源,则只会返回找到的第一个资源。

调用案例

csharp 复制代码
GameObject go = Resources.Load("Prefabs/Cube", typeof(GameObject)) as GameObject;

1.3、使用泛型加载指定类型的资源

Resources.Load<要加载的资源的类型>(string 要加载的资源的路径)

返回值是T型。

返回值是要加载的资源的类型。如果有多个相同类型,且相同路径的资源,则只会返回找到的第一个资源。

调用案例

csharp 复制代码
GameObject go = Resources.Load<GameObject>("Prefabs/Cube");

2、Resources.LoadAll同步加载多个资源

2.1、基本加载

Resources.LoadAll(string 要加载的资源的文件夹路径或文件路径)

返回值是Object[]型。

同步加载Resources文件夹中指定路径的文件夹中的所有资源,包括其中子孙文件夹中的所有资源,然后返回到一个Object[]型数组。

如果该路径是一个文件,则只会加载该文件,并返回到一个Object[]型数组。如果没有加载到任何资源,则返回一个没有任何元素的Object[]型数组。

调用案例

csharp 复制代码
Object[] gos = Resources.LoadAll("Prefabs");
for (int i = 0; i < gos.Length; i++)
{
    Instantiate(gos[i] as GameObject);
}

2.2、加载指定类型的资源

Resources.LoadAll(string 要加载的资源的文件夹路径或文件路径,System.Type 要加载的资源的类型的Type对象)

返回值是Object[]型。

同步加载Resources文件夹中指定路径的文件夹中的指定类型的所有资源,包括其中子孙文件夹中的该类型的所有资源,然后返回到一个Object[]型数组。

如果该路径是一个该指定类型的文件,则只会加载该文件,并返回到一个Object[]型数组。如果没有加载到任何资源,则返回一个没有任何元素的Object[]型数组。

调用案例

csharp 复制代码
Object[] gos = Resources.LoadAll("Prefabs", typeof(GameObject));
for (int i = 0; i < gos.Length; i++)
{
    Instantiate(gos[i] as GameObject);
}

2.3、使用泛型加载指定类型的资源

Resources.LoadAll<泛型T类型>(string 要加载的资源的文件夹路径或文件路径)

返回值是T[]型。

同步加载Resources文件夹中指定路径的文件夹中的指定类型的所有资源,包括其中子孙文件夹中的该类型的所有资源,然后返回到一个T[]型数组。

如果该路径是一个该指定类型的文件,则只会加载该文件,并返回到一个T[]型数组。如果没有加载到任何资源,则返回一个没有任何元素的T[]型数组。

调用案例

csharp 复制代码
GameObject[] gos = Resources.LoadAll<GameObject>("Prefabs");
for (int i = 0; i < gos.Length; i++)
{
    Instantiate(gos[i]);
}

四、异步加载Resources文件夹中的资源

Resources.LoadAsync异步加载单个资源方法

使用泛型加载指定类型的资源

Resources.LoadAsync<泛型T类型>(string 要加载的资源的路径)

返回值是ResourceRequest类型。

一般配合协程来使用。在协程中可以使用yield return来等待资源加载。

如果有多个相同路径的资源,则只会加载找到的第一个资源。

调用案例

csharp 复制代码
void Start()
{
   StartCoroutine(LoadAsyncCoroutine());
}

IEnumerator LoadAsyncCoroutine(){
   //开始异步加载资源
   ResourceRequest rr = Resources.LoadAsync<GameObject>("Prefabs/Cube");

   //等待资源加载完毕
   yield return rr;

   //加载完成执行逻辑
   Instantiate(rr.asset);
}

五、封装一个Resources资源管理器

1、封装异步加载资源

注意这个脚本依赖Mono管理器:【unity进阶知识2】Mono管理器

csharp 复制代码
public class ResourcesManager : Singleton<ResourcesManager>
{
    /// <summary>
    /// 异步加载Resources文件夹中指定类型的资源
    /// </summary>
    public void LoadAsync<T>(string path, UnityAction<T> callback = null) where T : Object
    {
        MonoManager.Instance.StartCoroutine(LoadAsyncCoroutine(path, callback));
    }
    IEnumerator LoadAsyncCoroutine<T>(string path, UnityAction<T> callback = null) where T : Object
    {
        ResourceRequest resourceRequest = Resources.LoadAsync<T>(path);
        yield return resourceRequest;
        callback?.Invoke(resourceRequest.asset as T);
    }
}

调用,效果和前面一样

csharp 复制代码
ResourcesManager.Instance.LoadAsync<GameObject>("Prefabs/Cube", Callback);

void Callback(GameObject obj){
    Instantiate(obj);
}

或者

csharp 复制代码
ResourcesManager.Instance.LoadAsync<GameObject>("Prefabs/Cube", (obj)=>{
    Instantiate(obj);
});

2、封装同步加载资源

同步加载封装虽然是一样的,但是好处是自己封装可以统一管理和进行自定义注释

csharp 复制代码
/// <summary>
/// <para>同步加载Resources文件夹中指定类型的资源。</para>
/// <para>如果有多个相同类型,且相同路径的资源,则只会返回找到的第一个资源。</para>
/// </summary>
/// <typeparam name="T">要加载的资源的类型</typeparam>
/// <param name="path">要加载的资源的路径。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>
public T Load<T>(string path) where T : Object
{
    return Resources.Load<T>(path);
}

调用

csharp 复制代码
GameObject[] gos = ResourcesManager.Instance.LoadAll<GameObject>("Prefabs");
for (int i = 0; i < gos.Length; i++)
{
    Instantiate(gos[i]);
}

3、封装卸载资源方法

通过Resources加载的资源,就算销毁了,但是它还是占在内存里面,没有释放,所以我们需要卸载资源,比如切换场景的时候

csharp 复制代码
/// <summary>
/// <para>异步卸载所有用Resources方式加载到内存中且当前没有被任何地方使用的资源。</para>
/// <para>例如要卸载某一个用Resources方式加载的预制体,则必须确保场景中所有这个预制体创建的物体都被销毁了,且这个预制体资源没有赋值给任何脚本中的任何变量。</para>
/// <para>如果有,可以把该变量也赋值为null,这样本方法才能成功释放它。</para>
/// </summary>
/// <param name="callback">资源卸载完毕后执行的逻辑</param>
public void UnloadUnusedAssets(UnityAction callback = null)
{
    MonoManager.Instance.StartCoroutine(UnLoadUnusedAssetsCoroutine(callback));
}
IEnumerator UnLoadUnusedAssetsCoroutine(UnityAction callback = null)
{
    //异步操作对象,记录了异步操作的数据。
    AsyncOperation asyncOperation = Resources.UnloadUnusedAssets();

    //等待资源卸载
    while (asyncOperation.progress < 1)
        yield return null;

    //资源卸载完毕后执行的逻辑
    callback?.Invoke();
}

调用

csharp 复制代码
ResourcesManager.Instance.UnloadUnusedAssets(() =>
{
    Debug.Log("异步卸载所有资源成功");
});

六、最终代码

csharp 复制代码
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
using XYFrame;

/// <summary>
/// Resources资源加载管理器
/// 这个脚本依赖Mono管理器。
/// 要加载的资源必须放到项目中名叫Resources的文件夹中。项目中可以有多个名叫Resources的文件夹,但如此一来,必须避免资源的路径相同。
/// </summary>
public class ResourcesManager : Singleton<ResourcesManager>
{
    #region 同步加载单个资源
    /// <summary>
    /// <para>同步加载Resources文件夹中的资源。</para>
    /// <para>如果有多个相同路径的资源,则只会返回找到的第一个资源。</para>
    /// </summary>
    /// <param name="path">要加载的资源的路径。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>
    public Object Load(string path)
    {
        return Resources.Load(path);
    }

    /// <summary>
    /// <para>同步加载Resources文件夹中指定类型的资源。</para>
    /// <para>如果有多个相同类型,且相同路径的资源,则只会返回找到的第一个资源。</para>
    /// </summary>
    /// <param name="path">要加载的资源的路径。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>
    /// <param name="systemTypeInstance">要加载的资源的类型的Type对象。例如typeof(GameObject)表示要加载的资源的类型是GameObject型。</param>
    /// <returns></returns>
    public Object Load(string path, System.Type systemTypeInstance)
    {
        return Resources.Load(path, systemTypeInstance);
    }

    /// <summary>
    /// <para>同步加载Resources文件夹中指定类型的资源。</para>
    /// <para>如果有多个相同类型,且相同路径的资源,则只会返回找到的第一个资源。</para>
    /// </summary>
    /// <typeparam name="T">要加载的资源的类型</typeparam>
    /// <param name="path">要加载的资源的路径。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>
    public T Load<T>(string path) where T : Object
    {
        return Resources.Load<T>(path);
    }
    #endregion

    #region 同步加载多个资源
    /// <summary>
    /// <para>同步加载Resources文件夹中指定路径的文件夹中的所有资源,包括其中子孙文件夹中的所有资源,然后返回到一个Object[]型数组。</para>
    /// <para>如果该路径是一个文件,则只会加载该文件,并返回到一个Object[]型数组。</para>
    /// <para>如果没有加载到任何资源,则返回一个没有任何元素的Object[]型数组。</para>
    /// </summary>
    /// <param name="path">要加载的文件夹或文件的路径。例如"Prefabs"表示Resources文件夹中的Prefabs文件夹。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>
    public Object[] LoadAll(string path)
    {
        return Resources.LoadAll(path);
    }

    /// <summary>
    /// <para>同步加载Resources文件夹中指定路径的文件夹中指定类型的所有资源,包括其中子孙文件夹中的该类型的所有资源,然后返回到一个Object[]型数组。</para>
    /// <para>如果该路径是一个该指定类型的文件,则只会加载该文件,并返回到一个Object[]型数组。</para>
    /// <para>如果没有加载到任何资源,则返回一个没有任何元素的Object[]型数组。</para>
    /// </summary>
    /// <param name="path">要加载的文件夹或文件的路径。例如"Prefabs"表示Resources文件夹中的Prefabs文件夹。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>
    /// <param name="systemTypeInstance">要加载的资源的类型的Type对象。例如typeof(GameObject)表示要加载的资源的类型是GameObject型。</param>
    public Object[] LoadAll(string path, System.Type systemTypeInstance)
    {
        return Resources.LoadAll(path, systemTypeInstance);
    }

    /// <summary>
    /// <para>同步加载Resources文件夹中指定路径的文件夹中指定类型的所有资源,包括其中子孙文件夹中的该类型的所有资源,然后返回到一个Object[]型数组。</para>
    /// <para>如果该路径是一个该指定类型的文件,则只会加载该文件,并返回到一个Object[]型数组。</para>
    /// <para>如果没有加载到任何资源,则返回一个没有任何元素的Object[]型数组。</para>
    /// </summary>
    /// <typeparam name="T">要加载的资源的类型</typeparam>
    /// <param name="path">要加载的文件夹或文件的路径。例如"Prefabs"表示Resources文件夹中的Prefabs文件夹。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>
    public T[] LoadAll<T>(string path) where T : Object
    {
        return Resources.LoadAll<T>(path);
    }

    /// <summary>
    /// <para>同步把Resources文件夹中指定路径的文件夹及其所有子孙文件夹中所有指定类型的资源添加到一个新建的字典中,并返回该字典。</para>
    /// <para>应保证Prefabs文件夹中以及它的子孙文件夹中没有重名的资源,如果有重名的,则只会添加找到的第一个资源进字典,其它重名的资源不会进到字典中。</para>
    /// </summary>
    /// <typeparam name="T">要加载的资源类型</typeparam>
    /// <param name="path">资源的路径。例如"Folder/Res"表示Resources文件夹中的Folder文件夹中的Res文件夹</param>
    public Dictionary<string, T> LoadAllIntoDictionary<T>(string path) where T : Object
    {
        Dictionary<string, T> dic = new Dictionary<string, T>();
        T[] temp = Resources.LoadAll<T>(path);
        for (int i = 0; i < temp.Length; i++)
        {
            if (!dic.ContainsKey(temp[i].name))//字典不存在该键,才添加进去。这样可以防止字典的键名重复而报错。
            {
                dic.Add(temp[i].name, temp[i]);
            }
            else//如果字典已经存在该键,则跳过这个资源,并输出警告,不将它加入到字典中
            {
                Debug.LogWarning(string.Format("Resources/{0}的子孙文件夹的资源{1}与已经添加到字典中的资源重名,因此无法将它添加到字典中,请确保加载的资源的名字是唯一的。", path, temp[i].name));
            }
        }
        return dic;
    }

    /// <summary>
    /// <para>同步把Resources文件夹中指定路径及其所有子孙文件夹中所有指定类型的资源添加到指定的字典中</para>
    /// </summary>
    /// <typeparam name="T">要加载的资源类型</typeparam>
    /// <param name="path">资源的路径。例如"Folder/Res"表示Resources文件夹中的Folder文件夹中的Res文件夹</param>
    /// <param name="dictionary">指定的字典</param>
    public void LoadAllIntoDictionary<T>(string path, Dictionary<string, T> dictionary) where T : Object
    {
        T[] temp = Resources.LoadAll<T>(path);
        for (int i = 0; i < temp.Length; i++)
        {
            if (!dictionary.ContainsKey(temp[i].name))//字典不存在该键,才添加进去。这样可以防止字典的键名重复而报错。
            {
                dictionary.Add(temp[i].name, temp[i]);
            }
            else//如果字典已经存在该键,则跳过这个资源,并输出警告,不将它加入到字典中
            {
                Debug.LogWarning(string.Format("Resources/{0}的子孙文件夹的资源{1}与已经添加到字典中的资源重名,因此无法将它添加到字典中,请确保加载的资源的名字是唯一的,并且传入参数的字典中不包含该名字的资源。", path, temp[i].name));
            }
        }
    }
    #endregion

    #region 异步加载单个资源
    /// <summary>
    /// <para>异步加载Resources文件夹中的资源。</para>
    /// <para>如果有多个相同路径的资源,则只会加载找到的第一个资源。</para>
    /// </summary>
    /// <param name="path">要加载的资源的路径。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>
    /// <param name="callback">资源加载完毕后要执行的逻辑。参数表示加载的资源。</param>
    public void LoadAsync(string path, UnityAction<Object> callback = null)
    {
        MonoManager.Instance.StartCoroutine(LoadAsyncCoroutine(path, callback));
    }
    IEnumerator LoadAsyncCoroutine(string path, UnityAction<Object> callback = null)
    {
        ResourceRequest resourceRequest = Resources.LoadAsync(path);
        yield return resourceRequest;
        callback?.Invoke(resourceRequest.asset);
    }

    /// <summary>
    /// <para>异步加载Resources文件夹中指定类型的资源。</para>
    /// <para>如果有多个相同类型,且相同路径的资源,则只会加载找到的第一个资源。</para>
    /// </summary>
    /// <param name="path"></param>
    /// <param name="type">要加载的资源的类型的Type对象。例如typeof(GameObject)表示要加载的资源的类型是GameObject型。</param>
    /// <param name="callback">资源加载完毕后要执行的逻辑。参数表示加载的资源。</param>
    public void LoadAsync(string path, System.Type type, UnityAction<Object> callback = null)
    {
        MonoManager.Instance.StartCoroutine(LoadAsyncCoroutine(path, type, callback));
    }
    IEnumerator LoadAsyncCoroutine(string path, System.Type type, UnityAction<Object> callback = null)
    {
        ResourceRequest resourceRequest = Resources.LoadAsync(path, type);
        yield return resourceRequest;
        callback?.Invoke(resourceRequest.asset);
    }

    /// <summary>
    /// <para>异步加载Resources文件夹中指定类型的资源。</para>
    /// <para>如果有多个相同类型,且相同路径的资源,则只会加载找到的第一个资源。</para>
    /// </summary>
    /// <typeparam name="T">加载的资源的类型</typeparam>
    /// <param name="path">要加载的资源的路径。例如"Prefabs/Cube"表示Resources文件夹中的Prefabs文件夹中的名叫Cube的资源。</param>
    /// <param name="callback">资源加载完毕后要执行的逻辑</param>
    public void LoadAsync<T>(string path, UnityAction<T> callback = null) where T : Object
    {
        MonoManager.Instance.StartCoroutine(LoadAsyncCoroutine(path, callback));
    }
    IEnumerator LoadAsyncCoroutine<T>(string path, UnityAction<T> callback = null) where T : Object
    {
        ResourceRequest resourceRequest = Resources.LoadAsync<T>(path);
        yield return resourceRequest;
        callback?.Invoke(resourceRequest.asset as T);
    }
    #endregion

    #region 卸载资源
    /// <summary>
    /// <para>异步卸载所有用Resources方式加载到内存中且当前没有被任何地方使用的资源。</para>
    /// <para>例如要卸载某一个用Resources方式加载的预制体,则必须确保场景中所有这个预制体创建的物体都被销毁了,且这个预制体资源没有赋值给任何脚本中的任何变量。</para>
    /// <para>如果有,可以把该变量也赋值为null,这样本方法才能成功释放它。</para>
    /// </summary>
    /// <param name="callback">资源卸载完毕后执行的逻辑</param>
    public void UnloadUnusedAssets(UnityAction callback = null)
    {
        MonoManager.Instance.StartCoroutine(UnLoadUnusedAssetsCoroutine(callback));
    }
    IEnumerator UnLoadUnusedAssetsCoroutine(UnityAction callback = null)
    {
        //异步操作对象,记录了异步操作的数据。
        AsyncOperation asyncOperation = Resources.UnloadUnusedAssets();

        //等待资源卸载
        while (asyncOperation.progress < 1)
            yield return CoroutineTool.WaitForFrame();

        //资源卸载完毕后执行的逻辑
        callback?.Invoke();
    }

    /// <summary>
    /// <para>同步卸载指定的资源。</para>
    /// <para>只能卸载非GameObject类型和Component类型,例如Mesh、Texture、Material、Shader。如果卸载了不让卸载的资源,则会报错。</para>
    /// <para>如果随后加载的任何场景或资源引用了该资源,将导致从磁盘中加载该资源的新实例。此新实例将与先前卸载的对象相互独立。</para>
    /// </summary>
    /// <param name="assetToUnload">要卸载的资源</param>
    public void UnloadAsset(Object assetToUnload)
    {
        Resources.UnloadAsset(assetToUnload);
    }
    #endregion
}

完结

赠人玫瑰,手有余香!如果文章内容对你有所帮助,请不要吝啬你的点赞评论和关注,你的每一次支持都是我不断创作的最大动力。当然如果你发现了文章中存在错误或者有更好的解决方法,也欢迎评论私信告诉我哦!

好了,我是向宇https://xiangyu.blog.csdn.net

一位在小公司默默奋斗的开发者,闲暇之余,边学习边记录分享,站在巨人的肩膀上,通过学习前辈们的经验总是会给我很多帮助和启发!如果你遇到任何问题,也欢迎你评论私信或者加群找我, 虽然有些问题我也不一定会,但是我会查阅各方资料,争取给出最好的建议,希望可以帮助更多想学编程的人,共勉~

相关推荐
Re.不晚9 分钟前
Java入门15——抽象类
java·开发语言·学习·算法·intellij-idea
老秦包你会11 分钟前
Qt第三课 ----------容器类控件
开发语言·qt
凤枭香14 分钟前
Python OpenCV 傅里叶变换
开发语言·图像处理·python·opencv
ULTRA??18 分钟前
C加加中的结构化绑定(解包,折叠展开)
开发语言·c++
远望清一色34 分钟前
基于MATLAB的实现垃圾分类Matlab源码
开发语言·matlab
confiself44 分钟前
大模型系列——LLAMA-O1 复刻代码解读
java·开发语言
XiaoLeisj1 小时前
【JavaEE初阶 — 多线程】Thread类的方法&线程生命周期
java·开发语言·java-ee
杜杜的man1 小时前
【go从零单排】go中的结构体struct和method
开发语言·后端·golang
幼儿园老大*1 小时前
走进 Go 语言基础语法
开发语言·后端·学习·golang·go
半桶水专家1 小时前
go语言中package详解
开发语言·golang·xcode