Unity3D Coroutine(协程)

http://blog.csdn.net/narutojzm1/article/details/50255051


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

[csharp]  view plain  copy
  1. using UnityEngine;  
  2. using System.Collections;  
  3.   
  4. public class NewBehaviourScript : MonoBehaviour {  
  5.     //本地图片的纹理  
  6.     private Texture tex0;  
  7.     //网络图片的纹理  
  8.     private Texture tex1;  
  9.     //加载本地图片  
  10.     IEnumerator loadLocal()  
  11.     {  
  12.         if (tex0 == ) {  
  13.             WWW date = new WWW("file://" + Application.dataPath + "/0.png");  
  14.             yield return ;  
  15.             tex0 = date.texture;  
  16.         }  
  17.         GetComponent ().material.mainTexture = tex0;  
  18.   
  19.     }  
  20.     //加载网络图片  
  21.     IEnumerator loadNetWork()  
  22.     {  
  23.         if (tex1 == )   
  24.         {  
  25.             WWW date = new WWW ("http://pic39.nipic.com/20140321/17561764_000020626150_2.jpg");  
  26.             while (!date.isDone)   
  27.             {  
  28.                 yield return ;  
  29.                 Debug.Log ("时间:" + Time.time + " " + "进度:" + date.progress);  
  30.             }  
  31.             tex1 = date.texture;  
  32.         }  
  33.         GetComponent ().material.mainTexture = tex1;  
  34.     }  
  35.     void OnGUI()  
  36.     {  
  37.         if (GUILayout.Button ("load local pic")) {  
  38.             StartCoroutine (loadLocal ());  
  39.         }  
  40.         if (GUILayout.Button ("load net pic")) {  
  41.             StartCoroutine (loadNetWork());  
  42.         }  
  43.     }  
  44.     // Use this for initialization  
  45.     void Start () {  
  46.           
  47.     }  
  48.       
  49.     // Update is called once per frame  
  50.     void Update () {  
  51.       
  52.     }  
  53. }  

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

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

第一步:

在Button的响应中使用

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

关于StartCoroutine

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

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

第二部:

执行loadNetWork函数

[csharp]  view plain  copy
  1. IEnumerator loadNetWork()  
  2.     {  
  3.         if (tex1 == )   
  4.         {  
  5.             WWW date = new WWW ("http://pic39.nipic.com/20140321/17561764_000020626150_2.jpg");  
  6.             /*while (!date.isDone)  
  7.             { 
  8.                 yield return null; 
  9.                 Debug.Log ("时间:" + Time.time + " " + "进度:" + date.progress); 
  10.             }*/  
  11.             yield return date;  
  12.             tex1 = date.texture;  
  13.         }  
  14.         GetComponent ().material.mainTexture = tex1;  
  15.     }  
我通过每句加输出语句观察到的现象是,程序执行完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那我就使用呗,如下:

[csharp]  view plain  copy
  1. WWW date = new WWW ("http://pic39.nipic.com/20140321/17561764_000020626150_2.jpg");  
  2.             while (!date.isDone)   
  3.             {  
  4.                 yield return ;  
  5.                 Debug.Log ("时间:" + Time.time + " " + "进度:" + date.progress);  
  6.             }  
  7.             tex1 = date.texture;  

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

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

[csharp]  view plain  copy
  1. IEnumerator loadNetWork()  
  2.     {  
  3.         if (tex1 == )   
  4.         {  
  5.             WWW date = new WWW ("http://pic39.nipic.com/20140321/17561764_000020626150_2.jpg");  
  6.             /*while (!date.isDone)  
  7.             { 
  8.                 yield return null; 
  9.                 Debug.Log ("时间:" + Time.time + " " + "进度:" + date.progress); 
  10.             }*/  
  11.             yield return new WaitForSeconds(10);  
  12.             tex1 = date.texture;  
  13.         }  
  14.         GetComponent ().material.mainTexture = tex1;  
  15.     }  
果然成功了。

于是我合理推测,如果是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执行后。有一个图可以参考一下:


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

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


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

你可能感兴趣的:(Unity3D脚本)