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.Load | path (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.LoadAll | path (string) | T[] | 加载目录下所有资源TextAsset[] texts = Resources.LoadAll<TextAsset>("Configs"); |
Resources.UnloadAsset | asset (Object) | void | 卸载非GameObject资源Resources.UnloadAsset(loadedTexture); |
Resources.UnloadUnusedAssets | 无 | AsyncOperation | 清理未引用资源Resources.UnloadUnusedAssets(); |
Resources.FindObjectsOfTypeAll | type (Type) | Object[] | 获取所有指定类型对象Material[] mats = Resources.FindObjectsOfTypeAll<Material>(); |
Resources.GetBuiltinResource | path (string) | T | 获取内置资源Material mat = Resources.GetBuiltinResource<Material>("Default-Diffuse.mat"); |
ResourceRequest关键属性
属性 | 类型 | 说明 |
---|---|---|
isDone | bool | 加载是否完成if(request.isDone) { /* ... */ } |
progress | float | 加载进度(0~1)Debug.Log(request.progress); |
asset | Object | 加载完成的资源Texture tex = request.asset as Texture; |
路径规则与注意事项
类别 | 规则 |
---|---|
路径格式 | 不包含Resources 文件夹名和文件扩展名示例: "UI/Icons/Sword" 对应Resources/UI/Icons/Sword.png |
平台差异 | 移动端路径大小写敏感,PC/Mac不敏感 |
内存特性 | 首次加载后资源常驻内存缓存,重复加载无内存开销 |
卸载策略对照表
方法 | 适用场景 | 限制 |
---|---|---|
UnloadAsset | 释放特定非实例化资源 | 不可用于GameObject |
UnloadUnusedAssets | 场景切换时批量清理 | 需配合GC.Collect() 使用 |
异步加载方式对比
方式 | 优点 | 缺点 |
---|---|---|
事件回调 | 代码简洁 | 无法跟踪进度 |
协程 | 支持进度监控 | 需处理协程嵌套 |