Unity基础学习(九)Resources资源同步与异步加载

        为什么需要进行资源加载,是避免直接将资源模型内容,直接进行拖拽,导致管理混乱。通过代码进行统一管理,自动化配置。

一、什么是Resources资源加载

定义
        Resources资源加载是Unity提供的一种动态加载资源的方式,允许通过代码从项目中标记为"Resources"的特殊文件夹中加载资源。这些资源在打包时会统一合并处理,无需通过拖拽方式手动赋值。

Resources特点:

1、必须将资源存放在Resources文件夹(可嵌套在子文件夹)
2、允许存在多个Resources文件夹,通过API加载时候 他会自己去每个文件夹寻找资源
3、所有Resources文件夹内容会被合并打包成一个总目录
4、通过路径名访问,无需后缀名(如.prefab)

二、有哪些资源加载方式

主要分为三种形式:
同步加载(立即加载)
Resources.Load():直接阻塞主线程直到加载完成
适用场景:小资源或初始化阶段

异步加载(后台加载)
Resources.LoadAsync():不阻塞主线程
适用场景:大资源或需要优化流畅性时

批量加载
Resources.LoadAll():加载指定路径下所有资源

三、怎么进行资源同步加载

核心API
// 泛型方法(推荐)
T Resources.Load<T>(string path) where T : Object;

// 非泛型方法
Object Resources.Load(string path, System.Type type);

加载各种对象的示例:

1. 加载预设体

// 加载预设体资源(未实例化)
GameObject prefab = Resources.Load<GameObject>("Cube");
// 实例化到场景中
Instantiate(prefab);

注意:

(1)加载的预设体只是配置数据,需通过Instantiate生成实例
(2)假设Cube.prefab位于Resources/Prefabs/Cube,则路径为"Prefabs/Cube" 

2. 加载音频

AudioClip clip = Resources.Load<AudioClip>("Music/BkMusic");
audioSource.clip = clip;
audioSource.Play();

直接使用:音频资源加载后可直接赋值给AudioSource组件 ,同样需要注意路径

3. 加载文本

TextAsset textAsset = Resources.Load<TextAsset>("Txt/Text");
Debug.Log(textAsset.text); // 获取文本内容

(1)支持txt/.xml/.json等文本格式
(2)通过text属性获取字符串,bytes获取二进制数据 

(同样仍需注意路径)

4. 加载图片

Texture texture = Resources.Load<Texture>("Picture/4");
// GUI显示示例
void OnGUI() {
    GUI.DrawTexture(new Rect(0,0,100,100), texture);
}

类型匹配:确保加载类型与资源类型一致(Texture2D/Sprite等) 

(同样仍需注意路径)

5. 处理同名资源

// 指定类型加载
Texture tex = Resources.Load("Picture/4", typeof(Texture)) as Texture;

// 加载所有同名资源
Object[] all = Resources.LoadAll("Picture/4");
foreach(Object obj in all){
    if(obj is Texture2D) {
        // 使用Texture2D资源
    }
}

 (同样仍需注意路径)

四、怎么进行异步加载

方式1:通过事件回调异步加载

using UnityEngine;

public class AsyncLoadExample_Event : MonoBehaviour
{
    private Texture2D loadedTexture;

    void Start()
    {
        // 开始异步加载
        ResourceRequest request = Resources.LoadAsync<Texture2D>("Textures/MyTexture");
        
        // 注册完成事件
        request.completed += OnLoadCompleted;
    }

    // 加载完成回调
    private void OnLoadCompleted(AsyncOperation operation)
    {
        // 类型转换
        ResourceRequest request = operation as ResourceRequest;
        
        // 错误检查
        if (request.asset == null)
        {
            Debug.LogError("加载失败,请检查路径或资源是否存在");
            return;
        }

        // 获取资源
        loadedTexture = request.asset as Texture2D;
        Debug.Log("异步加载完成,纹理尺寸:" + loadedTexture.width + "x" + loadedTexture.height);

        // 使用资源(示例:在屏幕上绘制)
        // 注意:实际使用时应通过Material或UI组件显示
    }

    void OnGUI()
    {
        if (loadedTexture != null)
        {
            GUI.DrawTexture(new Rect(10, 10, 100, 100), loadedTexture);
        }
    }

    void OnDestroy()
    {
        // 手动释放资源
        if (loadedTexture != null)
        {
            Resources.UnloadAsset(loadedTexture);
        }
    }
}

有几个关键注意点:

(1)必须是将需要加载的资源放在一个全局变量中,即是类的成员变量,而非函数的成员变量。

(2) 进行事件监听时,必须是ResourceRequest 类型的变量中completed才具备表示事件完成后,需要执行的操作。

(3)切记将AsyncOperation 转换为实际上使用的ResourceRequest 委托。

(4)切记通过request.asset as Texture2D这样来进行实际转换。

具体原理:

        Unity的资源异步加载通过事件回调机制实现,其核心原理围绕主线程与后台线程的协同工作展开。整个过程始于开发者调用Resources.LoadAsync方法发起异步加载请求,此时Unity会创建一个ResourceRequest对象作为加载任务的核心载体。该对象不仅记录了资源路径、加载进度等元信息,更重要的是维护了一个完成事件completed的委托链,这是回调机制的关键枢纽。
       当加载请求启动后,Unity的内部线程管理系统会将实际加载工作分配到后台线程执行。后台线程首先根据资源路径定位具体的物理文件(需确保资源位于项目的Resources目录或子目录下),随后执行文件读取、数据解码和资源反序列化等耗时操作。这一阶段完全独立于主线程运行,避免了主线程的阻塞。值得注意的是,不同类型的资源(如纹理、音频、预制体等)在此阶段会经历不同的处理流程,例如纹理需要解析图像格式,音频文件需进行解码采样,预制体则涉及组件结构的重建。
        后台线程完成资源处理后,Unity通过内部的消息队列机制将加载结果传递回主线程。这一跨线程通信过程采用线程安全的数据交换策略,通常涉及双重缓冲或原子操作来确保数据一致性。当主线程在下一帧的更新周期中检测到加载任务已完成时,会遍历ResourceRequest对象的completed事件列表,逐个触发注册的回调方法。值得注意的是,所有回调都是在主线程上下文中执行的,这使得我们可以直接在回调中安全调用Unity的API(如实例化游戏对象、修改组件属性等),无需担心跨线程访问的问题。
       事件回调的执行过程伴随着类型转换和错误处理的关键步骤。由于ResourceRequest的asset属性返回的是通用的UnityEngine.Object类型,我们需要通过as关键字将其转换为具体的资源类型(如Texture2D或AudioClip)。这种显式转换不仅确保了类型安全,也为错误检测提供了契机——当资源路径错误或类型不匹配时,转换结果会为null。所以我们可以进行错误检测。

方式2:通过协程异步加载

using UnityEngine;
using System.Collections;

public class ResourceLoader : MonoBehaviour
{
    [Header("加载配置")]
    public string texturePath = "Textures/MyTexture"; // 图片资源路径
    public string prefabPath = "Prefabs/MyPrefab";    // 预制体路径

    private Texture2D loadedTexture;
    private GameObject loadedPrefab;

    void Start()
    {
        // 显式启动协程
        StartCoroutine(LoadResources());
    }

    IEnumerator LoadResources()
    {
        // 加载纹理
        yield return StartCoroutine(LoadTexture(texturePath));

        // 加载预制体(等待纹理加载完成后执行)
        yield return StartCoroutine(LoadPrefab(prefabPath));

        // 所有资源加载完成后初始化
        InitializeGame();
    }

    IEnumerator LoadTexture(string path)
    {
        ResourceRequest request = Resources.LoadAsync<Texture2D>(path);
        
        while (!request.isDone)
        {
            Debug.Log($"正在加载纹理 [{path}] 进度:{request.progress:P0}");
            yield return null;
        }

        if (request.asset == null)
        {
            Debug.LogError($"纹理加载失败:{path}");
            yield break;
        }

        loadedTexture = request.asset as Texture2D;
        Debug.Log($"纹理加载完成:{loadedTexture.name} ({loadedTexture.width}x{loadedTexture.height})");
    }

    IEnumerator LoadPrefab(string path)
    {
        ResourceRequest request = Resources.LoadAsync<GameObject>(path);
        
        while (!request.isDone)
        {
            Debug.Log($"正在加载预制体 [{path}] 进度:{request.progress:P0}");
            yield return null;
        }

        if (request.asset == null)
        {
            Debug.LogError($"预制体加载失败:{path}");
            yield break;
        }

        loadedPrefab = request.asset as GameObject;
        Debug.Log($"预制体加载完成:{loadedPrefab.name}");
    }

    void InitializeGame()
    {
        // 实例化预制体
        if (loadedPrefab != null)
        {
            GameObject instance = Instantiate(loadedPrefab);
            Debug.Log($"实例化对象:{instance.name}");
        }

        // 使用纹理(示例:创建材质球)
        if (loadedTexture != null)
        {
            Material mat = new Material(Shader.Find("Standard"));
            mat.mainTexture = loadedTexture;
            Debug.Log($"创建材质球:{mat.name}");
        }
    }

    void OnDestroy()
    {
        // 释放资源
        if (loadedTexture != null) Resources.UnloadAsset(loadedTexture);
        if (loadedPrefab != null) Resources.UnloadAsset(loadedPrefab);
        Resources.UnloadUnusedAssets();
    }
}

五、怎么进行资源卸载

提问:当反复加载资源时,会重复消耗更多的内存吗?

回答:不是的,如果已经加载到内存时,就不会再反复加载相同的内容了。但是会消耗CPU的计算。

1. 指定资源卸载 Resources.UnloadAsset

适用对象:

纹理(Texture) ✅
音频(AudioClip) ✅
文本(TextAsset) ✅
材质(Material) ✅
不可用:预制体/GameObject ❌

void Update()
{
    if(Input.GetKeyDown(KeyCode.A))
    {
        // 加载到内存缓存
        text = Resources.Load<Texture>("Pic");
    }
    
    if(Input.GetKeyUp(KeyCode.A))
    {
        // 从缓存中移除
        Resources.UnloadAsset(text);
        // 必须解除引用
        text = null; 
    }
}

注意:

(1)一个是路径必须正确

(2)二个是释放完要解除引用,不然就内存泄露了。 

2. 批量卸载 Resources.UnloadUnusedAssets

        扫描内存中未被任何MonoBehaviour引用的资源

例如切换场景:

IEnumerator SwitchScene()
{
    // 卸载当前场景资源
    Resources.UnloadUnusedAssets();
    System.GC.Collect();
    
    // 等待清理完成
    yield return new WaitForEndOfFrame();
    
    // 加载新场景
    SceneManager.LoadScene("NewScene");
}

 七、总结核心API与属性速查表

属性/方法输入参数输出类型说明与示例
Resources.Load<T>path (string)T同步加载泛型方法
Texture tex = Resources.Load<Texture>("Textures/Icon");
Resources.Loadpath (string), type (Type)Object同步加载指定类型
Object obj = Resources.Load("Audio/Sound", typeof(AudioClip));
Resources.LoadAsync<T>path (string)ResourceRequest异步加载资源
ResourceRequest req = Resources.LoadAsync<GameObject>("Prefabs/Player");
Resources.LoadAllpath (string)T[]加载目录下所有资源
TextAsset[] texts = Resources.LoadAll<TextAsset>("Configs");
Resources.UnloadAssetasset (Object)void卸载非GameObject资源
Resources.UnloadAsset(loadedTexture);
Resources.UnloadUnusedAssetsAsyncOperation清理未引用资源
Resources.UnloadUnusedAssets();
Resources.FindObjectsOfTypeAlltype (Type)Object[]获取所有指定类型对象
Material[] mats = Resources.FindObjectsOfTypeAll<Material>();
Resources.GetBuiltinResourcepath (string)T获取内置资源
Material mat = Resources.GetBuiltinResource<Material>("Default-Diffuse.mat");

ResourceRequest关键属性

属性类型说明
isDonebool加载是否完成
if(request.isDone) { /* ... */ }
progressfloat加载进度(0~1)
Debug.Log(request.progress);
assetObject加载完成的资源
Texture tex = request.asset as Texture;

路径规则与注意事项

类别规则
路径格式不包含Resources文件夹名和文件扩展名
示例:"UI/Icons/Sword"对应Resources/UI/Icons/Sword.png
平台差异移动端路径大小写敏感,PC/Mac不敏感
内存特性首次加载后资源常驻内存缓存,重复加载无内存开销

卸载策略对照表

方法适用场景限制
UnloadAsset释放特定非实例化资源不可用于GameObject
UnloadUnusedAssets场景切换时批量清理需配合GC.Collect()使用

异步加载方式对比

方式优点缺点
事件回调代码简洁无法跟踪进度
协程支持进度监控需处理协程嵌套

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

FAREWELL00075

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值