由于VR的关系,第一次接触到了Unity3D的项目,对C#Script一些语法不是很了解,特别是IEnumerator yield,在项目中大量被使用,下面谈谈对它们的理解,文章转自

作者:王选易,出处:  http://www.cnblogs.com/neverdie/ 欢迎转载 ,也请保留这段声明。如果你喜欢这篇文章,请点【推荐】。谢谢!

为什么需要协程

在游戏中有许多过程(Process)需要花费多个逻辑帧去计算。

  • 你会遇到“密集”的流程,比如说寻路,寻路计算量非常大,所以我们通常会把它分割到不同的逻辑帧去进行计算,以免影响游戏的帧率。
  • 你会遇到“稀疏”的流程,比如说游戏中的触发器,这种触发器大多数时候什么也不做,但是一旦被调用会做非常重要的事情(比图说游戏中自动开启的门就是在门前放了一个Empty Object作为trigger,人到门前就会触发事件)。

不管什么时候,如果你想创建一个能够历经多个逻辑帧的流程,但是却不使用多线程,那你就需要把一个任务来分割成多个任务,然后在下一帧继续执行这个任务。

比如,A*算法是一个拥有主循环的算法,它拥有一个open list来记录它没有处理到的节点,那么我们为了不影响帧率,可以让A*算法在每个逻辑帧中只处理open list中一部分节点,来保证帧率不被影响(这种做法叫做time slicing)。

再比如,我们在处理网络传输问题时,经常需要处理异步传输,需要等文件下载完毕之后再执行其他任务,一般我们使用回调来解决这个问题,但是Unity使用协程可以更加自然的解决这个问题,如下边的程序:

private IEnumerator Test()
{  WWW www = new WWW(ASSEST_URL);  yield return www;  AssetBundle bundle = www.assetBundle;}

协程是什么

从程序结构的角度来讲,协程是一个有限状态机,这样说可能并不是很明白,说到协程(Coroutine),我们还要提到另一样东西,那就是子例程(Subroutine),子例程一般可以指函数,函数是没有  状态 的,等到它return之后,它的所有局部变量就消失了,但是在协程中我们可以在  一个函数里多次返回, 局部变量被当作状态保存在协程函数中,知道最后一次return,协程的状态才别清除。

简单来说,协程就是:你可以写一段顺序的代码,然后标明哪里需要暂停,然后在下一帧或者一段时间后,系统会继续执行这段代码。

协程怎么用?

一个简单的C#代码,如下:

IEnumerator LongComputation()
{while(someCondition){/* 做一系列的工作 */// 在这里暂停然后在下一帧继续执行
        yield return null;}
}

协程是怎么工作的

注意上边的代码示例,你会发现一个协程函数的返回值是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。

那么我举例如下,这是一个迭代器块:

public void Consumer()
{foreach(int i in Integers()){Console.WriteLine(i.ToString());}
}public IEnumerable<int> Integers()
{yield return 1;yield return 2;yield return 4;yield return 8;yield return 16;yield return 16777216;
}

注意上文在迭代的过程中,你会发现,在两个yield之间的代码只有执行完毕之后,才会执行下一个yield,在Unity中,我们正是利用了这一点,我们可以写出下面这样的代码作为一个迭代器块:

IEnumerator TellMeASecret(){PlayAnimation("LeanInConspiratorially");while(playingAnimation)yield return null;Say("I stole the cookie from the cookie jar!");while(speaking)yield return null;PlayAnimation("LeanOutRelieved");while(playingAnimation)yield return null;
}

然后我们可以使用下文这样的客户代码,来调用上文的程序,就可以实现延时的效果。

IEnumerator e = TellMeASecret();
while(e.MoveNext()) { // do whatever you like
}

协程是如何实现延时的?

如你所见,yield return返回的值并不一定是有意义的,如null,但是我们更感兴趣的是,如何使用这个yield return的返回值来实现一些有趣的效果。

Unity声明了YieldInstruction来作为所有返回值的基类,并且提供了几种常用的继承类,如WaitForSeconds(暂停一段时间继续执行),WaitForEndOfFrame(暂停到下一帧继续执行)等等。更巧妙的是yield 也可以返回一个Coroutine真身,Coroutine A返回一个Coroutine B本身的时候,即等到B做完了再执行A。下面有详细说明:

Normal coroutine updates 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:yield; The coroutine will continue after all Update functions have been called on the next frame.
yield WaitForSeconds(2); Continue after a specified time delay, after all Update functions have been called for the frame
yield WaitForFixedUpdate(); Continue after all FixedUpdate has been called on all scripts
yield WWW Continue after a WWW download has completed.
yield StartCoroutine(MyFunc); Chains the coroutine, and will wait for the MyFunc coroutine to complete first.
实现延时的关键代码是在StartCoroutine里面,以为笔者也没有见过Unity的源码,那么我只能猜想StartCoroutine这个函数的内部构造应该是这样的:
List<IEnumerator> unblockedCoroutines;
List<IEnumerator> shouldRunNextFrame;
List<IEnumerator> shouldRunAtEndOfFrame;
SortedList<float, IEnumerator> shouldRunAfterTimes;foreach(IEnumerator coroutine in unblockedCoroutines){if(!coroutine.MoveNext())// This coroutine has finishedcontinue;if(!coroutine.Current is YieldInstruction){// This coroutine yielded null, or some other value we don't understand; run it next frame.
        shouldRunNextFrame.Add(coroutine);continue;}if(coroutine.Current is WaitForSeconds){WaitForSeconds wait = (WaitForSeconds)coroutine.Current;shouldRunAfterTimes.Add(Time.time + wait.duration, coroutine);}else if(coroutine.Current is WaitForEndOfFrame){shouldRunAtEndOfFrame.Add(coroutine);}else /* similar stuff for other YieldInstruction subtypes */}unblockedCoroutines = shouldRunNextFrame;

当然了,我们还可以为YieldInstruction添加各种的子类,比如一个很容易想到的就是yield return new WaitForNotification(“GameOver”)来等待某个消息的触发,关于Unity的消息机制可以参考这篇文章:  【Unity3D技巧】在Unity中使用事件/委托机制(event/delegate)进行GameObject之间的通信 (二) : 引入中间层NotificationCenter 。

还有些更好玩的?

第一个有趣的地方是,yield return可以返回任意YieldInstruction,所以我们可以在这里加上一些条件判断:

YieldInstruction y;if(something)y = null;else if(somethingElse)y = new WaitForEndOfFrame();elsey = new WaitForSeconds(1.0f);yield return y;

第二个,由于一个协程只是一个  迭代器块 而已,所以你也可以自己遍历它,这在一些场景下很有用,例如在对协程是否执行加上条件判断的时候:

IEnumerator DoSomething(){/* ... */}IEnumerator DoSomethingUnlessInterrupted(){IEnumerator e = DoSomething();bool interrupted = false;while(!interrupted){e.MoveNext();yield return e.Current;interrupted = HasBeenInterrupted();}}

第三个,由于协程可以yield协程,所以我们可以自己创建一个协程函数,如下:

IEnumerator UntilTrueCoroutine(Func fn){while(!fn()) yield return null;}Coroutine UntilTrue(Func fn){return StartCoroutine(UntilTrueCoroutine(fn));}IEnumerator SomeTask(){/* ... */yield return UntilTrue(() => _lives < 3);/* ... */}

IEnumerator相关推荐

  1. 理解 IEnumerable 与 IEnumerator

    我们在编码中,经常使用foreach遍历集合,能够遍历的集合必定实现了 IEnumerator接口,IEnumerator接口如下: 1 public interface IEnumerator 2 ...

  2. C# IEnumerable和IEnumerator的区别,如何实现

    IEnumerable接口和IEnumerator接口是.NET中非常重要的接口,二者有何区别? 1. 简单来说IEnumerable是一个声明式的接口,声明实现该接口的类就是"可迭代的en ...

  3. 关于迭代器中IEnumerable与IEnumerator的区别

    首先是IEnumerable与IEnumerator的定义: 1.IEnumerable接口允许使用foreach循环,包含GetEnumerator()方法,可以迭代集合中的项. 2.IEnumer ...

  4. IEnumerable和IEnumerator 详解

    IEnumerable接口是非常的简单,只包含一个抽象的方法GetEnumerator(),它返回一个可用于循环访问集合的IEnumerator对象.IEnumerator对象有什么呢?它是一个真正的 ...

  5. IEnumeratorTItem和IEnumerator Java 抽象类和普通类、接口的区别——看完你就顿悟了...

    IEnumerable 其原型至少可以说有15年历史,或者更长,它是通过 IEnumerator 来定义的,而后者中使用装箱的 object 方式来定义,也就是弱类型的.弱类型不但会有性能问题,最主要 ...

  6. IEnumerator,IEnumerable,IEnumerableT

    1 接口IEnumerator,IEnumerable区别IEnumerable是一个声明式的接口,声明实现该接口的类就是"可迭代的enumerable",但并没用说明如 何实现迭 ...

  7. Unity 新手入门 如何理解协程 IEnumerator yield

    Unity 新手入门 如何理解协程 IEnumerator 本文包含两个部分,前半部分是通俗解释一下Unity中的协程,后半部分讲讲C#的IEnumerator迭代器 协程是什么,能干什么? 为了能通 ...

  8. foreach和IEnumerable+yield和IEnumerator

    C#里,foreach可以算是个高一等级的循环,因为想要使用foreach必须实现IEnumberable,然后还需要在这个接口的唯一方法中,用yield return返回元素,才能达到foreach ...

  9. 2021年了,`IEnumerator`、`IEnumerable`接口还傻傻分不清楚?

    IEnumerator.IEnumerable这两个接口单词相近.含义相关,傻傻分不清楚. 入行多年,一直没有系统性梳理这对李逵李鬼. 最近本人在怼着why神的<其实吧,LRU也就那么回事> ...

  10. [转]那些年我还不懂:IList,ICollection,IEnumerable,IEnumerator,IQueryable

    1.首先看一个简单的例子 int[] myArray = { 1, 32, 43, 343 };IEnumerator myie = myArray.GetEnumerator();myie.Rese ...

最新文章

  1. 《OpenCV3编程入门》学习笔记6 图像处理(四)形态学滤波(2):开运算、闭运算、形态学梯度、顶帽、黑帽
  2. JavaScript正则表达式
  3. Visual studio(VS)如何修改显示字体类型和字体大小?
  4. Centos 7安装gvim
  5. python executescript_在selenium中一些相对常用的JavaScript事件
  6. 集群、分布式、微服务概念和区别
  7. 【Java例题】1.3给朋友的贺卡
  8. 系统调用之creat
  9. 计算大数阶乘--VB Script 版
  10. 如何在IDM官网安装IDM谷歌插件?
  11. Delphi2010及注册码下载地址
  12. excel表格快捷键
  13. AlphaGo人肉臂黄士杰:我的使命完成 阿尔法狗项目结束 | 重磅
  14. 基于京东云服务器来搭建javaWeb 运行环境
  15. 北京市金融工作局:大数据重塑未来金融监管方式
  16. 第一章 富爸爸,穷爸爸
  17. oracle dba培训教程 第九章 创建数据库
  18. android 九宫格带删除,Android--选择多张图片,支持拖拽删除、排序、预览图片
  19. android广告赚钱[转]
  20. 5G和车联网的本质联系

热门文章

  1. PTA6-2使用函数求最大公约数
  2. 计算机知识欠缺,计算机虚拟内存不足,该怎么办?
  3. 说话人识别(声纹识别)发展史简单总结——(阶段学习总结,GMM,GMM-UBM,GMM-SVM理解)
  4. 奥运歌曲推荐:《未来就是现在》周笔畅铃声 奥运歌曲推荐:《...
  5. Python使用Pillow包报错 ImportError: DLL load failed while importing _imaging: 找不到指定的程序。
  6. 国外家喻户晓的搜索引擎优化SEO工具商SEOmoz公司获$1800万融资
  7. 六年级数学期中考试只考了88分, 但试卷被老师写下:Good! very good!! very very good!!!
  8. Android面试题---真实分享
  9. 辐射神经场算法——Wild-NeRF / Mipi-NeRF / BARF / NSVF / Semantic-NeRF / DSNeRF
  10. 源代码阅读利器 Source Insight使用指导