当前位置: 首页 > news >正文

Unity笔记(八)——资源动态加载、场景异步加载

写在前面:

写本系列(自用)的目的是回顾已经学过的知识、记录新学习的知识或是记录心得理解,方便自己以后快速复习,减少遗忘。

九、资源动态加载

1、特殊文件夹

(1)工程路径获取

该方式下获取到的路径一般只在编辑模式下使用,游戏发布后,该方法就不存在了。

void Start()
{print(Application.dataPath);
}

(2)Resources资源文件夹

一般不获取它的路径,直接使用Resources相关API进行加载。如果一定要获取,只能工程路径拼接。

void Start()
{
//最好不用print(Application.dataPath + "/Resources");
}

资源文件夹需要手动创建,名字不能出错。并且有以下几点需要注意:

1、需要通过Resources相关API动态加载的资源需要放在其中。

2、该文件夹下所有文件都会被打包出去,打包时Unity会对其压缩加密

3、该文件夹打包后只读,只能通过Resources相关API加载

(3)流动资源文件夹

流动资源文件夹是streamingAssets,也需要我们自己创建,获取路径的方法是:

void Start()
{print(Application.streamingAssetsPath);
}

流动资源文件夹的作用是:打包出去不会被压缩加密、移动平台只读,PC平台可读可写、可以放入一些需要自定义动态加载的初始资源。

(4)持久数据文件夹

持久数据文件夹persistentDataPath,不需要我们自己创建。

void Start()
{print(Application.persistentDataPath);
}

作用是:固定的数据文件夹。所有平台都可读可写。一般用于放置动态下载或者动态创建的文件夹。游戏中创建或者获取的文件都放在其中。

(5)Plugins插件文件夹

一般不获取,需要我们手动创建。不同平台(如第三平台的开发工具)的插件相关的文件放在其中。

(6)Editor编辑器文件夹

一般不获取,需要我们手动创建。作用是开发Unity编辑器时,编辑器相关脚本放在该文件夹中,该文件夹的内容不会被打包出去。

(7)Standard Assets默认资源文件夹

一般不获取,需要我们自己创建,一般Unity自带资源都放在这个文件夹下,代码和资源优先被编译。

2、Resources同步加载

(1)作用

通过代码动态加载Resources文件夹下指定路径资源,避免繁琐的拖拽操作。

(2)常用资源类型

1、预设体对象:GameObject

2、音效文件:AudioClip

3、文本文件:TextAsset

4、图片文件:Texture

5、其他类型

其中,预设体加载需要实例化,其他资源加载一般直接用。

(3)资源同步加载

在一个工程当中,Resources可以有多个,通过API加载时,它会自己去这些同名文件夹中找资源。打包时,Resources文件夹里的内容都会打包在一起。

资源加载使用的API都是Resources.Load(),括号内传入需要加载的预设体/音效/文本相对路径

1、预设体对象加载

这里预设体位于Resources文件中,名字叫Cube

void Start()
{//第一步,要去加载预设体的资源文件(本质上 就是加载配置数据在内存中)Object obj = Resources.Load("Cube");//第二步,实例化Instantiate(obj);
}

2、音效加载

这里音频文件位于Resources/Music文件中,名字叫BKMusic

public AudioSource audioS;
void Start()
{//第一步加载数据Object obj1 = Resources.Load("Music/BKMusic");//第二步赋值脚本audioS.clip = obj1 as AudioClip;
}

3、文本加载

文本资源支持的格式:.txt .xml .bytes .json .html .csv

void Start()
{TextAsset ta = Resources.Load("Txt/Test") as TextAsset;//文本内容print(ta.text);//字节数据组//print(ta.bytes);
}

4、图片

private Texture tex;void Start()
{tex = Resources.Load("Tex/TestJPG") as Texture;
}private void OnGUI()
{GUI.DrawTexture(new Rect(0, 0, 100, 100), tex);
}

5、资源同名时

加载同名资源时,无法准确加载出你想要的内容。可以指定类型:

private Texture tex;void Start()
{tex = Resources.Load("Tex/TestJPG", typeof(Texture)) as Texture;
}

加载所有类型的指定资源

void Start()
{Object[] objs = Resources.LoadAll("Tex/TestJPG");
}

(4)资源同步加载泛型方法

void Start()
{Texture tex2 = Resources.Load<Texture>("Tex/TestJPG");
}

3、Resources异步加载

使用同步加载时,如果加载过大的资源可能造成程序卡顿。越大的资源耗时越长,就会造成掉帧卡顿。Resources异步加载就是内部新开一个线程进行资源加载,不会造成主线程卡顿。在完成加载过后,将加载的内容放入公共区域,主线程去公共区域中取数据。

需要注意的是,异步加载不能马上得到加载的资源,至少要等一帧。

(1)完成时间监听异步加载

异步加载使用的API是:Resources.LoadAsync<>()(为了方便这里只介绍泛型),<>中传入需要加载的类型,括号内传入的是加载数据在Resources文件夹中的路径,和同步加载一样。该API的返回值是ResourceRequest类型的变量,可以定义一个变量接收,例如:

ResourceRequest rq = Resources.LoadAsync<Texture>("Tex/TestJPG");

这句代码的意思就是:在Unity内部新开一个线程进行资源下载。

在ResourceRequest类型中,提供了completed事件。也就是当你调用Resources.LoadAsync时,会马上进行资源下载结束的事件函数监听,当它加载完毕时,内部会自动调用这个事件函数。

此时,我们使用rq.completed += LoadOver;可以自行为这个事件添加函数LoadOver,在LoadOver中实现资源赋值、提醒加载完成等一系列操作。此外,按照规定,函数LoadOver必须传入一个AsyncOperation类型的参数,AsyncOperation是ResourceRequest的父类。传入的这个参数可以理解为,当加载完毕后,Unity会自动将你加载的数据转化为AsyncOperation类型,并且在你为completed添加函数时传入作为参数,因此必须传入一个AsyncOperation类型的参数。

为了得到加载的资源,ResourceRequest类提供了.asset方法,可以通过该方法获得加载的资源。只需要将传入的AsyncOperation类型的变量转为ResourceRequest类型的变量,即可使用。如下:

private Texture tex;void Start()
{ResourceRequest rq = Resources.LoadAsync<Texture>("Tex/TestJPG");rq.completed += LoadOver;
}private void LoadOver(AsyncOperation rq)
{print("加载结束");tex = (rq as ResourceRequest).asset as Texture;
}private void OnGUI()
{if(tex != null){GUI.DrawTexture(new Rect(0, 0, 100, 100), tex);}
}

(2)通过协程

还可以使用协程来完成资源异步加载。首先需要创建一个协程函数。

在协程函数中使用:ResourceRequest rq = Resources.LoadAsync<Texture>("Tex/TestJPG");来开启异步资源加载。直接使用yield return rq;Unity知道该返回值意味着你在异步加载资源,Unity会自己判断该资源是否加载完毕了,加载完毕后,才会继续执行后面的代码。在该代码后直接使用 tex = rq.asset as Texture;  即可获得加载的资源。

private Texture tex;void Start()
{StartCoroutine(Load());
}IEnumerator Load()
{ResourceRequest rq = Resources.LoadAsync<Texture>("Tex/TestJPG");yield return rq;tex = rq.asset as Texture;  
}private void OnGUI()
{if(tex != null){GUI.DrawTexture(new Rect(0, 0, 100, 100), tex);}
}

此外,还有另外一种方式,可以直接使用rq.isDone判断数据加载是否完成,使用print(rq.priority)打印当前进度。

private Texture tex;void Start()
{StartCoroutine(Load());
}IEnumerator Load()
{ResourceRequest rq = Resources.LoadAsync<Texture>("Tex/TestJPG");while(!rq.isDone){//打印当前的加载进度,如果加载的内容少,该进度不会特别准确,过度也不是特别明显print(rq.priority);yield return null;} 
}private void OnGUI()
{if(tex != null){GUI.DrawTexture(new Rect(0, 0, 100, 100), tex);}
}

(3)区别

第一种方式的写法简单,但是只能在资源加载结束后进行处理

第二种方式写法稍麻烦,但是可以在协程中处理复杂逻辑,比如进度条更新

4、Resources资源卸载

Resources加载过一次资源后,该资源就已知存放在内存中作为缓存。第二次加载时发现缓存中存在该资源,会直接取出来使用。因此,多次加载不会浪费内存,但是会浪费性能。

(1)卸载指定资源

使用方法:Resources.UnloadAsset()方法可以卸载指定资源。需要注意的是,该方法不能释放GameObject对象,因为它会用于实例化对象。它只能用于一些不需要实例化的内容,比如图片、音效、文本等。

void Update()
{if(Input.GetKeyDown(KeyCode.Alpha1)){print("加载资源");tex = Resources.Load<Texture>("Tex/TestJPG");}if(Input.GetKeyDown(KeyCode.Alpha2)){print("卸载资源");Resources.UnloadAsset(tex);tex = null;}
}

(2)卸载未使用资源

一般在过场景时和GC一起使用,可以顺带把垃圾回收了。会卸载所有未使用的资源。

Resources.UnloadUnusedAssets();
GC.Collect();

十、场景异步加载

1、场景同步切换

之前学到的利用SceneManager.LoadScene()切换场景时,Unity会删除当前场景上的所有对象,并且去加载下一个场景的相关信息。如果当前场景对象过多或者下一个场景对象过多,这个过程会非常耗时,会让玩家感到卡顿。

2、场景异步切换

场景异步加载和资源异步加载几乎一致。

(1)通过事件回调函数异步加载

这里使用的GUI是:SceneManager.LoadSceneAsync(),括号内传入场景名即可。场景异步加载结束后,会自行切换到新场景。和资源异步加载一样我们可以在场景加载结束时,可以在completed事件函数中写处理逻辑:

void Start()
{AsyncOperation ao = SceneManager.LoadSceneAsync("Scene2");ao.completed += (a) =>{print("加载结束");};
}

(2)通过协程异步加载

由于加载场景时会把当前场景上没有特别处理的对象都删除了,所以协程中的部分逻辑可能执行不了(协程的脚本是挂载在当前场景的对象上的,所以当场景切换完毕后,对象会被删除,协程就不会继续执行。因此在这之后执行的逻辑将不能执行)。

但事件回调函数在场景切换完成后,依然可以执行completed事件函数。原因是此时对象虽然被删除了,但这个对象仍然存储在内存中,之后垃圾回收时(GC)进行统一删除。即使在当时就触发了垃圾回收,系统会发现,这个对象依然有脚本占用着它,因此不会立马删除,还可以继续执行completed事件函数。

协程逻辑执行不了解决思路是:让处理场景加载的脚本依附的对象过场景时不被移除。这个点之前提到过:DontDestroyOnLoad(this.gameObject),这句代码可以让当前脚本依附的对象过场景不被移除。如下:

void Start()
{DontDestroyOnLoad(this.gameObject);StartCoroutine(LoadScene("Scene2"));
}IEnumerator LoadScene(string name)
{AsyncOperation ao = SceneManager.LoadSceneAsync(name);while(ao.isDone){print(ao.progress);yield return null;}print("加载结束");
}

这样,就可以在加载结束后,还继续执行协程打印出“加载结束”。当然,这里还显示了进度条、不断判断加载是否结束。和资源异步加载相同,也可以不执行判断直接加载:

void Start()
{StartCoroutine(LoadScene("Scene2"));
}IEnumerator LoadScene(string name)
{AsyncOperation ao = SceneManager.LoadSceneAsync(name);yield return ao;
}

http://www.dtcms.com/a/358787.html

相关文章:

  • DbVisualizer:一款功能强大的通用数据库管理开发工具
  • 自动修改psd_生成套图 自动合并图片 自动生成psd文字层
  • Go 语言面试指南:常见问题及答案解析
  • 【具身智能】【机器人动力学】台大林佩群笔记-待持续更新
  • 索引结构与散列技术:高效数据检索的核心方法
  • HTS-AT模型代码分析
  • Shell脚本编程入门:从基础语法到流程控制
  • 本地运行 Ollama 与 DeepSeek R1 1.5B,并结合 Open WebUI 测试
  • 告别图片处理焦虑:用imgix实现智能、实时且高效的视觉媒体交付(含案例、截图)
  • Linux shell命令扩涨
  • HarmonyOS Router 基本使用详解:从代码示例到实战要点
  • 免费开源的 Gemini 2.5 Flash 图片生成器
  • Robolectric如何启动一个Activity
  • Coze源码分析-API授权-删除令牌-后端源码
  • SQL注入6----(其他注入手法)
  • 普蓝自研AutoTrack-4X导航套件平台适配高校机器人实操应用
  • 《Java反射与动态代理:从原理到实践》
  • 以声为剑,绘山河热血——刘洋洋《不惧》8月30日全网上线
  • 【深入解析——AQS源码】
  • OpenCV安装及其开发环境配置(Windows系统Visual Studio 2022)
  • 【物联网】MQTT / Broker / Topic 是什么?
  • 【分享】如何显示Chatgpt聊天的时间
  • 【Android】JSONObject和Gson的使用
  • 数据结构青铜到王者第十三话---优先级队列(堆)(2)
  • 中级函数三
  • 如何使用 DeepSeek 帮助自己的工作?—— 从效率工具到能力延伸的实战指南
  • BGP路由协议(四):工作原理
  • Redis 持久化配置
  • 使用python格式化nginx配置文件
  • 【系统分析师】高分论文:论系统测试技术及应用