首页 > 代码库 > Unity3D中的Coroutine详解

Unity3D中的Coroutine详解

Unity中的coroutine是通过yield expression;来实现的。官方脚本中到处会看到这样的代码。

疑问:

yield是什么?

Coroutine是什么?

unity的coroutine程序执行流程怎么那么奇怪?

unity中的coroutine原理是什么,怎么实现的?

使用unity的coroutine需要注意什么问题?


一、yield的在几种语言中的程序执行特性

     Lua中的yield是使得协同函数运行->挂起并且传递参数给resume。resume使得协同函数挂起->运行并且传递参数给协同函数。

     C#中yield return/break是用于函数查询集合生成器里面的值(类似迭代)返回,并记录当前现场,下次查询时从上一次记录的yield现场处,继续往下执行,直到继续往下执行没有了,那么退出这段yield的逻辑。yield break会终止掉yield迭代逻辑并跳出。
YieldImplementation:
   1).Caller callsfunction
   2).Caller requestsitem 按需请求一个元素
   3).Next itemreturned 返回请求的元素
   4).Goto step #2

    Python中的yield expression, 有yield的函数就变成了一个生成器,调用该函数返回的是迭代器对象,用迭代器对象调用next方法(或者循环中会自动调用next方法),才开始执行函数,执行到yield expression处则中断,返回迭代器当前的值,并保留现场,下次调用next则从现场处开始执行,迭代完了就停止了。可以看出Python中的yield和C#中的yield是类似的,用于创建生成器,执行时中断返回迭代器值,并记录现场,下次从现场处继续执行。

   Unity中的yield就是和C#,python中的类似,因为unity是基于.net框架的,且unity脚本开始是用Boo(Python的一个变种)写的。只是unity中多了coroutine特性类型,和StartCoroutine的coroutine管理类。StartCoroutine不是启动了一个新的线程,而是开启一个协同程序,默认unity所有代码都在一个线程中(http://answers.unity3d.com/questions/280597/new-thread-vs-startcoroutine.html)。

 

二、Unity的Coroutine执行现象

第一种方法:  

 voidStart()

    {

       print("Starting " +Time.time);----------------------------------------1

       StartCoroutine(WaitAndPrint(2));-------------------------------------2

       print("Done " +Time.time);-------------------------------------------3

    }

   IEnumerator WaitAndPrint(float waitTime)

    {

       yield return new WaitForSeconds(waitTime);------------------------4

       print("WaitAndPrint " + Time.time);----------------------------------5

    }

该段代码的执行顺序是12435

IEnumerator Start()

    {

       print("Starting " +Time.time);----------------------------------------1

       yield return StartCoroutine(WaitAndPrint(2.0F));------------------------2

       print("Done " +Time.time);------------------------------------------3

    }

   IEnumerator WaitAndPrint(float waitTime)

    {

       yield return new WaitForSeconds(waitTime);----------------------------4

       print("WaitAndPrint " + Time.time);-----------------------------------------5

    }

该段代码的执行顺序是12453

Why?这么奇怪的执行方式。


三、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关键字。


四、Unity中的Coroutine原理猜测

虚拟机分段执行机制, 同类型嵌套用栈存放实现串行执行:.NET虚拟机在每一帧循环中, 会依次进入每个编译器预定义好的入口。对于Coroutine类型,编译器需要产生一些代码,在Coroutine类型指定的时间或事件完成后(可通过包含不同Coroutine迭代器的多个管理类管理各个coroutine, coroutine子类通过多态检查时间或事件到达,所有权交给coroutine子类中断的现场;或是用的.net的虚拟机用函数指针进行标记管理现场和在流程中定期检查时间或者事件满足后发生消息,所有权交给yield中断的现场),从yield中断后的代码处继续往下执行, 这样就形成了我们看到的一个function能分段执行的机制

而对于嵌套Coroutine类型,会串行的执行而不是并行的,可能.net虚拟机对于coroutine类型用栈存放,栈顶的先执行,从而实现串行执行,如果外层的不使用yield return,那么不会串行执行,而是并行执行。

原理图:


 

 见:http://www.richardfine.co.uk/2012/10/unity3d-monobehaviour-lifecycle/


五、Unity中使用Coroutine需要注意的问题:

1.使用的地方和不能使用的地方:

必须在MonoBehaviour或继承于MonoBehaviour的类中调用 yield coroutine。yield不可以在Update或者FixedUpdate里使用。

 

2.开启协程:

StartCoroutine(string methodName)和StartCoroutine(IEnumeratorroutine)都可以开启一个协程,

区别:

使用字符串作为参数时,开启协程时最多只能传递一个参数,并且性能消耗会更大一点; 而使用IEnumerator 作为参数则没有这个限制。

 

3.删除协程:

1).在Unity3D中,使用StopCoroutine(stringmethodName)来终止该MonoBehaviour指定方法名的一个协同程序,使用StopAllCoroutines()来终止所有该MonoBehaviour可以终止的协同程序。

包括StartCoroutine(IEnumerator routine)的。

2).还有一种方法可以终止协同程序,即将协同程序所在gameobject的active属性设置为false,当再次设置active为ture时,协同程序并不会再开启;

如是将协同程序所在脚本的enabled设置为false则不会生效。

 

4.js和C#中使用区别:

在C#中要使用 yield return而不是yield。

C#中yield(中断)语句必须要在IEnumerator类型里,C#方法的返回类型为IEnumerator,返回值如(eg:yield return new WaitForSeconds(2); 或者 yield returnnull);

 

5.协程函数返回值和参数类型,组合的设计模式:

协同程序的返回类型为Coroutine类型。在Unity3D中,Coroutine类继承于YieldInstruction,所以,协同程序的返回类型只能为null、等待的帧数(frame)以及等待的时间。

协同程序的参数不能指定ref、out参数。但是,我们在使用WWW类时会经常使用到协同程序,由于在协同程序中不能传递参数地址(引用),也不能输出对象,

这使得每下载一个WWW对象都得重写一个协同程序,解决这个问题的方法是建立一个基于WWW的类(用组合模式来解决),并实现一个下载方法。如下:

 

using UnityEngine;

using System.Collections;

public class WWWObject : MonoBehaviour

{

 public WWW www;

 

 public WWWObject(string url)

 {

 if(GameVar.wwwCache)

  www = WWW.LoadFromCacheOrDownload(url, GameVar.version);

 else

  www = new WWW(url);

 }

 

 public IEnumerator Load()

 {

 Debug.Log("Start loading : " + www.url);

 while(!www.isDone)

  {

  if(GameVar.gameState == GameState.Jumping || GameVar.gameState ==GameState.JumpingAsync)

   LoadScene.progress = www.progress;

  

  yield return 1;

  }

 if(www.error != null)

  Debug.LogError("Loading error : " + www.url + "\n" +www.error);

 else

  Debug.Log("End loading : " + www.url);

 }

 

 public IEnumerator LoadWithTip(string resourcesName)

 {

 Debug.Log("Start loading : " + www.url);

 LoadScene.tipStr = "Downloading  resources<" + resourcesName + "> . . .";

 while(!www.isDone)

  {

  if(GameVar.gameState == GameState.Jumping || GameVar.gameState ==GameState.JumpingAsync)

    LoadScene.progress= www.progress;

  

  yield return 1;

  }

 if(www.error != null)

  Debug.LogError("Loading error : " + www.url + "\n" +www.error);

 else

  Debug.Log("End loading : " + www.url);

 }

}

调用:

using UnityEngine;

using System.Collections;

using System.Collections.Generic;

public class LoadResources : MonoBehaviour

{

 static string url ="http://61.149.211.88/Package/test.unity3d";

 public static WWW www = null;

 IEnumerator Start()

 {

 if(!GameVar.resourcesLoaded)

 { 

  GameVar.gameState = GameState.Jumping;

  

  WWWObject obj = new WWWObject(url);

  www = obj.www;

  yield return StartCoroutine(obj.LoadWithTip("Textures"));

  

  GameVar.resourcesLoaded = true;

  GameVar.gameState = GameState.Run;

  }

 }

}

参考文章:

http://game.ceeger.com/forum/read.php?tid=13148

http://www.zhihu.com/question/23895384

http://blog.csdn.net/tkokof1/article/details/11842673

http://blog.csdn.net/tkokof1/article/details/12834939

http://www.richardfine.co.uk/2012/10/unity3d-monobehaviour-lifecycle/

Unity3D中的Coroutine详解