20.Resources资源加载:基本实现

一、实现目标

Resources 的加载和卸载方法统一封装到 ResMgr 中:

  • 同步加载资源。
  • 异步加载资源。
  • 卸载指定资源。
  • 异步卸载未使用资源。
    ResMgr 继承非 MonoBehaviour 单例基类:
csharp 复制代码
public class ResMgr : BaseManager<ResMgr>

由于普通 C# 类不能直接开启协程,异步操作需要通过 MonoMgr 执行。

二、同步加载

csharp 复制代码
public T Load<T>(string path) where T : UnityEngine.Object
{
    return Resources.Load<T>(path);
}
  • T:需要加载的资源类型。
  • path:资源相对于 Resources 文件夹的路径。
  • 返回值:加载到的资源对象。
    调用示例:
csharp 复制代码
GameObject prefab = ResMgr.Instance.Load<GameObject>("Test");
GameObject obj = Object.Instantiate(prefab);

Load() 只负责加载资源,Instantiate() 才会在场景中创建实例。

三、泛型异步加载

外部调用方法:

csharp 复制代码
public void LoadAsync<T>(string path,UnityAction<T> callBack)
    where T : UnityEngine.Object
{
    MonoMgr.Instance.StartCoroutine(ReallyLoadAsync(path, callBack));
}

真正执行异步加载的协程:

csharp 复制代码
private IEnumerator ReallyLoadAsync<T>(string path,UnityAction<T> callBack)
    where T : UnityEngine.Object
{
    ResourceRequest request = Resources.LoadAsync<T>(path);
    yield return request;
    callBack?.Invoke(request.asset as T);
}

执行流程:

text 复制代码
调用 LoadAsync<T>()
        ↓
MonoMgr 开启协程
        ↓
Resources.LoadAsync<T>()
        ↓
等待 ResourceRequest完成
        ↓
通过回调返回加载结果

调用示例:

csharp 复制代码
ResMgr.Instance.LoadAsync<GameObject>("Test", prefab =>
{
    if (prefab != null)
    {
        Instantiate(prefab);
    }
});

四、Type 异步加载

不知道具体泛型类型,或者类型需要在运行时传入时,可以使用 Type

csharp 复制代码
public void LoadAsync(
    string path,
    System.Type type,
    UnityAction<UnityEngine.Object> callBack)
{
    MonoMgr.Instance.StartCoroutine(
        ReallyLoadAsync(path, type, callBack)
    );
}
csharp 复制代码
private IEnumerator ReallyLoadAsync(
    string path,
    System.Type type,
    UnityAction<UnityEngine.Object> callBack)
{
    ResourceRequest request = Resources.LoadAsync(path, type);

    yield return request;

    callBack?.Invoke(request.asset);
}

调用后得到的是 UnityEngine.Object,使用前需要转换类型:

csharp 复制代码
ResMgr.Instance.LoadAsync(
    "Test",
    typeof(GameObject),
    asset =>
    {
        GameObject prefab = asset as GameObject;

        if (prefab != null)
        {
            Instantiate(prefab);
        }
    }
);

五、两种异步加载的区别

方式 回调结果 特点
LoadAsync<T>() 指定类型 T 类型明确,使用方便
LoadAsync(path, type) UnityEngine.Object 类型可在运行时决定,使用前需要转换

六、卸载指定资源

csharp 复制代码
public void UnloadAsset(UnityEngine.Object assetToUnload)
{
    if (assetToUnload != null)
    {
        Resources.UnloadAsset(assetToUnload);
    }
}

Resources.UnloadAsset() 用于卸载单个资源,例如纹理、材质或音频资源;不能用它卸载场景实例、GameObjectComponentAssetBundle。场景中通过 Instantiate() 创建出的实例,应使用:

csharp 复制代码
Destroy(gameObject);

不能使用 Resources.UnloadAsset() 代替实例销毁。

七、卸载未使用资源

csharp 复制代码
public void UnloadUnusedAssets(UnityAction callBack = null)
{
    MonoMgr.Instance.StartCoroutine(
        ReallyUnloadUnusedAssets(callBack)
    );
}
csharp 复制代码
private IEnumerator ReallyUnloadUnusedAssets(UnityAction callBack)
{
    AsyncOperation operation = Resources.UnloadUnusedAssets();

    yield return operation;

    callBack?.Invoke();
}

执行过程:

text 复制代码
Resources.UnloadUnusedAssets()
        ↓
异步清理当前未使用的 Resources 资源
        ↓
清理完成后执行回调

该操作不应频繁调用,通常在切换场景或确定需要集中清理资源时使用。

八、使用注意事项

  1. 资源必须放在名为 Resources 的文件夹中。
  2. 路径相对于 Resources 文件夹,不写文件扩展名。
  3. 子文件夹路径使用 /,例如:
csharp 复制代码
"Prefabs/Player"
  1. 异步加载结果可能为 null,使用前应判空。
  2. ResMgr 不是 MonoBehaviour,必须通过 MonoMgr 开启协程。

九、完整代码

  1. ResMgr.cs

    using System;
    using System.Collections;
    using UnityEngine;
    using UnityEngine.Events;

    ///


    /// Resources 资源加载管理器。
    ///

    public class ResMgr : BaseManager
    {
    private ResMgr()
    {
    }

    复制代码
     /// <summary>
     /// 同步加载资源。
     /// </summary>
     /// <typeparam name="T">资源类型。</typeparam>
     /// <param name="path">Resources 文件夹下的相对路径。</param>
     public T Load<T>(string path)
         where T : UnityEngine.Object
     {
         return Resources.Load<T>(path);
     }
    
     /// <summary>
     /// 使用泛型异步加载资源。
     /// </summary>
     public void LoadAsync<T>(
         string path,
         UnityAction<T> callBack)
         where T : UnityEngine.Object
     {
         MonoMgr.Instance.StartCoroutine(
             ReallyLoadAsync(path, callBack)
         );
     }
    
     /// <summary>
     /// 真正执行泛型异步加载的协程。
     /// </summary>
     private IEnumerator ReallyLoadAsync<T>(
         string path,
         UnityAction<T> callBack)
         where T : UnityEngine.Object
     {
         ResourceRequest request =
             Resources.LoadAsync<T>(path);
    
         yield return request;
    
         callBack?.Invoke(request.asset as T);
     }
    
     /// <summary>
     /// 根据 Type 异步加载资源。
     /// </summary>
     public void LoadAsync(
         string path,
         Type type,
         UnityAction<UnityEngine.Object> callBack)
     {
         MonoMgr.Instance.StartCoroutine(
             ReallyLoadAsync(path, type, callBack)
         );
     }
    
     /// <summary>
     /// 真正执行 Type 异步加载的协程。
     /// </summary>
     private IEnumerator ReallyLoadAsync(
         string path,
         Type type,
         UnityAction<UnityEngine.Object> callBack)
     {
         ResourceRequest request =
             Resources.LoadAsync(path, type);
    
         yield return request;
    
         callBack?.Invoke(request.asset);
     }
    
     /// <summary>
     /// 卸载指定的单个资源。
     /// 不能用于销毁场景中的 GameObject 实例。
     /// </summary>
     public void UnloadAsset(
         UnityEngine.Object assetToUnload)
     {
         if (assetToUnload == null)
             return;
    
         Resources.UnloadAsset(assetToUnload);
     }
    
     /// <summary>
     /// 异步卸载当前没有使用的资源。
     /// </summary>
     ///`Resources.UnloadAsset()` 主要用于卸载资源本身,不是拿来直接销毁场景里的 GameObject
     public void UnloadUnusedAssets(
         UnityAction callBack = null)
     {
         MonoMgr.Instance.StartCoroutine(
             ReallyUnloadUnusedAssets(callBack)
         );
     }
    
     /// <summary>
     /// 真正执行资源清理的协程。
     /// </summary>
     ///> 开始异步卸载当前没有被任何地方引用的资源。`UnloadUnusedAssets()` 不需要你指定某个资源。Unity 会自己检查哪些资源已经没人用了,然后回收它们。
     private IEnumerator ReallyUnloadUnusedAssets(
         UnityAction callBack)
     {
         AsyncOperation operation =
             Resources.UnloadUnusedAssets();
    
         yield return operation;
    
         callBack?.Invoke();
     }

    }

  2. Main.cs

    using UnityEngine;

    public class Main : MonoBehaviour
    {
    private GameObject currentObj;

    复制代码
     private void Start()
     {
         // 泛型异步加载
         ResMgr.Instance.LoadAsync<GameObject>(
             "Test",
             prefab =>
             {
                 if (prefab == null)
                 {
                     Debug.LogError(
                         "Resources 中没有找到 Test"
                     );
                     return;
                 }
    
                 currentObj = Instantiate(prefab);
             }
         );
    
         // Type 异步加载
         ResMgr.Instance.LoadAsync(
             "Test",
             typeof(GameObject),
             asset =>
             {
                 GameObject prefab = asset as GameObject;
    
                 if (prefab == null)
                 {
                     Debug.LogError(
                         "加载结果不是 GameObject"
                     );
                     return;
                 }
    
                 Instantiate(prefab);
             }
         );
     }
    
     private void Update()
     {
         // 按下空格测试同步加载
         if (Input.GetKeyDown(KeyCode.Space))
         {
             GameObject prefab =
                 ResMgr.Instance.Load<GameObject>("Test");
    
             if (prefab != null)
             {
                 Instantiate(prefab);
             }
         }
    
         // 按下 U 清理未使用资源
         if (Input.GetKeyDown(KeyCode.U))
         {
             ResMgr.Instance.UnloadUnusedAssets(() =>
             {
                 Debug.Log("未使用资源清理完成");
             });
         }
    
         // 按下 D 销毁场景实例
         if (Input.GetKeyDown(KeyCode.D) &&
             currentObj != null)
         {
             Destroy(currentObj);
             currentObj = null;
         }
     }

    }

相关推荐
李少兄1 小时前
Java 中只有值传递吗?
java·开发语言·python
张人玉2 小时前
基于 C# / .NET 8 + Windows Forms + SQLite 的桌面银行业务演示系统——华夏示范银行管理系统
windows·sqlite·c#·.net
可乐鸡翅yeah_2 小时前
hls.js 播放质量埋点实战,采集卡顿、起播、错误指标定位线上用户问题
开发语言·前端·javascript·ecmascript·音视频·m3u8·音视频在线播放
1314lay_10072 小时前
C#调用Sql Server存储过程,并且使用传入表结构
数据库·经验分享·笔记·sqlserver·c#
传奇开心果编程2 小时前
【Xilem 0.4 基础语法学与练】第二课视图修饰符、列表渲染、组件拆分、memoize 性能优化
学习·rust·前端框架
szarron2 小时前
RF Demo Kit|NanoVNA 射频演示测试板完整上手教程,滤波器、衰减器、SOLT 校准学习板
开发语言·人工智能·学习·php·射频工程·频谱仪
边境悍匪2 小时前
蜗牛学苑 Java 智能体学习 Day39|SpringAI 会话存储、Function‑Call 工具调用、MCP 思维导图复盘
java·开发语言·spring boot·学习·阿里云
慧都小妮子2 小时前
.NET 报表控件选型对比:Stimulsoft 与 FastReport 怎么选
c#·.net·报表开发·数据可视化·报表控件·报表引擎选型
万物智能信息科技3 小时前
RK3568 的多路显示移植—【万物智能之开源鸿蒙OpenHarmony系统实战开发系列教程】
linux·开发语言·华为·开源·harmonyos