在学习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让这个函数每帧都被调用了

转载于:https://www.cnblogs.com/zsw-1993/p/4879151.html

简要分析unity3d中剪不断理还乱的yield相关推荐

  1. java inverse_简要分析java中inverse和cascade有何区别

    在java开发的过程中,inverse和cascade的作用或许有些人比较清楚,而另外有的人感到陌生,究竟两者之间有没有关系?正如它们又都能影响对象关联关系的维护,所以将这两个属性的作用区分开也不是很 ...

  2. 简要分析Defi中存在的风险

    "你永远赚不到超出你认知范围之外的钱",这段话很好地说明了财富与认知的关系,即不断的提升自己的认知.如果你不去了解你所投资的加密货币项目,你可能掉进交易规则.市场操纵.智能合约漏洞 ...

  3. 【unity】简要分析yield及协同程序的用法

    在学习unity3d的时候很容易看到下面这个例子: 1 void Start () {2 StartCoroutine(Destroy());3 }4 5 IEnumerator Destroy(){ ...

  4. 对下图所示的连通网络G,用克鲁斯卡尔(Kruskal)算法求G的最小生成树T,请写出在算法执行过程中,依次加入T的边集TE中的边。说明该算法的基本思想及贪心策略,并简要分析算法的时间复杂度

    对下图所示的连通网络G,用克鲁斯卡尔(Kruskal)算法求G的最小生成树T,请写出在算法执行过程中,依次加入T的边集TE中的 边.说明该算法的基本思想及贪心策略,并简要分析算法的时间复杂度

  5. Java7中的ForkJoin并发框架初探(中)——JDK中实现简要分析

    为什么80%的码农都做不了架构师?>>>    根据前文描述的Doug Lea的理论基础,在JDK1.7中已经给出了Fork Join的实现.在Java SE 7的API中,多了Fo ...

  6. java中匿名类例题的简要分析

    在学习java的继承的过程中遇到这样的一个分析类题目,比较有意思,在这里做出简要分析: //请写出以下程序运行结果:package test; public class Test {public st ...

  7. 生信分析过程中这些常见文件的格式以及查看方式你都知道吗?

    生信分析过程中,会与很多不同格式的文件打交道,除了原始测序数据fastq之外,还需要准备基因组文件fasta格式和基因注释文件gtf格式.在分析的过程中还会有众多中间文件的生成,如bed.bed12. ...

  8. 生信分析过程中这些常见文件(fastq/bed/gtf/sam/bam/wig)的格式以及查看方式你都知道吗?

    生信分析过程中,会与很多不同格式的文件打交道,除了原始测序数据fastq之外,还需要准备基因组文件fasta格式和基因注释文件gtf格式.在分析的过程中还会有众多中间文件的生成,如bed.bed12. ...

  9. MediaPlayer源码流程简要分析

    涉及文件目录: \frameworks\base\media\java\android\media\MediaPlayer.java \frameworks\base\media\jni\androi ...

最新文章

  1. CSS结构选择器四种结构关系的范围
  2. [LeetCode 题解]: Rotate List
  3. ES Next Arrow function Promise Iterator Generator yield Async Await
  4. Delphi的StringReplace[转]
  5. wxWidgets:wxTextInputStream类用法
  6. 女朋友来大姨妈怎么办?
  7. 演示教学法在计算机基础课程中的应用,演示教学法在《计算机基础》课程中的应用...
  8. 搜索 由浅入深 之一 水题
  9. 深夜,我偷听到程序员要对session下手......
  10. oracle 12c transaction guard,oracle 12c的Data guard中废弃
  11. synchornized实现原理
  12. heartbeat+drbd+mysql高可用架构
  13. C++ stack(STL stack)
  14. python生成手写文字图片_使用PHP辅助 快速制作一套自己的手写字体实践
  15. vhdl变量除法_在VHDL中实现高精度快速除法
  16. 达内java月考试题_达内java1512第二次月考(附答案)doc.doc
  17. 首届 Rust China Hackathon Online 参赛队伍名单出炉
  18. 矩阵乘法的计算和来源
  19. ip网络广播系统服务器,IP网络广播系统控制软件
  20. 活用async/await,实现一些让Vue更好用的装饰器

热门文章

  1. apache伪静态综述
  2. Linux文件夹打包发送到本地
  3. php 实现树状组织图插件,使用jstree插件实现树形结构
  4. c++ qt获取电脑的内存_QT开发(十四)——QT绘图系统
  5. 网页php自描述,一段代码让wordpress博客自动添加关键词(keywords)及网页描述(description)...
  6. conv2d 公式_Pytorch 从0开始学(6)——Conv2d 详解
  7. Linux各个版本防火墙操作(CentOS Ubuntu)
  8. Python机器学习:梯度下降法001什么是梯度下降法
  9. 幼师学计算机心得体会怎么写,幼儿教师学习心得
  10. mysql csv 表头_Mysql实例mysql 导出CSV文件 并带表头的方法