Unity基础-Resources资源动态加载

Unity基础-Resources资源动态加载

五、Resources资源动态加载

1. 特殊文件夹

在Unity中,有几个特殊的文件夹用于不同的用途:

  1. 工程路径(Assets)
// 该方法获取到的路径只会在编辑模式下使用,游戏开发过后路径就不存在了
print(Application.dataPath);
  1. Resources资源文件夹
// 一般不会获取,只能使用Resources相关API加载,如果非要获取,使用工程路径拼接
print(Application.dataPath+"/Resources");
  • 需要我们自己创建
  • 作用:资源文件夹
  • 需要通过Resources相关API动态加载的资源需要放在其中
  • 该文件夹下的所有文件都会打包出去
  • 打包时Unity会对其进行压缩加密
  • 该文件夹打包后只读
  1. StreamingAssets流动资源文件夹
// 路径获取
print(Application.streamingAssetsPath);
  • 需要我们自己创建
  • 作用:流动文件夹
  • 打包出去不会压缩加密,任由我们摆布
  • 移动平台只读,PC平台可读可写
  • 可以放入一些需要自定义动态加载的初始资源
  1. persistentDataPath持久数据文件夹
// 路径获取
print(Application.persistentDataPath);
  • 不需要我们自己创建
  • 作用:固定数据文件夹
  • 所有平台可读可写
  • 一般用于放置动态下载或者动态创建的文件,游戏中创建或者获取的文件都放在其中
  • 与游戏的热更新息息相关
  1. Plugins插件文件夹
  • 路径获取:一般不获取
  • 不同平台的插件文件放在其中,例如Android和Ios
  1. Editor编辑器文件夹
  • 路径获取:一般不获取
  • 需要我们自己创建
  • 开发Unity编辑器时,编辑器相关脚本放在该文件夹中
  • 该文件夹内容不会被打包出去
  1. 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 异步加载的两种方法
  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);
}
  1. 通过协程
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 两种方法的区别
  1. 事件监听异步加载

    • 好处:写法简单
    • 坏处:只能在资源加载结束后进行处理,更像是"线性加载"
  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的工作原理
  1. 标记阶段:GC会标记所有仍然被引用的对象
  2. 清除阶段:删除所有未被标记的对象
  3. 压缩阶段:整理内存碎片,使可用内存连续
GC的触发时机
  • 当系统内存不足时
  • 当分配新对象时,如果可用内存不足
  • 当程序显式调用 GC.Collect()
GC的优缺点

优点:

  • 自动管理内存,减少内存泄漏
  • 程序员不需要手动释放内存
  • 简化了内存管理

缺点:

  • 可能造成程序暂停(GC暂停)
  • 内存使用效率可能不如手动管理
  • 无法精确控制内存释放的时机
减少GC压力的最佳实践
  1. 使用对象池重用对象
  2. 避免在频繁调用的方法中创建新对象
  3. 使用值类型(struct)代替引用类型(class)
  4. 缓存频繁使用的对象
  5. 使用 StringBuilder 代替字符串拼接
  6. 避免使用 foreach 循环(在Unity中)
  7. 使用 List 时预分配容量
在Unity中的特殊考虑
  • Unity的GC是分代的
  • 移动平台对GC更敏感
  • 可以使用Unity Profiler监控GC行为
  • 在关键性能点可以手动触发GC

5. 资源管理器实现

下面是一个简单的资源管理器实现,提供统一的方法给外部用于资源异步加载:

/// 
/// 资源管理器类,用于统一管理Unity中Resources文件夹的异步资源加载
/// 使用单例模式确保全局只有一个资源管理器实例
/// 
public class ResourcesManager
{
    private static ResourcesManager instance = new ResourcesManager();
    // 公共静态属性,提供对ResourcesManager唯一实例的全局访问点
    // 使用Lambda表达式简化属性定义
    public static ResourcesManager Instance => instance;

    /// 
    /// 泛型方法,用于异步加载Resources文件夹中的指定资源
    /// 
    /// 要加载的资源类型,必须是UnityEngine.Object或其派生类
    /// 资源在Resources文件夹中的路径/名称(不包含文件扩展名)
    /// 资源加载完成后的回调委托,会将加载到的资源(类型为T)作为参数传入
    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);
        };
    }
}

使用示例:

/// 
/// 示例类,展示如何使用ResourcesManager进行资源加载
/// 
public class Example : MonoBehaviour
{
    // 用于存储加载的文本资源
    private TextAsset textAsset;
    
    void Start()
    {
        // 调用ResourcesManager单例的LoadRes方法异步加载资源
        //  指定了要加载的资源类型为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);
            }
        });
    }
}

这个资源管理器的主要优点是:

  1. 使用单例模式,方便全局访问
  2. 支持异步加载,不会阻塞主线程
  3. 使用泛型,可以加载任何类型的Unity资源
  4. 使用Lambda表达式简化了回调处理
  5. 代码简洁且易于使用

你可能感兴趣的:(Unity基础,unity,游戏引擎,c#,服务器,游戏程序)