Unity3D中的Coroutine 与 Yield

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

            http://www.cnblogs.com/manuosex/p/3726475.html


Yield的基本用法:

最近,需要需要用unity 3d做点东西,但是了碰到了延迟加载问题,我总结余下:
  Coroutines & Yield是unity3d编程中重要的概念,它可以实现将一段程序延迟执行或者将其各个部分分布在一个时间段内连续执行,但是在Javascript与C#中实现Coroutines & Yield,在语法上却有一些区别:
  javascript中yield用法很简单,直接yield就行了,或者yield WaitForSeconds (2);
  c#中的用法如下:
  yield不可单独使用
  需要与return配合使用,例如:
  1 yield return 0; //等0帧
  2 yield return 1; //等1帧
  3 yield return WaitForSeconds(3.0); //等待3秒
  所有使用yield的函数必须将返回值类型设置为IEnumerator类型,例如:

IEnumerator DoSomeThingInDelay() {...}
最后,也是在”Using C#”这个章节中没有讲到的关键一点是,所有IEnumerator类型函数必须使用”StartCoroutine”这个函数触发,不能单独使用,例如:

StartCoroutine(DoSomeThingInDelay());
这就是yield的用法。

StartCoroutine(DoSomeThingInDelay());

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类型指定的时间或事件完成后(.net的虚拟机用函数指针进行标记管理现场和在流程中每帧检查时间或者事件满足后发送消息,将cpu所有权交给yield中断的现场或是通过包含不同Coroutine迭代器的多个管理类管理各个coroutine, 每帧用coroutine子类通过多态检查时间或事件到达,将cpu所有权交给coroutine子类中断的现场),从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/

你可能感兴趣的:(Game)