异步任务相信大家应该不会陌生,那么本章内容MOMO将带领大家学习Unity中的一些异步任务。在同步加载游戏场景的时候通常会使用方法 Application.LoadLevel(“yourScene”); 这句代码执行完毕后程序会干什么呢??如下图所示,这是我随便找了一个游戏场景, 在Hierarchy视图中我们可以看到该场景中“天生”的所有游戏对象。天生的意思就是运行程序前该场景中就已经存在的所有游戏对象。然后这些对象就会在执行完Application.LoadLevel(“yourScene”);方法后加载至内存当中。如果该场景中的游戏对象过多那么瞬间将会出现卡一下的情况,因为LoadLevel()方法是同步进行的。MOMO把这种加载起个名字叫A形式加载。
下面我说说“后天“加载的游戏对象。意思是这些游戏对象是通过脚本动态的创建出来的。比如常用方法 :
GameObject Obj = (GameObject)Instantiate(prefab);
这句代码执行完毕后同样会在Hierarchy视图中添加对应的游戏对象。MOMO把这种加载起个名字叫B形式加载。
下面我们学习异步加载游戏场景,异步异步顾名思义就是不影响当前游戏场景的前提下加载新场景。通常异步加载的方式分为两种:第一种是异步加载新游戏场景,当新场景加载完成后进入新场景并且销毁之前的场景。第二种:同样异步加载新场景,新场景加载完毕后,保留旧场景的游戏对象并且进入新场景。 这里加载的内容就是上面提到的A形式加载。然后B形式加载不会记入这里的加载。
第一种异步加载游戏场景对应的方法是:
Application.LoadLevelAsync("yourScene");
第二种异步家在游戏场景对应的方法是:
Application.LoadLevelAdditiveAsync ("yourScene");
这两种方法加载的方式完全一样。异步加载其实重要还是应用于游戏LOADING界面,毕竟LOADING如果采用同步的机制会影响用户体验,说到这里MOMO告诉大家如何在Unity中制作游戏进度条。我们应当在Unity中创建一个专门用于读取进度的场景,假设A场景到C场景,我们应当让A场景先到读取进度的场景B场景,当异步任务完成后在进入C场景。 A – 》B -》 C ,在B场景中绘制游戏进度条或读取动画。因为B场景仅仅是个显示LOADING动画的场景,所以读取该场景是瞬间就完成的。
程序在切换场景时应当有一个全全局的静态变量来记录简要读取的场景名称。这里简单的写一下。
using UnityEngine;
using System.Collections;
public class Globe
{
//在这里记录当前切换场景的名称
public static string loadName;
}
在A场景中通过某些触发条件 调用LoadLevel进入B场景。
//记录LOADING场景中需要读取的C场景名称
Globe.loadName = "C";
//先进入B场景
Application.LoadLevel ("B");
OK我们在B场景中异步读取C场景与 播放读取动画,Loading.cs 绑定在B场景的摄像机对象身上。当C场景异步读取完毕后即可直接进入C场景。
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 把它挂在摄像机对象中。
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 把它挂在摄像机中。
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程序研究院 发表




雨松大神, 小弟看了你 书的第九章 加载资源包,我想, 加载一个资源包, 如你写的 代码9-06IEnumerator loadBundleObject () {WWW date = http://WWW.LoadFromCacheOrDownload(“http://192.168.1.221:8080/his/Cube.unity3d“,5); yield return Instantiate(date.assetBundle.mainAsset);}在编辑器中可以加载,打包成exe也可以加载,但是打包成webplayer 就网页出错了 ,我用到tomcat , 之前说是crossdomant.xml 没有,我在网上下载了一个, 安装进去后,没报错,但是网页依然不能加载cube.unity3d求大神指教!谢谢了!
用assetBundle 需要在服务端 写个配置文件的。。
请问怎么的一种配置文件, 求指教? 不管哪种服务器 ,用assetBundle 都需要写配置文件么?谢谢了
配置文件要放在服务器 和本地没关系。。
请问怎么的一种配置文件, 求指教? 不管哪种服务器 ,用assetBundle 都需要写配置文件么?谢谢了
松哥,我用异步方式加载一个大场景,卡住,进度条无效果;然后我新开了Thread用于执行异步加载,进度条开始动了,但是加载不了新的场景,我估计是不是加载场景必须要在主线程里面才能执行呢,请指教!!!
你只能在协同中来写的, unity所有东西都需要写在主线程中的。。
考察下来,目前要实现多线程只能自己写plugin
处理数据的话还好办。 用thread就可以 但是要访问gameobject 只能在mainThread 。 你有什么好的思路吗?
unity的api只能再MainThread,这个没啥好办法啊
我做过专门的测试。。 unity中每个对象的update并不是并行完成的, 它是按照顺序完成的。。 也就是说 unity后台有一个大的update 在更新每一个对象上的update 从而刷新一帧。。
StartCoroutine是开启一个新线程吗?不是!
StartCoroutine 是一个假的多线程。。。
大牛你好 ,我想 通过 Resources.loadall()方法 加载图片 ,我用C# 写的, Resources .loadall()返回值 是 Object[]类型的数组吧, 我如何把它赋值到 一个 Texture[]数组上去?求指导,谢谢了·我看了你写的书,上面是js写的,js没有转换吧。
大牛你好 ,我想 通过 Resources.loadall()方法 加载图片 ,我用C# 写的, Resources .loadall()返回值 是 Object[]类型的数组吧, 我如何把它赋值到 一个 Texture[]数组上去?求指导,谢谢了·我看了你写的书,上面是js写的,js没有转换吧。
得强转一下, 因为load过来的就是 object对象。texture继承与Object
c#的 Instantiate 在真机上运行没有效果 无论是ios还是安卓 都没效果 ,但是换成js就有效果 这是什么原因?
不会的。。。
momo,你好,异步加载,基本没什么问题但是我运行到手机上的时候,加载已经ok,切换场景的时候还是略卡,有办法解决吗?
读条吧。。
感觉异步加载主要就是用在从网络端下载数据,然后导入场景的。这样客户端可以做的相对很小。MOMO知道那种类似无限地图是咋做出来的呢?比如混沌与秩序,感觉就是无限大的地图,然后一点都不卡。异步加载的话,运行到哪里加载到哪里,就感觉会一直都有进度条。
异步加载 和 异步释放, 无限大的地图是可以做出 。。 随机的加载新的游戏对象, 释放老的游戏对象 无限的循环下去, 没问题的。
MOMO你好,请教个问题,就是Test.cs和Main.cs。我把这两个脚本挂载到相机上面的时候,结果显示不但不遍历,逐个显示模型,结果就是原main下面挂载的那个预制物体竟然还在不停地克隆,求指教。
克隆的方法 是不是写在update中了??
诡异的现象:我使用obj.active = true; 这句无效,执行完后log obj.active还是false。。。反过来使用obj.active = false就是起效的。。。什么状况
诡异啊。。
阁下这个问题多半是因为在MonoBehaviour编辑器里有变量名第一个字母写成了大写,导致你的脚本被损坏了,数据无法传出,重建一个脚本,变量名第一个字母不要写成大写就行了。
遇到个问题。用了你上面介绍的方法。可是当加载一个场景时就会卡住(用的是异步加载)。进度条完全更新不了。请问有没有什么解决方法?谢谢。
可以试试用Thread 我上面用的是协同任务, 它并不是真正意义的多线程。
遇到个囧问题,只要我调用这个加载一个大场景就会卡住,开了线程还是一样,怎么和直接加载没区别呢。。。。
StartCoroutine(loadScene()); ?异步任务??
用了的。。。。
MOMO 你好,买到您写的书了。挺好的,希望有时间出本更深点的哈哈。说说这个异步,场景小了不需要,场景大的话还是会卡住呢。不知道为什么了,有时间回复下啊 嘿嘿。还有获取不了进度。。。感觉不准确的样子
你可以先进入场景然后异步激活
请问怎么做得到类似于在当前场景根据上个场景选的参数去创建实例?例如在上一个场景选择了某几个角色,然后到下一个场景才下载这几个角色的资源和创建它们,我看到prefab基本都是在编辑器中拖动到到脚本上,怎么动态下载并生成需要的prefab呢?
你可以prefab写在静态类中, 两个界面切换同时访问这个静态类中的对象, 然后在创建在场景中即可
请问全局类,Globe在A场景销毁时,是怎么保存下来的?
Globe 不属于任何一个场景 它是凌驾于整个工程之上 所以它不会被释放掉
你好,momo,异步加载方式的话,当加载c的时候,b的进度条运行到某个地方(还不到100%)会画面卡住,之后就直接跳转到c,请问这是什么原因?谢谢
Hi MOMO,我用你的方法尝试了加载新scene的进度,但是返回值一直是0。
请问你有没有demo project可以给我参考一下?
请问一下,我现在想在游戏开始的splash界面就将play界面的资源加载完,然后在homepage界面点击play按钮直接进入play界面,应当如何实现呢?上述两种方法异步加载都要进入新的界面,所以我这种逻辑貌似用不了。
MOMO大神,请问如何实现Unity界面间的交互啊?就是在一个界面选择个player 然后 运行游戏,游戏中的player就是你之前选择的那个!我是个新手,求教大神帮忙!谢谢!![[给力] [给力]](http://img.t.sinajs.cn/t35/style/images/common/face/ext/normal/c9/geili_org.gif)
static 场景1的时候写入player的名称 然后进入场景2 在去取play的名称
OK!
谢谢啦,我试试!