您现在的位置是:首页 > 前端 > UnityUnity

[Unity 3d] Unity3D中的协程(Coroutine)详解

Tdou2020-03-13【Unity】人已围观

简介Unity3D中的协程(Coroutine)详解


为什么需要协程
 
在游戏中有许多过程(Process)需要花费多个逻辑帧去计算。你会遇到“密集”的流程,比如说寻路,寻路计算量非常大,所以我们通常会把它分割到不同的逻辑帧去进行计算,以免影响游戏的帧率。
 
你会遇到“稀疏”的流程,比如说游戏中的触发器,这种触发器大多数时候什么也不做,但是一旦被调用会做非常重要的事情(比图说游戏中自动开启的门就是在门前放了一个Empty Object作为trigger,人到门前就会触发事件)。
 
不管什么时候,如果你想创建一个能够历经多个逻辑帧的流程,但是却不使用多线程,那你就需要把一个任务来分割成多个任务,然后在下一帧继续执行这个任务。
 
比如,A*算法是一个拥有主循环的算法,它拥有一个open list来记录它没有处理到的节点,那么我们为了不影响帧率,可以让A*算法在每个逻辑帧中只处理open list中一部分节点,来保证帧率不被影响(这种做法叫做time slicing)。
 
再比如,我们在处理网络传输问题时,经常需要处理异步传输,需要等文件下载完毕之后再执行其他任务,一般我们使用回调来解决这个问题,但是Unity使用协程可以更加自然的解决这个问题,如下边的程序:
 
  1. private IEnumerator Test()  
  2. {  
  3.     WWW www = newWWW(ASSEST_URL);  
  4.     yield return www;  
  5.     AssetBundle bundle = www.assetBundle;
  6. }
 
 
协程是什么
 
 
从程序结构的角度来讲,协程是一个有限状态机,这样说可能并不是很明白,说到协程(Coroutine),我们还要提到另一样东西,那就是子例程(Subroutine),子例程一般可以指函数,函数是没有状态的,等到它return之后,它的所有局部变量就消失了,但是在协程中我们可以在一个函数里多次返回,局部变量被当作状态保存在协程函数中,知道最后一次return,协程的状态才别清除。
 
简单来说,协程就是:你可以写一段顺序的代码,然后标明哪里需要暂停,然后在下一帧或者一段时间后,系统会继续执行这段代码。


协程怎么用?
 
      一个简单的C#代码,如下:
  1. IEnumeratorLongComputation()
  2. {
  3.     while(someCondition)
  4.     {
  5.         /* 做一系列的工作 */
  6.         // 在这里暂停然后在下一帧继续执行
  7.         yield return null;
  8.     }
  9. }
 
协程是怎么工作的
 
注意上边的代码示例,你会发现一个协程函数的返回值是IEnumerator,它是一个迭代器,你可以把它当成指向一个序列的某个节点的指针,它提供了两个重要的接口,分别是Current(返回当前指向的元素)和MoveNext()(将指针向前移动一个单位,如果移动成功,则返回true)。IEnumerator是一个interface,所以你不用担心的具体实现。
 
通常,如果你想实现一个接口,你可以写一个类,实现成员,等等。迭代器块(iterator block)是一个方便的方式实现IEnumerator没有任何麻烦-你只是遵循一些规则,并实现IEnumerator由编译器自动生成。
 
一个迭代器块具备如下特征:
 
      1.    返回IEnumerator
      2.    使用yield关键字
 
所以yield关键词是干啥的?它声明序列中的下一个值或者是一个无意义的值。如果使用yield x(x是指一个具体的对象或数值)的话,那么movenext返回为true并且current被赋值为x,如果使用yield break使得movenext()返回false。
那么我举例如下,这是一个迭代器块:
  1. public void Consumer()
  2. {
  3.     foreach(int i in Integers())
  4.     {
  5.         Console.WriteLine(i.ToString());
  6.     }
  7. }
  8. public IEnumerable<int>Integers()
  9. {
  10.     yield return 1;
  11.     yield return 2;
  12.     yield return 4;
  13.     yield return 8;
  14.     yield return 16;
  15.     yield return 16777216;
  16. }
 
注意上文在迭代的过程中,你会发现,在两个yield之间的代码只有执行完毕之后,才会执行下一个yield,在Unity中,我们正是利用了这一点,我们可以写出下面这样的代码作为一个迭代器块:
 
  1. IEnumeratorTellMeASecret(){
  2.   PlayAnimation("LeanInConspiratorially");
  3.   while(playingAnimation)
  4.     yield return null;
  5.   Say("I stole the cookie from the cookie jar!");
  6.   while(speaking)
  7.     yield return null;
  8.   PlayAnimation("LeanOutRelieved");
  9.   while(playingAnimation)
  10.     yield return null;
  11. }
 
然后我们可以使用下文这样的客户代码,来调用上文的程序,就可以实现延时的效果。
  1. IEnumerator e =TellMeASecret();
  2. while(e.MoveNext()) {
  3.     // do whatever you like
  4. }
 
 
协程是如何实现延时的?
 
如你所见,yield return返回的值并不一定是有意义的,如null,但是我们更感兴趣的是,如何使用这个yield return的返回值来实现一些有趣的效果。
 
Unity声明了YieldInstruction来作为所有返回值的基类,并且提供了几种常用的继承类,如WaitForSeconds(暂停一段时间继续执行),WaitForEndOfFrame(暂停到下一帧继续执行)等等。更巧妙的是yield 也可以返回一个Coroutine真身,Coroutine A返回一个Coroutine B本身的时候,即等到B做完了再执行A。下面有详细说明:

 
  1. Normal coroutineupdates are run after the Update function returns. A coroutine is a function that can suspend its execution (yield)until the given YieldInstruction finishes. Different uses of Coroutines:
  2. yield; The coroutine will continue afterall Update functions have been called on the next frame.
  3. yield WaitForSeconds(2);Continue after a specified time delay, after all Update functions have beencalled for the frame
  4. yield WaitForFixedUpdate(); Continue after allFixedUpdate has been called on all scripts
  5. yield WWW Continue after a WWW download hascompleted.
  6. yield StartCoroutine(MyFunc); Chains thecoroutine, and will wait for the MyFunc coroutine to complete first.
 
 
实现延时的关键代码是在StartCoroutine里面,以为笔者也没有见过Unity的源码,那么我只能猜想StartCoroutine这个函数的内部构造应该是这样的:
  1. List<IEnumerator>unblockedCoroutines;
  2. List<IEnumerator>shouldRunNextFrame;
  3. List<IEnumerator>shouldRunAtEndOfFrame;
  4. SortedList<float, IEnumerator> shouldRunAfterTimes;
  5. foreach(IEnumerator coroutine in unblockedCoroutines){
  6.     if(!coroutine.MoveNext())
  7.         // This coroutine has finished
  8.         continue;
  9.     if(!coroutine.Current isYieldInstruction)
  10.     {
  11.         // This coroutine yielded null, or some other value wedon't understand; run it next frame.
  12.         shouldRunNextFrame.Add(coroutine);
  13.         continue;
  14.     }
  15.     if(coroutine.Current isWaitForSeconds)
  16.     {
  17.         WaitForSeconds wait =(WaitForSeconds)coroutine.Current;
  18.         shouldRunAfterTimes.Add(Time.time +wait.duration, coroutine);
  19.     }
  20.     else if(coroutine.Current isWaitForEndOfFrame)
  21.     {
  22.         shouldRunAtEndOfFrame.Add(coroutine);
  23.     }
  24.     else /* similar stuff for other YieldInstruction subtypes */}
  25. unblockedCoroutines= shouldRunNextFrame;
 
当然了,我们还可以为YieldInstruction添加各种的子类,比如一个很容易想到的就是yield return new WaitForNotification(“GameOver”)来等待某个消息的触发. 文章来源于【狗刨学习网
 
 
还有些更好玩的?
 
第一个有趣的地方是,yield return可以返回任意YieldInstruction,所以我们可以在这里加上一些条件判断:
  1. YieldInstructiony;
  2. if(something)
  3. y = null;else if(somethingElse)
  4. y = new WaitForEndOfFrame();else
  5. y = new WaitForSeconds(1.0f);
  6. yield return y;
 
第二个,由于一个协程只是一个迭代器块而已,所以你也可以自己遍历它,这在一些场景下很有用,例如在对协程是否执行加上条件判断的时候:
  1. IEnumeratorDoSomething(){
  2.   /* ... */}
  3. IEnumeratorDoSomethingUnlessInterrupted(){
  4.   IEnumerator e = DoSomething();
  5.   bool interrupted = false;
  6.   while(!interrupted)
  7.   {
  8.     e.MoveNext();
  9.     yield return e.Current;
  10.     interrupted = HasBeenInterrupted();
  11.   }}
 
第三个,由于协程可以yield协程,所以我们可以自己创建一个协程函数,如下:
  1. IEnumeratorUntilTrueCoroutine(Func fn){
  2.    while(!fn()) yield return null;}
  3. CoroutineUntilTrue(Func fn){
  4.   return StartCoroutine(UntilTrueCoroutine(fn));}
  5. IEnumeratorSomeTask(){
  6.   /* ... */
  7.   yield return UntilTrue(() => _lives < 3);
  8.   /* ... */}
 
 

Tags:Unity   协成   Coroutine

很赞哦! ()

  • 微信收款码
  • 支付宝收款码
打赏

文章评论

站点信息

  • 建站时间:2019-12-11
  • 网站程序:帝国CMS7.5
  • 主题模板《今夕何夕》
  • 文章统计52篇文章
  • 标签管理标签云
  • 统计数据百度统计
  • 微信公众号:扫描二维码,关注我们