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

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.xdnf.cn/news/19341.html

相关文章:

  • 迷你电脑用到什么型号的RJ45网口
  • 揭秘表格推理的“思维革命”:RoT模型介绍
  • seafile-setup-troubleshooting_# Seafile 安装与问题解决记录 # Seafile/Seahub 启动问题记录文档
  • linux基础——UDP、TCP
  • JavaScript之性能优化
  • 深入理解C++中的移动赋值与拷贝赋值函数——兼论移动构造函数及其实际应用场景
  • STM32手动移植FreeRTOS
  • 【学Python自动化】 1. Python 安装与配置完全指南 (Windows)
  • 从“互联网+”到“人工智能+”:云计算生态演进揭示AI应用破局之道
  • springboot 实现不同接口指定上传文件大小
  • 腾讯云centos7.6的运维笔记——从yum的安装与更新源开始
  • 小狼毫输入法中让数字键盘上的数字键不再选择候选词而是与原始输入一起直接上屏
  • 抖音热榜展示页面
  • Android 权限申请现代化指南
  • MySQL 在 CentOS 上的安装与配置文件路径详解
  • 2025-08-18面试题(nginx,mysql,zabbix为主)
  • LeetCode 2540.最小公共值
  • 1.7 Rendering模块
  • 数据结构 03(线性:单链表)
  • web渗透之RCE漏洞
  • Java中使用JSONUtil处理JSON数据:从前端到后端的完美转换
  • [Mysql数据库] 知识点总结7
  • 06.《STP 基础原理与配置详解》
  • DFS 回溯 【各种题型+对应LeetCode习题练习】
  • 突发,支付宝发布公告
  • 一体化步进伺服电机在汽车线束焊接设备中的应用案例
  • LZ4 解压工具(WPF / .NET 8)说明书
  • 【金仓数据库产品体验官】KingbaseES-ORACLE兼容版快速体验
  • 深入理解drv_spi.c后,完全正向亲手移植rt-thread的drv_spi.c驱动 (基于stm32h750 artpi)
  • Java函数式编程之【流(Stream)性能优化】