一、Resources资源动态加载
1、Unity中特殊文件夹
1、工程路径获取
Application.dataPath //开发使用,打包后无效
2、Resources资源文件夹
路径获取:一般不获取
只能使用Resources相关API进行加载
可以用工程路径拼接获取路径
print(Application.dataPath+"/Resources");
注意:
需要我们自己创建
作用:
资源文件夹
1.需要通过Resources相关API动态加载的资源需要放在其中
2.该文件夹下所有文件都会被打包
3.打包时Unity会对其压缩加密
4.该文件夹打包后只读只能通过Resources相关API加载
3、StreamingAssets流动资源文件夹
路径获取
print(Application.streamingAssetsPath);
注意:
需要我们自已创建
作用:
流文件夹
1.打包出去不会被压缩励密,可以任由我们门摆布
2.移动平台只读,PC平台可读可写
3.可以放入一些需要自定义动态加载的初始资源
4、persistentDataPath持久数据文件夹
路径获取
print(Application.persistentDataPath);
注意:
不需要我们自己创建
作用:
固定数据文件夹
1.所有平台都可读写
2.一般用于放置动态下载或者动态创建的文件,游戏中创建或者获取的文件都放在其中
5、Plugins插件文件夹
路径获取般不获取
注意:
需要我们自己创建
作用:
插件文件夹
不同平台的插件相关文件放在其中
比如放入Ios和Android平台的工具
6、Editor编辑器文件夹
路径获取:一般不获取
如果硬要获取可以用工程路径拼接
注意:
需要我们自己创建
作用:
编辑器文件夹
1.开发Unity编辑器时,编辑器相关脚本放在该文件夹中
2.该文件夹中内容不会被打包出去
7、默认资源文件夹StandardAssets
路劲获取:一般不获取
注意:
需要我们自己创建
作用:
默认资源文件夹
一般Unity自带资源都放在这个文件夹下
代码和资源优先被编译
2、Resources同步加载
1、Resources资源动态加载的作用
1.通过代码动态加载Resources文件夹下指定路径资源
2.避免繁锁的拖曳操作
2、常用资源类型
1.预设体对象 Gameobject
2.音效文件 AudioClip
3.文本文件 TextAsset
4.图片文件 Texture
5.其它类型 需要什么用什么类型
注意:
预设体对象加载需要实例化
其它资源加载一般直接用
3、资源同步加载【普通方法】
一个工程可以有多个Resources文件夹,打包时会自动整合在一起
1.预设体对象 想要创建在场景上【实例化】
第一步:要去加载设体的资源文件(本质上就是加载配置数据在内存中)
Object obj = Resources.Load("Cube");
第二步:如果想要在场景创建预设体一定是加载配置文件过后然后实例化
Instantiate(obj);
2.音效资源
public AudioSource audioS;
第一步:加载数据
Object obj = Resources.Load("Music/BkMusic");
第二步:使用数据,不需要实例化音效切片,只需要把数据赋值到正确的脚本上即可
audioS.clip = obj as AudioClip;
audioS.Play();
3.文本资源
文本资源支持的格式
.txt.xml.bytes.json.html.csv
TextAsset ta = Resources.Load("Txt/Test")as TextAsset;
文本内容
print(ta.text);
字节数据组
print(ta.bytes);
4.图片
private Texture tex;
tex = Resources.Load("Tex/TestJPG") as Texture;
private void OnGUI(){
GUI.DrawTexture(new Rect(0, 0,100, 100),tex);
}
5.其它类型 需要什么类型就用什么类型就行
6.问题:资源同名怎么办
Resources.Load加载同名资源时,无法准确载出你想要的内容
可以使用另外的API
1.加载指定类型的资源
tex = Resources.Load("Tex/TestJPG",typeof(Texture)) as Texture;
ta = Resources.Load("Tex/TestJPG",typeof(TextAsset)) as TextAsset;
2.加载指定名学的所有资源
Object[] objs = Resources.LoadAll("Tex/TestJPG")
foreach (Object item in objs){
if(itemisTexture){}
else if(item is TextAsset){}
}
4、资源同步加载【泛型方法】
TextAsset ta = Resources.Load<TextAsset>("Tex/TestJPG");
tex = Resources.Load<Texture>("Tex/TestJPG");
例:
bullet = Resources.Load<GameObject>("bullet"); //每次加载不会浪费内存,会消耗性能
3、Resources异步加载
1、Resources异步加载概念
在同步加载中
如果加载过大的资源可能会造成程序卡顿
卡顿的原因就是从硬盘上把数据读取到内存中是需要进行计算的
越大的资源耗时越长,就会造成掉顿卡顿
Resources异步加载就是内部新开一个线程进行资源加载不会造成主线程卡顿
2、Resources异步加载方法
异步加载不能马上得到加载的资源,至少要等一帧
1、通过【异步加载】中的完成事件监听,使用加载的资源
private Texture tex;
private void LoadOver(AsyncOperation ao){
print("加载完成");
print(Time.frameCount); //测试查看帧
//加载完成后使用asset,是资源对象
tex = (ao as ResourceRequest).asset as Texture;
}
void Start(){
//Unity在内部就会去开一个线程进行资源下载
//LoadAsync方法的返回值ResourceRequest里的asset;
//ResourceRequest继承的AsyncOperation类有completed事件
ResourceRequest rq = Resources.LoadAsync<Texture>("Tex/TestJPG");
//马上进行一个资源下载结束的一个事件函数监听
rq.completed += LoadOver;
print(Time.frameCount);
}
private void OnGUI()
{
if(tex!= null)
GUI.DrawTexture(new Rect(0, 0, 100, 100), tex);
}
2、通过【协程】使用加载的资源
StartCoroutine(Load());
IEnumerator Load(){
//【选代器函数】遇到yield return时就会停止执行之后的代码
//然后【协程协调器】通过得到返回的值去判断下一次执行后面步骤的时间
ResourceRequest rq = Resources.LoadAsync<Texture>("Tex/TestJPG");
//第一部分
//Unity自动检测到异步加载资源
yield return rq;
打印进度
while(!re.isDone){ //是否结束
print(re.priority); //priority进度
yield return null; //测试
}
//加载完毕后,Unity自动执行之后代码
tex = rq.asset as Texture;
}
线程加载和协程异步加载【比较】
1.完成事件监听异步加载
写法简单
但只能在资源加载结束后进行处理
“线性加载”
2.协程异步加载
好处:可以在协程中处理复杂逻辑,比如同时加载【多】个资源,比如进度条更新
坏处:写法稍麻烦
“并行加载”
思考:
理解为什么异步加载不能马上加载结束,为什么至少要等1顿
理解协程异步加载的原理
思考 封装资源异步加载管理器
请写一个简单的资源管理器:
提供统一的方法给外部用于资源异步加载,
外部可以传入委托用于当资源加载结束时使用资源
ResourcesMgr
using UnityEngine;
using UnityEngine.Events;
public class ResourcesMgr
{
private static ResourcesMgr instance = new ResourcesMgr();
public static ResourcesMgr Instance => instance;
private ResourcesMgr(){}
/// <summary>
/// 异步加载资源
/// </summary>
/// <param name="name">函数名</param>
/// <param name="callBack">回调函数</param>
public void AsyncLoadResources<T>(string name, UnityAction<T> callBack) where T : Object
{
ResourceRequest resReq = Resources.LoadAsync<T>(name);
resReq.completed += (finish) =>
{
//参数finish是completed的值,事件completed是ResourceRequest的父类,所以:
//资源对象(finish as ResourceRequest).asset as T
callBack((finish as ResourceRequest).asset as T);
};
}
}
Test_ResMgr
using UnityEngine;
public class Test_ResMgr : MonoBehaviour
{
private Texture tex;
void Start()
{
//封装前代码
ResourceRequest rq = Resources.LoadAsync<Texture>("Tex/TestJPG");
rq.completed += (v) =>
{
tex = (v as ResourceRequest).asset as Texture;
};
//封装后代码
ResourcesMgr.Instance.AsyncLoadResources<Texture>("Tex/TestJPG", (obj) => { tex = obj; });
}
private void OnGUI()
{
if (tex != null) //避免异步加载有可能为空
GUI.DrawTexture(new Rect(0, 0, 200, 100), tex);
}
}
4、Resources卸载资源
1、Resources重复加载资源与内存
其实Resources加载一次资源过后
该资源就一直存放在内存中【作为缓存】
第二次加载时发现缓存中存在该资源会直接取出来进行使用
所以多次重复加载不会浪费内存
但是会浪费性能(每次加载都会去查找取出,始终伴随一些性能消耗)
2、如何手动释放掉缓存中的资源
1.卸载指定资源
Resources.UnloadAsset方法
注意
该方法不能释放Gameobiect对象因为它会用于实例化对象
它只能用于一些不需要实例化的内容比如图片和音效文本等
一般情况下很少单独使用它
2.卸载未使用的资源
注意:
一般在过场景时和GC一起使用
Resources.UnloadUnusedAssets();
GC.Collect();
二、场景异步加载
1、场景同步切换
SceneManager.LoadScene("Test") //在Build Settings添加切换场景
场景同步切换的缺点
在切换场景时
Unity会删除当前场景上所有对象,并且去加载下一个场景的相关信息
如果当前场景对象过多或者下一个场景对象过多,这个过程会非常的耗时、卡顿
2、场景异步切换
1、通过事件回调函数异步加载
场景异步加载和资源异步加载几乎一致,有两种方式
1.通过事件回调函数异步加载
SceneManager.LoadSceneAsync("Test");
void Start()
{
AsyncOperation ao = SceneManager.LoadSceneAsync("Test");
//异步加载完成后,使用逻辑
ao.completed += (a) =>
{
print("加载完成");
};
ao.completed += LoadOver;
}
private void LoadOver(AsyncOperation ao)
{
print("LoadOver");
}
2、通过协程异步加载
2.通过协程异步加载
需要注意的是:加载场景会把当前场景上没有特别处理的对象都删除了
所以协程中的部分逻辑可能是执行不了的
解决思路
让处理场景加载的脚本依附的对象,过场景时不被移除
void Start()
{
DontDestroyOnLoad(gameObject); //挂载此脚本的对象过场景时不被移除
StartCoroutine(LoadScene("Test"));
}
IEnumerator LoadScene(string name)
{
///第一步:
//异步加载场景
AsyncOperation ao = SceneManager.LoadSceneAsync(name);
//Unity内部的协程协调器发现是异步加载类型的返回对象那么就会
//等待异步加载结束后会续执行选代器函数中后面的步骤
print("异步加载中");
//协程的好处是异步加载场景时我可以在加载的同时做一些别的逻辑
yield return ao;
//第二步骤:
print("异步加载后");
//比如我们门可以在异步加载过程中去更新进度条
第一种 就是利用场景异步加载的进度去更新但是不是特别准确一般也不会直接用
while(!ao.isDone)
{
print(ao.progress);
yield return null;
}
//离开循环后就会认为场景加载结束,可以把进度条顶满然后隐藏进度条
第二种 就是根据你游戏的规则自己定义进度条变化的条件
//场景加载结束更新20%进度
//接着去加载场景中的其它信息
//比如动态加载怪物
//这时进度条再更新20%
//动态加载场景模型
//这时就认为加载结束了100%进度条
//隐藏进度条
}
3、场景异步加载总结
场景异步加载和资源异步加载一样有两种方式
1.通过事件回调函数 2.协程异步加载
也们的优缺点表现和资源异步加载也是一样的
1.事件回调函数
优点:写法简单,逻辑清晰
缺点:只能加载完场景做一些事情不能再加载过程中处理逻辑
2.协程异步加载
优点:可以在加载过程中处理逻辑,比如进度条更新等
缺点:写法较为麻烦,要通过协程
思考 封装场景管理器
请写一个简单的场景管理器,
提供统一的方法给外部用于场景异步切换
外部可以传入委托用于当异步切换结束时执行某些逻辑
SceneMgr
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
public class SceneMgr
{
private static SceneMgr instance;
public static SceneMgr Instance => instance;
public SceneMgr(){}
public void LoadScene(string name, UnityAction action)
{
AsyncOperation ao = SceneManager.LoadSceneAsync(name);
//通过lambda表达式包裹一层,在内部直接调用外部传入的委托即可
ao.completed += (a) => { action(); };
}
}
Test_Scene
using UnityEngine;
public class Test_Scene : MonoBehaviour
{
void Start()
{
SceneMgr.Instance.LoadScene("Test", () => { print("加载完成"); });
}
}