Unity3D 关于Coroutine(协程)使用的研究笔记及关于yield的理解

时间:2021-09-23 23:34:30

我先亮出我用的有关协程的代码:

using UnityEngine;
using System.Collections;

public class NewBehaviourScript : MonoBehaviour {
//本地图片的纹理
private Texture tex0;
//网络图片的纹理
private Texture tex1;
//加载本地图片
IEnumerator loadLocal()
{
if (tex0 == null) {
WWW date = new WWW("file://" + Application.dataPath + "/0.png");
yield return null;
tex0 = date.texture;
}
GetComponent<Renderer> ().material.mainTexture = tex0;

}
//加载网络图片
IEnumerator loadNetWork()
{
if (tex1 == null)
{
WWW date = new WWW ("http://pic39.nipic.com/20140321/17561764_000020626150_2.jpg");
while (!date.isDone)
{
yield return null;
Debug.Log ("时间:" + Time.time + " " + "进度:" + date.progress);
}
tex1 = date.texture;
}
GetComponent<Renderer> ().material.mainTexture = tex1;
}
void OnGUI()
{
if (GUILayout.Button ("load local pic")) {
StartCoroutine (loadLocal ());
}
if (GUILayout.Button ("load net pic")) {
StartCoroutine (loadNetWork());
}
}
// Use this for initialization
void Start () {

}

// Update is called once per frame
void Update () {

}
}

如上所示,我的目的是通过点击Button本别加载本地图片与网络图片到Panel上。

既然是加载,那么加载就需要耗费时间,于是这里使用了协程,以保证图片加载完成后,再把图片赋值给mainTexture;

第一步:

在Button的响应中使用

StartCoroutine(loadNetWork())启动一个协程程序.

关于StartCoroutine

1.这个函数的参数,必须是一个返回值为IEnumerator类型的函数。

2.StartCoroutine不是启动了一个新的线程,而是开启一个协同程序,默认unity所有代码都在一个线程中

第二部:

执行loadNetWork函数

IEnumerator loadNetWork()
{
if (tex1 == null)
{
WWW date = new WWW ("http://pic39.nipic.com/20140321/17561764_000020626150_2.jpg");
/*while (!date.isDone)
{
yield return null;
Debug.Log ("时间:" + Time.time + " " + "进度:" + date.progress);
}*/
yield return date;
tex1 = date.texture;
}
GetComponent<Renderer> ().material.mainTexture = tex1;
}
我通过每句加输出语句观察到的现象是,程序执行完yield return date,之后停住了!,然后去执行了主程序中的下面的程序,然后接着执行了tex1 = date.texture;然后图片就显示出来了。我觉得好神奇,做了如下实验:

我把网断了,然后我发现程序在执行完yield return date后,很长时间没有反应,然后弹出了错误,说的是网络有问题访问不到之类的。

我把yield return date, 改成了yield return null;结果是,程序执行下一句了,不过下一句告知纹理还没有获取到,请使用isDone判断是否加载完成再执行(you are trying to load data from a www stream which has not completed the download yet.You need to yield the download or wait until isDone returns true)
那他既然让我使用isDone那我就使用呗,如下:

WWW date = new WWW ("http://pic39.nipic.com/20140321/17561764_000020626150_2.jpg");
while (!date.isDone)
{
yield return null;
Debug.Log ("时间:" + Time.time + " " + "进度:" + date.progress);
}
tex1 = date.texture;

然后我发现我打印的进度一直在增长,最后就加载OK了!

于是我想当然的认为下面这种方式也可以,只要s够成:

IEnumerator loadNetWork()
{
if (tex1 == null)
{
WWW date = new WWW ("http://pic39.nipic.com/20140321/17561764_000020626150_2.jpg");
/*while (!date.isDone)
{
yield return null;
Debug.Log ("时间:" + Time.time + " " + "进度:" + date.progress);
}*/
yield return new WaitForSeconds(10);
tex1 = date.texture;
}
GetComponent<Renderer> ().material.mainTexture = tex1;
}
果然成功了。

于是我合理推测,如果是yield return date, 其中我返回的这个date,程序绝对做了处理,也就是说他们认识这个www类型,然后会帮忙进行加载,加载完毕后,才返回。而如果是null,或者wait之类的,由于程序没有得到date,也就没法特殊处理了,所以只有等得够久才有收获。

现在我对上面这段函数解释一番:

这个函数关键的就是这么几句话:

1)创建WWW对象加载资源

这个资源的加载是个异步的过程,也就是说new WWW(...)执行完了之后,不用等待加载结果,直接执行下一句。正因为是异步的过程,所以才有了下面一行代码;

2)循环判断date.isDone是否为true,如果为true,则说明资源加载完毕,可以执行纹理赋值贴图过程。

3)如果资源未加载完毕,则执行yield return null。

      关键就是解释这一句,我之前两年C#编程工作并没有接触到这个东西,这次看到后,感叹一句:这是个什么神马?!

      一句话说就是:yield,用于创建生成器,执行时中断返回迭代器值,并记录现场,下次从现场处继续执行。

这里有两点:   1.Q:这句话的“下次”是啥时候?

    A:这个总的来说,应该是下一帧,应该是Update执行后。有一个图可以参考一下:

Unity3D 关于Coroutine(协程)使用的研究笔记及关于yield的理解

2.Q:返回的值干什么用?

    A:返回的值有很多类型,我引用下面这段来解释:

Unity官方文档对coroutine的解释

Normal coroutine updates are run after theUpdate function returns. Acoroutine is a function that can suspend its execution (yield) until the givenYieldInstruction finishes. Different uses of Coroutines:

 

yield; The coroutine will continue after all Update functionshave been calledon the next frame.

yield WaitForSeconds(2); Continueafter a specified time delay, after all Update functions have been called for theframe.

yield WaitForFixedUpdate(); Continue afterall FixedUpdate has been called on all scripts.

yield WWWContinue aftera WWW download has completed

yield StartCoroutine(MyFunc); Chains the coroutine, and will wait for the MyFunc coroutine to completefirst.

 C#要在yield coroutine之间加上return关键字。

看吧,上面这段文字完美的解释了我关于www的疑问,果不其然,yield有这么几种返回类型,其中WWWContinue的用法就是当下载完毕才会返回的,我也找到了另一处对返回值类型的说明,感觉描述不一样,但是意思一样,当个补充吧,如下:

WWW - after Updates happen for all game objects; check the isDone flag. If true, call the IEnumerator's MoveNext() function;

WaitForSeconds - after Updates happen for all game objects; check if the time has elapsed, if it has, call MoveNext();

null or some unknown value - after Updates happen for all game objects; Call MoveNext();

WaitForEndOfFrame - after Render happens for all cameras; Call MoveNext().

我单独翻译一下第一条:WWW-当该脚本绑定的所有游戏对象的Update函数执行完毕后发生,检查WWW这个返回值的isDone属性,如果这个属性为true,则调用枚举器的MoveNext函数(这也就意味着,一次yield的过程完成了,可以执行下一句了,当然如果没有下一句了,moveNext就会返回false,这个枚举过程就全部结束喽)


我这篇文章也只是记录了我的一些研究过程中的问题,只是协程整个解释中的极小一部分,我在研究过程*参考了这么三篇文章,我觉得很值得一读,读的顺序就可以按照我的下面的顺序读

Unity3D中的Coroutine详解

http://blog.csdn.net/blues1021/article/details/40959915

这个里边简单例子多,更通俗些,关键还有个关于WWW类使用协程的封装,特别好


简要分析unity3d中剪不断理还乱的yield

http://blog.csdn.net/huang9012/article/details/29595747

这个主要是有一些更丰满的例子,比如关于在对话框中逐个显示文字啦之类的,还有个对协程原理分析的摘要,也很到位


Unity协程(Coroutine)原理深入剖析再续】

http://www.manew.com/blog-53680-2637.html

这个最后读吧,因为确实讲的比较深,连关于协程的代码实现也罗列了,让人看完更清楚了


今天一天也就研究了个协程,不过研究清楚了,心里也踏实了才。睡觉去~