Unity基础-Resources资源动态加载
Unity基础-Resources资源动态加载
五、Resources资源动态加载
1. 特殊文件夹
在Unity中,有几个特殊的文件夹用于不同的用途:
- 工程路径(Assets)
// 该方法获取到的路径只会在编辑模式下使用,游戏开发过后路径就不存在了
print(Application.dataPath);
- Resources资源文件夹
// 一般不会获取,只能使用Resources相关API加载,如果非要获取,使用工程路径拼接
print(Application.dataPath+"/Resources");
- 需要我们自己创建
- 作用:资源文件夹
- 需要通过Resources相关API动态加载的资源需要放在其中
- 该文件夹下的所有文件都会打包出去
- 打包时Unity会对其进行压缩加密
- 该文件夹打包后只读
- StreamingAssets流动资源文件夹
// 路径获取
print(Application.streamingAssetsPath);
- 需要我们自己创建
- 作用:流动文件夹
- 打包出去不会压缩加密,任由我们摆布
- 移动平台只读,PC平台可读可写
- 可以放入一些需要自定义动态加载的初始资源
- persistentDataPath持久数据文件夹
// 路径获取
print(Application.persistentDataPath);
- 不需要我们自己创建
- 作用:固定数据文件夹
- 所有平台可读可写
- 一般用于放置动态下载或者动态创建的文件,游戏中创建或者获取的文件都放在其中
- 与游戏的热更新息息相关
- Plugins插件文件夹
- 路径获取:一般不获取
- 不同平台的插件文件放在其中,例如Android和Ios
- Editor编辑器文件夹
- 路径获取:一般不获取
- 需要我们自己创建
- 开发Unity编辑器时,编辑器相关脚本放在该文件夹中
- 该文件夹内容不会被打包出去
- StandardAssests默认资源文件夹
- 需要我们自己创建
- 一般Unity自带资源都放在这个文件夹下,代码和资源优先被编译
2. Resources资源动态加载
通过代码动态加载Resources文件夹指定路径下的资源,可以避免复杂的拖拽操作。
2.1 常用资源类型
- 预设体对象—GameObject
- 音效文件—AudioClip
- 文本文件—TextAsset
- 图片文件—Texture
注意:资源加载后一定要使用!预制体对象加载后一定要实例化!
2.2 资源加载方法
// 在一个工程当中,Resources文件夹可以有多个,通过API加载时,他会自己去这些同名的文件夹中去找资源,打包时所有文件加内容会打包到一起// 1. 预设体对象
GameObject obj1 = Resources.Load("资源路径(如果有嵌套加/)");
Instantiate(obj);// 2. 音效资源
GameObject obj2 = Resources.Load("") as AudioClip;
audioS.clip = obj2;
audioS.Play();// 3. 文本资源
// 文本资源支持的格式: .txt .xml .bytes .json .html .csv
TextAsset ta = Resources.Load("") as TextAsset;
print(ta); // 4. 图片
Texture tex = Resources.Load("") as Texture;private void OnGUI()
{// 在屏幕上绘制一个纹理(图片)// new Rect(0,0,100,100) 定义了纹理的绘制区域,即从屏幕左上角(0,0)开始,宽度为100,高度为100的矩形GUI.DrawTexture(new Rect(0,0,100,100), tex);
}// 5. 加载指定类型的资源
Texture tex = Resources.Load("", typeof(Texture)) as Texture;// 6. 加载指定名字的所有资源
Object[] objs = Resources.LoadAll("");
foreach(Object item in objs)
{if(item is Texture){}else if(item is TextAsset){}
}// 7. 使用泛型加载
// 避免使用 of 关键字
TextAsset ta = Resources.Load<TextAsset>("");
3. Resources资源异步加载
如果我们加载过大的资源可能会造成程序卡顿。卡顿的原因是从硬盘把数据读取到内存中是需要进行计算的,越大的资源耗时越长,会造成掉帧卡顿的现象。Resources异步加载就是在内部新开一个线程进行资源加载,不会造成主线程卡顿。
注意:异步加载不能马上得到资源,至少要相隔一帧。
3.1 异步加载的两种方法
- 通过异步加载完成事件监听
private TextAsset textAsset;
void Start()
{// 使用Unity中给的异步加载方法ResourceRequest rq = Resources.LoadAsync<TextAsset>("Text");// 监听加载事件结束rq.completed += Load;// 一定要等加载结束过后才能使用
}// 定义的监听函数要符合事件签名
private void Load(AsyncOperation asyncOperetion)
{// asset是资源对象,加载完毕就能得到textAsset = (asyncOperetion as ResourceRequest).asset as TextAsset;print(textAsset);
}
- 通过协程
void Start()
{StartCoroutine(enumaretor());
}IEnumerator enumaretor()
{ResourceRequest rq = Resources.LoadAsync<TextAsset>("Text");// Unity中规定如果返回一个ResourceRequest类型的值,Unity会识别出正在异步加载// 下面这条语句的意思是加载完向后执行yield return rq;textAsset = rq.asset as TextAsset;print(textAsset);// 下面是RecourceRequest中的其他成员变量和属性// 每帧打印异步加载进度while(!rq.isDone){print(rq.priority);yield return null;}
}
3.2 两种方法的区别
-
事件监听异步加载
- 好处:写法简单
- 坏处:只能在资源加载结束后进行处理,更像是"线性加载"
-
协程异步加载
- 好处:可以在协程中处理复杂逻辑,比如同时加载多个资源,比如进度条更新
- 坏处:写法麻烦
4. Resources资源卸载
4.1 资源缓存机制
Resources加载一次资源过后该资源就一直放在内存中作为缓存,第二次加载时发现缓存中存在该资源会取出来直接使用,所以多次重复加载不会浪费资源,但会浪费性能,每次加载伴随着查找。
4.2 手动释放资源
// 卸载指定资源
Resources.UnloadAsset("资源路径");// 卸载未使用的资源
// 一般在换场景中和GC一起使用
Resources.UnloadUnusedAssets();
GC.Collect();
注意:Resources.UnloadAsset方法不能释放GameObject类型对象,因为他会用于实例化对象,他只能用于不需要实例化的内容,比如图片、音效、文本等。
4.3 GC(垃圾回收)机制
GC (Garbage Collection) 是垃圾回收的缩写,是.NET运行时环境中的自动内存管理机制。
GC的基本概念
- GC是自动内存管理机制,用于自动回收不再使用的内存
- 在C#中,GC是.NET运行时环境的一部分
- 它负责跟踪和释放不再被程序使用的对象所占用的内存
GC的工作原理
- 标记阶段:GC会标记所有仍然被引用的对象
- 清除阶段:删除所有未被标记的对象
- 压缩阶段:整理内存碎片,使可用内存连续
GC的触发时机
- 当系统内存不足时
- 当分配新对象时,如果可用内存不足
- 当程序显式调用
GC.Collect()
时
GC的优缺点
优点:
- 自动管理内存,减少内存泄漏
- 程序员不需要手动释放内存
- 简化了内存管理
缺点:
- 可能造成程序暂停(GC暂停)
- 内存使用效率可能不如手动管理
- 无法精确控制内存释放的时机
减少GC压力的最佳实践
- 使用对象池重用对象
- 避免在频繁调用的方法中创建新对象
- 使用值类型(struct)代替引用类型(class)
- 缓存频繁使用的对象
- 使用
StringBuilder
代替字符串拼接 - 避免使用
foreach
循环(在Unity中) - 使用
List<T>
时预分配容量
在Unity中的特殊考虑
- Unity的GC是分代的
- 移动平台对GC更敏感
- 可以使用Unity Profiler监控GC行为
- 在关键性能点可以手动触发GC
5. 资源管理器实现
下面是一个简单的资源管理器实现,提供统一的方法给外部用于资源异步加载:
/// <summary>
/// 资源管理器类,用于统一管理Unity中Resources文件夹的异步资源加载
/// 使用单例模式确保全局只有一个资源管理器实例
/// </summary>
public class ResourcesManager
{private static ResourcesManager instance = new ResourcesManager();// 公共静态属性,提供对ResourcesManager唯一实例的全局访问点// 使用Lambda表达式简化属性定义public static ResourcesManager Instance => instance;/// <summary>/// 泛型方法,用于异步加载Resources文件夹中的指定资源/// </summary>/// <typeparam name="T">要加载的资源类型,必须是UnityEngine.Object或其派生类</typeparam>/// <param name="name">资源在Resources文件夹中的路径/名称(不包含文件扩展名)</param>/// <param name="unityAction">资源加载完成后的回调委托,会将加载到的资源(类型为T)作为参数传入</param>public void LoadRes<T>(string name, UnityAction<T> unityAction) where T : Object{// 调用Unity内置的Resources.LoadAsync方法,开始异步加载指定路径的资源// ResourceRequest对象是异步加载操作的句柄,可以用来查询进度或等待完成ResourceRequest rq = Resources.LoadAsync(name);// 为ResourceRequest的completed事件添加一个匿名回调函数// 当异步加载操作完成时,这个lambda表达式会被调用rq.completed += (AsyncOperation a) => {// 'a' 参数是AsyncOperation类型,需要将其转换为ResourceRequest// 然后访问其asset属性(类型为UnityEngine.Object),再将其安全地转换为T类型// 最后,调用传入的unityAction回调,并传入加载到的资源unityAction((a as ResourceRequest).asset as T);};}
}
使用示例:
/// <summary>
/// 示例类,展示如何使用ResourcesManager进行资源加载
/// </summary>
public class Example : MonoBehaviour
{// 用于存储加载的文本资源private TextAsset textAsset;void Start(){// 调用ResourcesManager单例的LoadRes方法异步加载资源// <TextAsset> 指定了要加载的资源类型为TextAsset// "Text" 是Resources文件夹中资源的名称(例如:Assets/Resources/Text.txt)// (asset) => { ... } 是一个lambda表达式,作为资源加载完成后的回调函数ResourcesManager.Instance.LoadRes<TextAsset>("Text", (asset) => {// 在资源加载完成后,将加载到的asset(此时是Object类型)安全地转换为TextAsset类型textAsset = asset;// 打印加载到的TextAsset对象,通常会显示其名称和类型print("异步加载完成,加载到的TextAsset是: " + textAsset.name);// 检查textAsset是否成功加载,并打印其内容if (textAsset != null){print("文本内容:\n" + textAsset.text);}});}
}
这个资源管理器的主要优点是:
- 使用单例模式,方便全局访问
- 支持异步加载,不会阻塞主线程
- 使用泛型,可以加载任何类型的Unity资源
- 使用Lambda表达式简化了回调处理
- 代码简洁且易于使用