• 转自:http://blog.csdn.net/blues1021/article/details/40959915 ... Yield的基本用法: 最近,需要需要用unity 3d做点东西,但是了碰到了延迟加载问题,我总结余下:  Coroutines & 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/

    展开全文
  • Unity3D yield的总结

    2014-05-23 10:36:23
    可以把yield理解成一种特殊形式的return,它和return一样,会立即把执行权返回父级函数。特别之处在于,yield后面跟的函数或对象会跟一个条件判断,当条件满足时,就会再次回调包含该yield的子函数,并且从yield语句...

    可以把yield理解成一种特殊形式的return,它和return一样,会立即把执行权返回父级函数。特别之处在于,yield后面跟的函数或对象会跟一个条件判断,当条件满足时,就会再次回调包含该yield的子函数,并且从yield语句之后继续执行。条件满足之前,执行父函数下面的语句,可以看作异步执行。

    例如:

    //在c#中必须显示的指明,启动一个纤程以调用含有yield的函数。
       StartCoroutine(callYieldFunction());
    普通浏览复制代码
    1. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客//在c#中必须显示的指明,启动一个纤程以调用含有yield的函数。
    2. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客   StartCoroutine(callYieldFunction()); 
    3. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客   Debug.Log("print second"); 
    4. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客
    5. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客//在c#中含有yield的函数,返回值必须为IEnumerator
    6. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客   IEnumerator callYieldFunction()    
    7. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客   {
    8. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客Debug.Log("print first");
    9. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客yield return new WaitForSeconds(2);    
    10. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客Debug.Log("print after 2 seconds"); 
    11. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客   }
    12. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客   Debug.Log("print second"); 
    13. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客
    14. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客//在c#中含有yield的函数,返回值必须为IEnumerator
    15. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客   IEnumerator callYieldFunction()    
    16. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客   {
    17. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客Debug.Log("print first");
    18. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客yield return new WaitForSeconds(2);    
    19. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客Debug.Log("print after 2 seconds"); 
    20. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客   } 
    21. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客 


    当没有父函数可以返回,本身已经是顶级函数的时候,yield的条件相当于同步执行,程序一直等到条件满足,才继续执行下面的语句。

    c#:
    普通浏览复制代码
    1. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客IEnumerator Start()     //注意c#中的返回值
    2. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客{
    3. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客   Debug.Log("print first");
    4. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客   yield return new WaitForSeconds(2);    
    5. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客   Debug.Log("print after 2 seconds"); 
    6. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客}



    在理解了这个之后,就可以理解使用嵌套的yield,来实现同步的子纤程调用。
    例如:因为start函数已经是顶级函数,所以外层的yield会”死在这里“,直到嵌套的纤程执行完毕,再继续执行。

    c#:
    普通浏览复制代码
    1. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客IEnumerator Start() 
    2. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客{
    3. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客   yield return StartCoroutine("callYieldFunction");    
    4. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客   Debug.Log("print latest");
    5. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客}
    6. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客
    7. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客IEnumerator callYieldFunction()    
    8. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客{
    9. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客    Debug.Log("print first");
    10. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客    yield return new WaitForSeconds(2);    
    11. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客    Debug.Log("print after 2 seconds"); 
    12. Unity3D yield的总结【转】 - 疯狂加加林 - 疯狂加加林的博客}  

    展开全文
  • unity 3d yield 用法总结

    2016-09-13 21:40:43
     最近,需要需要用unity 3d做点东西,但是了... Coroutines & Yieldunity3d编程中重要的概念,它可以实现将一段程序延迟执行或者将其各个部分分布在一个时间段内连续执行,但是在Javascript与C#中实现Co

    转载出处:http://www.cnblogs.com/manuosex/p/3726475.html


     最近,需要需要用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的用法。

    1
    1 StartCoroutine(DoSomeThingInDelay());

    展开全文
  • 在学习unity3d的时候很容易看到下面这个例子: 1 void Start () { 2 StartCoroutine(Destroy()); 3 } 4 5 IEnumerator Destroy(){ 6 yield return WaitForSeconds(3.0f); 7 Destroy(gameObject); 8 } ...

     在学习unity3d的时候很容易看到下面这个例子:

    复制代码
    1 void Start () {
    2     StartCoroutine(Destroy());
    3 }
    4 
    5 IEnumerator Destroy(){
    6     yield return WaitForSeconds(3.0f);
    7     Destroy(gameObject);
    8 }
    复制代码

      这个函数干的事情很简单:调用StartCoroutine函数开启协程,yield等待一段时间后,销毁这个对象;由于是协程在等待,所以不影响主线程操作。一般来说,看到这里的时候都还不会晕,yield就是延时一段时间以后继续往下执行呗,恩,学会了,看着还蛮好用的。

    ====================================================分割线====================================================

      当然,yield能干的事情远远不止这种简单的特定时间的延时,例如可以在下一帧继续执行这段代码(yield return null),可以在下一次执行FixedUpdate的时候继续执行这段代码(yield new WaitForFixedUpdate ();),可以让异步操作(如LoadLevelAsync)在完成以后继续执行,可以……可以让你看到头晕。

      unity3d官方对于协程的解释是:一个协同程序在执行过程中,可以在任意位置使用yield语句。yield的返回值控制何时恢复协同程序向下执行。协同程序在对象自有帧执行过程中堪称优秀。协同程序在性能上没有更多的开销。StartCoroutine函数是立刻返回的,但是yield可以延迟结果。直到协同程序执行完毕。(原文:The execution of a coroutine can be paused at any point using the yield statement. The yield return value specifies when the coroutine is resumed. Coroutines are excellent when modelling behaviour over several frames. Coroutines have virtually no performance overhead. StartCoroutine function always returns immediately, however you can yield the result. This will wait until the coroutine has finished execution.)

      如果只是认为yield用于延时,那么可以用的很顺畅;但是若看到yield还有这么多功能,目测瞬间就凌乱了,更不要说活学活用了。不过,如果从原理上进行理解,就很容易理清yield的各种功能了。

    C#中的yield

    复制代码
     1 public static IEnumerable<int> GenerateFibonacci()
     2 {
     3     yield return 0;
     4     yield return 1;
     5 
     6     int last0 = 0, last1 = 1, current;
     7 
     8     while (true)
     9     {
    10         current = last0 + last1;
    11         yield return current;
    12 
    13         last0 = last1;
    14         last1 = current;
    15     }
    16 }
    复制代码

      yield return的作用是在执行到这行代码之后,将控制权立即交还给外部。yield return之后的代码会在外部代码再次调用MoveNext时才会执行,直到下一个yield return——或是迭代结束。虽然上面的代码看似有个死循环,但事实上在循环内部我们始终会把控制权交还给外部,这就由外部来决定何时中止这次迭代。有了yield之后,我们便可以利用“死循环”,我们可以写出含义明确的“无限的”斐波那契数列。转自老赵的博客

      IEnumerable与IEnumerator的区别比较小,在unity3d中只用到IEnumerator,功能和IEnumerable类似。至于他们的区别是什么,网上搜了半天,还是模糊不清,有童鞋能解释清楚的请留言。不过对于这段代码对于unity3d中yield的理解已经足够了。

    游戏中需要使用yield的场景

      既然要使用yield,就得给个理由吧,不能为了使用yield而使用yield。那么先来看看游戏中可以用得到yield的场景:

    • 游戏结算分数时,分数从0逐渐上涨,而不是直接显示最终分数

    • 人物对话时,文字一个一个很快的出现,而不是一下突然出现
    • 10、9、8……0的倒计时

    • 某些游戏(如拳皇)掉血时血条UI逐渐减少,而不是突然降低到当前血量

    …………………………

    unity3d中yield应用举例

      首先是官网的一段代码:

    复制代码
     1 using UnityEngine;
     2 using System.Collections;
     3 
     4 public class yield1 : MonoBehaviour {
     5 
     6     IEnumerator Do() {
     7         print("Do now");
     8         yield return new WaitForSeconds(2);
     9         print("Do 2 seconds later");
    10     }
    11     void Awake() {
    12         StartCoroutine(Do());
    13         print("This is printed immediately");
    14     }
    15 
    16     // Use this for initialization
    17     void Start () {
    18     
    19     }
    20     
    21     // Update is called once per frame
    22     void Update () {
    23     
    24     }
    25 }
    复制代码

      这个例子将执行Do,但是Do函数之后的print指令会立刻执行。这个例子没有什么实际意义,只是为了验证一下yield确实是有延时的。

      下面来看看两段显示人物对话的代码(对话随便复制了一段内容),功能是一样的,但是方法不一样:

    复制代码
     1 using UnityEngine;
     2 using System.Collections;
     3 
     4 public class dialog_easy : MonoBehaviour {
     5     public string dialogStr = "yield return的作用是在执行到这行代码之后,将控制权立即交还给外部。yield return之后的代码会在外部代码再次调用MoveNext时才会执行,直到下一个yield return——或是迭代结束。虽然上面的代码看似有个死循环,但事实上在循环内部我们始终会把控制权交还给外部,这就由外部来决定何时中止这次迭代。有了yield之后,我们便可以利用“死循环”,我们可以写出含义明确的“无限的”斐波那契数列。";
     6     public float speed = 5.0f;
     7 
     8     private float timeSum = 0.0f;
     9     private bool isShowing = false;
    10     // Use this for initialization
    11     void Start () {
    12         ShowDialog();
    13     }
    14     
    15     // Update is called once per frame
    16     void Update () {
    17         if(isShowing){
    18             timeSum += speed * Time.deltaTime;
    19             guiText.text = dialogStr.Substring(0, System.Convert.ToInt32(timeSum));
    20 
    21             if(guiText.text.Length == dialogStr.Length)
    22                 isShowing = false;
    23         }
    24     }
    25 
    26     void ShowDialog(){
    27         isShowing = true;
    28         timeSum = 0.0f;
    29     }
    30 }
    复制代码

      这段代码实现了在GUIText中逐渐显示一个字符串的功能,速度为每秒5个字,这也是新手常用的方式。如果只是简单的在GUIText中显示一段文字,ShowDialog()函数可以做的很好;但是如果要让字一个一个蹦出来,就需要借助游戏的循环了,最简单的方式就是在Update()中更新GUIText。

      从功能角度看,这段代码完全没有问题;但是从代码封装性的角度来看,这是一段很恶心的代码,因为本应由ShowDialog()完成的功能放到了Update()中,并且在类中还有两个private变量为这个功能服务。如果将来要修改或者删除这个功能,需要在ShowDialog()和Update()中修改,并且还可能修改那两个private变量。现在代码比较简单,感觉还不算太坏,一旦Update()中再来两个类似的的功能,估计写完代码一段时间之后自己修改都费劲。

      如果通过yield return null实现帧与帧之间的同步,则代码优雅了很多:

    复制代码
     1 using UnityEngine;
     2 using System.Collections;
     3 
     4 public class dialog_yield : MonoBehaviour {
     5     public string dialogStr = "yield return的作用是在执行到这行代码之后,将控制权立即交还给外部。yield return之后的代码会在外部代码再次调用MoveNext时才会执行,直到下一个yield return——或是迭代结束。虽然上面的代码看似有个死循环,但事实上在循环内部我们始终会把控制权交还给外部,这就由外部来决定何时中止这次迭代。有了yield之后,我们便可以利用“死循环”,我们可以写出含义明确的“无限的”斐波那契数列。";
     6     public float speed = 5.0f;
     7 
     8     // Use this for initialization
     9     void Start () {
    10         StartCoroutine(ShowDialog());
    11     }
    12     
    13     // Update is called once per frame
    14     void Update () {
    15     }
    16     
    17     IEnumerator ShowDialog(){
    18         float timeSum = 0.0f;
    19         while(guiText.text.Length < dialogStr.Length){
    20             timeSum += speed * Time.deltaTime;
    21             guiText.text = dialogStr.Substring(0, System.Convert.ToInt32(timeSum));
    22             yield return null;
    23         }
    24     }
    25 }
    复制代码

      相关代码都被封装到了ShowDialog()中,这么一来,不论是要增加、修改或删除功能,都变得容易了很多。

      根据官网手册的描述,yield return null可以让这段代码在下一帧继续执行。在ShowDialog()中,每次更新文字以后yield return null,直到这段文字被完整显示。看到这里,可能有童鞋不解:

    • 为什么在协程中也可以用Time.deltaTime?
    • 协程中的Time.deltaTime和Update()中的一样吗?
    • 这样使用协程,会不会出现与主线程访问共享资源冲突的问题?(线程的同步与互斥问题)
    • yield return null太神奇了,为什么会在下一帧继续执行这个函数?
    • 这段代码是不是相当于为ShowDialog()构造了一个自己的Update()?

      要解释这些问题,先看看unity3d中的协程是怎么运行的吧。

    协程原理分析

      本段内容转自这篇博客,想看的童鞋自己点击。

     

      首先,请你牢记:协程不是线程,也不是异步执行的。协程和 MonoBehaviour 的 Update函数一样也是在MainThread中执行的。使用协程你不用考虑同步和锁的问题。

      UnityGems.com给出了协程的定义:

      A coroutine is a function that is executed partially and, presuming suitable conditions are met, will be resumed at some point in the future until its work is done.

      协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码。Unity在每一帧(Frame)都会去处理对象上的协程。Unity主要是在Update后去处理协程(检查协程的条件是否满足):

      从上图的剖析就明白,协程跟Update()其实一样的,都是Unity每帧对会去处理的函数(如果有的话)。如果MonoBehaviour 是处于激活(active)状态的而且yield的条件满足,就会协程方法的后面代码。还可以发现:如果在一个对象的前期调用协程,协程会立即运行到第一个 yield return 语句处,如果是 yield return null ,就会在同一帧再次被唤醒。如果没有考虑这个细节就会出现一些奇怪的问题。
      注:图和结论都是从UnityGems.com 上得来的,经过验证发现与实际不符,D.S.Qiu用的是Unity 4.3.4f1 进行测试的。经过测试验证,协程至少是每帧的LateUpdate()后去运行

      协程其实就是一个IEnumerator(迭代器),IEnumerator 接口有两个方法 Current 和 MoveNext() ,迭代器方法运行到 yield return 语句时,会返回一个expression表达式并保留当前在代码中的位置。 当下次调用迭代器函数时执行从该位置重新启动。unity3d在每帧做的工作就是:调用协程(迭代器)MoveNext() 方法,如果返回 true ,就从当前位置继续往下执行。详情见这篇博客

     

      如果理解了这张图,之前显示人物对话的功能最后提到的那些疑惑也就很容易理解了:

    • 协程和Update()一样更新,自然可以使用Time.deltaTime了,而且这个Time.deltaTime和在Update()当中使用是一样的效果(使用yield return null的情况下)
    • 协程并不是多线程,它和Update()一样是在主线程中执行的,所以不需要处理线程的同步与互斥问题
    • yield return null其实没什么神奇的,只是unity3d封装以后,这个协程在下一帧就被自动调用了
    • 可以理解为ShowDialog()构造了一个自己的Update(),因为yield return null让这个函数每帧都被调用了
    展开全文
  • YieldUnity 3D编程中重要的概念,它可以实现将一段程序 延迟执行或者将其各个部分分布在一个时间段内连续执行,但是在Javascript与C#中实现Coroutines &amp; Yield,在语法上却有一些区别: yield不可单独...

    Coroutines & Yield是Unity 3D编程中重要的概念,它可以实现将一段程序 延迟执行或者将其各个部分分布在一个时间段内连续执行,但是在Javascript与C#中实现Coroutines & Yield,在语法上却有一些区别:
    yield不可单独使用
    需要与return配合使用,例如:
    1 yield return 0; //等0帧  
    2 yield return 1; //等1帧  
    3 yield return WaitForSeconds(3.0); //等待3秒
    所有使用yield的函数必须将返回值类型设置 为IEnumerator类型,例如:
    1 IEnumerator DoSomeThingInDelay() {...}
    最后,也是在”Using C#”这个章节中没有讲到的关键一点是,所有IEnumerator类型函数必须使用”StartCoroutine”这个函数触发,不能单独使用,例如:
    1 StartCoroutine(DoSomeThingInDelay());
    最后附上学习 Coroutines & Yield时所做的小例子脚本 的作用是不断随机改变材质 的颜色,演示 demo使用”V字仇杀队”中的面具。
    01 using UnityEngine;  
    02 using System.Collections;  
    03   
    04 public class RandomColor : MonoBehaviour {  
    05   
    06  public float delayInSecond = 1;  
    07  public Material targetMaterial;  
    08   
    09  // Use this for initialization  
    10  void Start () {  
    11  StartCoroutine(AutoChangeColor());  
    12  }  
    13   
    14  // Update is called once per frame  
    15  void Update () {  
    16  }  
    17   
    18  IEnumerator AutoChangeColor()  
    19  {  
    20  yield return 0; //确保Time.deltaTime为0  
    21   
    22  Color colorNew = GenerateRandomColor();  
    23  Color colorNow = targetMaterial.GetColor(&amp;quot;_Color&amp;quot;);  
    24  float timeEclapsed = 0;  
    25  for (timeEclapsed = 0; timeEclapsed &amp;lt; delayInSecond; timeEclapsed += Time.deltaTime)  
    26  {  
    27  float progress = timeEclapsed / delayInSecond;  
    28  Color colorTween = new Color(  
    29  (colorNew.r - colorNow.r) * progress + colorNow.r,  
    30  (colorNew.g - colorNow.g) * progress + colorNow.g,  
    31  (colorNew.b - colorNow.b) * progress + colorNow.b  
    32  );  
    33  targetMaterial.SetColor(&amp;quot;_Color&amp;quot;, colorTween);  
    34  yield return 1;  
    35  }  
    36   
    37  StartCoroutine(AutoChangeColor());  
    38  }  
    39   
    40  Color GenerateRandomColor(){  
    41  Color color = new Color();  
    42  color.r = Random.value;  
    43  color.g = Random.value;  
    44  color.b = Random.value;  
    45   
    46  return color;  
    47  }  
    48 }

    展开全文
  • 各位朋友,大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/...比如博主在写《[Unity3D]Unity3D游戏开发之自由视角下的角色控制》和《[Unity3D]Unity3D游戏开发之角色控制漫谈》这两篇
  • 简要分析unity3dyield

    2017-07-07 10:59:25
    在学习Unity3D的时候很容易看到下面这个例子:void Start () { StartCoroutine(Destroy()); }IEnumerator Destroy(){ yield return WaitForSeconds(3.0f); Destroy(gameObject); } 这个函数干的事情很简单:...
  • 今天我们来说说通过反编译Unity3D的AssetBundle来提取游戏资源,博主写这篇文章的目的并非是要教大家如何去破解一款基于Unity3D引擎开发的游戏,而是想通过今天这篇文章来告诉大家如何在开发Unity3D游戏的过程中保护...
  • Unity 3D IEnumerator(协程) 什么是协程? 在主线程运行时同时开启另一段逻辑处理,来协助当前程序的执行。可以用来控制运动、序列以及对象的行为。 简单来说,协程就是:你可以写一段顺序的代码,然后标明...
  • Unity3D等待函数介绍

    2014-05-23 10:32:40
    Coroutines & YieldUnity3D编程中重要的概念,它可以实现将一段程序延迟执行或者将其各个部分分布在一个时间段内连续执行,但是在Javascript与C#中实现Coroutines & Yield,在语法上却有一些区别: yield不可...
  • 写游戏代码,往往最终需要代码为连续的事件.结果会像这样: [它可以实现将一段程序延迟执行或者将其各个部分分布在一个时间段内连续执行。] private int state = 0; void Update() { if (state == 0) ...
  • 之前之前看了一天的博客,各种文章巴拉巴拉,又说到迭代器了,又贴代码了,看的我头都晕了,还是啥都不懂。最后答案还是在微软C#的官网找到了,可喜可贺,故发上来给大家看看,兴趣能赚个几百评论呢(并没有)?
  • 这是一个比较综合的项目,希望对大家学习Unity3D有所帮助,我会在文章最后给出项目代码。作为一个游戏而言,游戏策划十分重要,所以在开始今天的文章之前,我们先来了解下这个项目的策划。我们的玩家是一个飞机,
  • 大家晚上好,我是秦元培,欢迎大家关注我的博客,我的博客地址是blog.csdn.net/...所以,今天的博客的主题就是《Unity3D游戏开发之跑酷游戏项目讲解》。从博主自身来考虑这件事情,当你选择做自己热爱的事情的时
  • Unity3D的协程中,如果发生异常,是无法捕获到异常的,try catch不允许跨yield使用,finally也不能确保代码块在协程异常结束时还能被执行,所以很多时候无法知道一个协程是否正常执行结束,出现错误也不方便查找...
  • Unity3D中的Coroutine详解

    2016-05-06 11:16:16
    Unity中的Coroutine原理猜测:虚拟机分段执行机制, 同类型嵌套用栈存放实现串行执行. Unity中使用yield Coroutine需要注意的问题
  • Unity3D常见面试题

    2017-08-11 20:20:00
    Unity3D常见面试题
  • 在网络上找到的关于Unity3D的脚本,大多是使用JS写出来的,很多的地方都用到了yield这个关键字,但是在JS脚本中yield的使用方式与在C#脚本中是不同的。 在JS脚本中的方式大多如下: function Start(){ yield new ...
1 2 3 4 5 ... 20
收藏数 6,286
精华内容 2,514
热门标签