异步任务相信大家应该不会陌生,那么本章内容MOMO将带领大家学习Unity中的一些异步任务。在同步加载游戏场景的时候通常会使用方法 Application.LoadLevel(“yourScene”); 这句代码执行完毕后程序会干什么呢??如下图所示,这是我随便找了一个游戏场景, 在Hierarchy视图中我们可以看到该场景中“天生”的所有游戏对象。天生的意思就是运行程序前该场景中就已经存在的所有游戏对象。然后这些对象就会在执行完Application.LoadLevel(“yourScene”);方法后加载至内存当中。如果该场景中的游戏对象过多那么瞬间将会出现卡一下的情况,因为LoadLevel()方法是同步进行的。MOMO把这种加载起个名字叫A形式加载。
下面我说说“后天“加载的游戏对象。意思是这些游戏对象是通过脚本动态的创建出来的。比如常用方法 :
1 2 3 |
GameObject Obj = (GameObject)Instantiate(prefab); |
这句代码执行完毕后同样会在Hierarchy视图中添加对应的游戏对象。MOMO把这种加载起个名字叫B形式加载。
下面我们学习异步加载游戏场景,异步异步顾名思义就是不影响当前游戏场景的前提下加载新场景。通常异步加载的方式分为两种:第一种是异步加载新游戏场景,当新场景加载完成后进入新场景并且销毁之前的场景。第二种:同样异步加载新场景,新场景加载完毕后,保留旧场景的游戏对象并且进入新场景。 这里加载的内容就是上面提到的A形式加载。然后B形式加载不会记入这里的加载。
第一种异步加载游戏场景对应的方法是:
1 2 3 |
Application.LoadLevelAsync("yourScene"); |
第二种异步家在游戏场景对应的方法是:
1 2 3 |
Application.LoadLevelAdditiveAsync ("yourScene"); |
这两种方法加载的方式完全一样。异步加载其实重要还是应用于游戏LOADING界面,毕竟LOADING如果采用同步的机制会影响用户体验,说到这里MOMO告诉大家如何在Unity中制作游戏进度条。我们应当在Unity中创建一个专门用于读取进度的场景,假设A场景到C场景,我们应当让A场景先到读取进度的场景B场景,当异步任务完成后在进入C场景。 A – 》B -》 C ,在B场景中绘制游戏进度条或读取动画。因为B场景仅仅是个显示LOADING动画的场景,所以读取该场景是瞬间就完成的。
程序在切换场景时应当有一个全全局的静态变量来记录简要读取的场景名称。这里简单的写一下。
1 2 3 4 5 6 7 8 9 10 |
using UnityEngine; using System.Collections; public class Globe { //在这里记录当前切换场景的名称 public static string loadName; } |
在A场景中通过某些触发条件 调用LoadLevel进入B场景。
1 2 3 4 5 6 |
//记录LOADING场景中需要读取的C场景名称 Globe.loadName = "C"; //先进入B场景 Application.LoadLevel ("B"); |
OK我们在B场景中异步读取C场景与 播放读取动画,Loading.cs 绑定在B场景的摄像机对象身上。当C场景异步读取完毕后即可直接进入C场景。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
using UnityEngine; using System.Collections; public class Loading : MonoBehaviour { private float fps = 10.0f; private float time; //一组动画的贴图,在编辑器中赋值。 public Texture2D[] animations; private int nowFram; //异步对象 AsyncOperation async; //读取场景的进度,它的取值范围在0 - 1 之间。 int progress = 0; void Start() { //在这里开启一个异步任务, //进入loadScene方法。 StartCoroutine(loadScene()); } //注意这里返回值一定是 IEnumerator IEnumerator loadScene() { //异步读取场景。 //Globe.loadName 就是A场景中需要读取的C场景名称。 async = Application.LoadLevelAsync(Globe.loadName); //读取完毕后返回, 系统会自动进入C场景 yield return async; } void OnGUI() { //因为在异步读取场景, //所以这里我们可以刷新UI DrawAnimation(animations); } void Update() { //在这里计算读取的进度, //progress 的取值范围在0.1 - 1之间, 但是它不会等于1 //也就是说progress可能是0.9的时候就直接进入新场景了 //所以在写进度条的时候需要注意一下。 //为了计算百分比 所以直接乘以100即可 progress = (int)(async.progress *100); //有了读取进度的数值,大家可以自行制作进度条啦。 Debug.Log("xuanyusong" +progress); } //这是一个简单绘制2D动画的方法,没什么好说的。 void DrawAnimation(Texture2D[] tex) { time += Time.deltaTime; if(time >= 1.0 / fps){ nowFram++; time = 0; if(nowFram >= tex.Length) { nowFram = 0; } } GUI.DrawTexture(new Rect( 100,100,40,60) ,tex[nowFram] ); //在这里显示读取的进度。 GUI.Label(new Rect( 100,180,300,60), "lOADING!!!!!" + progress); } } |
OK 下面我们继续学习在游戏场景中加载对象,文章的开始MOMO已经告诉大家,游戏场景中Hierarchy视图中的所有的对象在切换场景的时候都会加载。其实有一种方法可以让某些游戏对象不会被加载,如下图所示,首先在Hierarchy视图中选择一个游戏对象,在右侧监测面板视图中我们可以看到一个 “小对勾”默认情况下是勾选状态,说明该游戏对象处于激活状态,如果点掉的话该对象将被隐藏。这个小功能在开发中其实用处非常大,请大家务必记住哈。
此时此刻大家相像一个游戏场景,默认进入的时候是没有任何游戏对象的,然后运行游戏时开启一个异步任务将它们一个一个的加载显示出来,这种方式适合异步的加载一个比较大的游戏场景。
Test.cs 把它挂在摄像机对象中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
using UnityEngine; using System.Collections; public class Test : MonoBehaviour { //这里是需要加载激活的游戏对象 public GameObject [] Objects; //当前加载的进度 int load_index =0; void Start () { //开启一个异步任务,加载模型。 StartCoroutine(loadObject()); } IEnumerator loadObject() { //便利所有游戏对象 foreach(GameObject obj in Objects) { //激活游戏对象 obj.active = true; //记录当前加载的对象 load_index ++; //这里可以理解为通知主线程刷新UI yield return 0; } //全部便利完毕返回 yield return 0; } void OnGUI () { //显示加载的进度 GUILayout.Box("当前加载的对象ID是: " + load_index); } } |
如下图所示,我们把需要加载的游戏对象以数组的形式放在Objects数组中,因为这些对象属于未激活状态,所以不能通过Find 等方法在脚步那种中找到他们。讲到这里我们在说说 编辑器赋值与代码中赋值的区别,编辑器中赋值所消耗的时间都会记在loadlevel ()读取场景中。而代码中使用Resource.load()这类方法所消耗的时间会记在脚本中。开发中还得自行的把握一下把loading加在那里。
当然我们还可以使用Instantiate(prefab);方法来动态的创建游戏对象。
Main.cs 把它挂在摄像机中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
using UnityEngine; using System.Collections; public class Main : MonoBehaviour { public int count; //在编辑器中预设一个游戏对象 public GameObject prefab; void Start () { StartCoroutine(loaditem()); } void OnGUI() { GUILayout.Box("游戏对象已经加载到 : " + count); } IEnumerator loaditem() { //开始加载游戏对象 for(int i =0; i< 1000; i++) { Instantiate(prefab); count = i; //可以理解为刷新UI,显示新加载的游戏对象 yield return 0; } //结束 yield return 0; } } |
运行游戏后该游戏对象会循环1000遍逐个创建,不影响主线程。那么今天我们其实学习最多的就是StartCoroutine(),其实就是开启一个异步线程,这里可能有朋友会问Thread可以代替它吗? 答案是不行, 比如查询数据库的时候如果用Thread的话Unity就会报错说不能在线程中查询,但是在StartCoroutine()中就可以完成,所以开发中大家可以尝试着使用它,我们还可以使用StopCoroutine(“name”)来关闭一个正在执行的异步线程。不早了晚安,MOMO祝大家学习愉快。
- 本文固定链接: https://www.xuanyusong.com/archives/1427
- 转载请注明: 雨松MOMO 于 雨松MOMO程序研究院 发表
请问一下,用www加载打成AssertBundle的场景和这样加载有什么区别吗?
为什么最近用Application.LoadLevel 发现提示说这个已经是过时的了。。新的好像要用到C# ?public static void LoadScene(string sceneName, SceneManagement.LoadSceneMode mode = LoadSceneMode.Single); 这样的方式 可是 我试着用了之后还是会报错
为什么最近用Application.LoadLevel 发现提示说这个已经是过时的了。。新的好像要用到C# ⇒public static void LoadScene(string sceneName, SceneManagement.LoadSceneMode mode = LoadSceneMode.Single); 这样的方式 可是 我试着用了之后还是会报错
unity 5.3以后要用SceneManager来load场景。
MOMO,A-》B-》C,异步加载C场景中的AssetBundle包的代码写在哪里?是写在Loading脚本中还是写在C场景中的脚本里?
协程没有开启异步线程啊。unity单线程生物。算是异步方式。
Debug.Log(“LoadLevel before”);
Application.LoadLevel(“level2”);
Debug.Log(“LoadLevel after”);
上面代码发现:Debug.Log(“LoadLevel after”); 会在第二个场景的awake之前执行,这种表现应该是异步啊,请教下您说的同步和异步对代码执行顺序的影响。
还有就是我希望 Debug.Log(“LoadLevel after”); 在第二个场景的 awake之后执行,有没有办法?
Debug.Log(“LoadLevel before”);
Application.LoadLevel(“level2”);
Debug.Log(“LoadLevel after”);
上面代码发现:Debug.Log(“LoadLevel after”); 会在第二个场景的awake之前执行,这种表现应该是异步啊,请教下您说的同步和异步对代码执行顺序的影响。
还有就是我希望 Debug.Log(“LoadLevel after”); 在第二个场景的 awake之后执行,有没有办法?
loadLevel是在下一帧才同步执行、 所以Debug.Log(“LoadLevel after”); 就会先执行。
感谢这么快回复,话说您还没放假……..
再问下,如果要实现LoadLevel之后执行新场景的awake,然后执行LoadLevel后面的代码,是不是只能用StartCoroutine,没有简单的方式?
你可以把代码放在下一个场景的任意脚本的awake里 或者 OnLevelWasLoaded 里
博主,请教一个问题,按你文章中说的情况,是不是被加载的场景只会加载激活状态的gameobject,这些gameobject上的脚本并不会执行,还是需要切换到被加载的场景才会执行脚本,那假如我在被加载的场景里的awake和start里写了一些比如对象池的脚本导致场景开始有点慢,我可以把对象池的方法写到loading scene里吗?但是这样我怎么控制进度条合理呢?
Loading.cs那个为什么要用协程呢?Application.LoadLevelAsync本来就已经是异步了,协程在这里完全没有意义吧?
如果在update里面判断进度的话 这个携程是没有意义的。。
我想请教一下,为什么我的loading scene会一闪而过直接跳到 C场景啊。。。
我想问一个问题,我按照上面的操作了,我把这个理解成简单的Splash画面,所以在B画面就当成了引导画面,将B画面的所有对象都移走,然后在B画面中添加了一个空物体,挂载Loading脚本,然后加载了一个图片,去掉了那个文字进度条,但是加载到C后,C的场景特别是C场影的DirectLight的灯光的颜色变了,不是原来的白色了,为什么?我使用提Unity5.1 版本
猜测一下,你这样情况进入C场景,Lighting里面的环境光设置还是会继续沿用B场景的,就是说你要把B场景的Lighting设置成C场景那样
我把原来的B删除了,然后把B的SkyBox 还有 直线光 都添加进去了,这回好了,
也许是你说的
解决了就好
老師 我想請問 如果我想要用 過場動畫 例如:有人在跑or一些小動畫 不要用進度條 ,那我要用哪個方法!?
学到了好多啊,谢谢老师
呵呵。。过奖啦。~~
大神,发布成web版,如何打开html 的时候显示加载资源的百分比啊?跪求指导
Gameobject这就是个组合模式,按组合模式访问所有子级update接口就是遍历…..当然不会有什么并行之类的行为了
场景动态加载,会导致drawcall增加将近一倍,如果要给渲染组件设置光照等需要获取这个renderer又会增加一倍的drawcall。怎么去优化将drawcall降低到原来的水平呢?
动态的合并drawcall
新年快乐!我想请问下:异步加载场景后 //Globe.loadName 就是A场景中需要读取的C场景名称。async = Application.LoadLevelAsync(Globe.loadName);Unity会直接自动进入场景的吗?能不能像某些游戏那样加载完成后点击或者按某个键来切换场景的呢?
新年快乐!我想请问下:异步加载场景后 //Globe.loadName 就是A场景中需要读取的C场景名称。 async = Application.LoadLevelAsync(Globe.loadName);Unity会直接自动进入场景的吗?能不能像某些游戏那样加载完成后点击或者按某个键来切换场景的呢?
好象不行。。
我想问一下,异步加载对象为啥还要用协程?直接在start()中循环setActive或者Initiate不就行了么??还有,异步加载场景时如果加载完成时怎么才能使得加载前的工作(比如动画)全部完成才进入场景,而不是加载完成就自动进入。(前一个场景的工作被打断)
可以不用携程, 可以在update里面判断是否加载完成。
我觉得你这个可以在代码中稍稍做下修改,
//读取完毕后返回, 系统会自动进入C场景
yield return async;
这句代码之前可以加入一些提示性的文字,比如按下鼠标左键,或者enter键进入新场景。
然后用一个if语句,来判断下是否按下某键,后面再跟这句。
我觉得你这个可以在代码中稍稍做下修改,
//读取完毕后返回, 系统会自动进入C场景
yield return async;
这句代码之前可以加入一些提示性的文字,比如按下鼠标左键,或者enter键进入新场景。
然后用一个if语句,来判断下是否按下某键,后面再跟这句。
问下MOMO大神我的进度条总是0,是和pro版本有关系吗
在手机上看。。