在Unity中讲到协程(Coroutine),那肯定先是要理解yield的意义,及其如何去使用它。yield其实并不难,只要你理解了它,但是我们在开发中一般如果理解了如何去使用协程了,也就没再去深究yield,今天咱们就讲一下yield到底神秘在哪里!(以下例子来源于网络)

void Start ()

{

StartCoroutine(Destroy());

}

IEnumerator Destroy()

{

yield return WaitForSeconds(3.0f);

Destroy(gameObject);

}

这个函数很简单,调用StartCoroutine函数开启协程,yield等待一段时间后,销毁这个对象,这个看着还可以吧?能够理解吧?

unity3d官方对于协程的解释是:一个协同程序在执行过程中,可以在任意位置使用yield语句。yield的返回值控制何时恢复协同程序向下执行。协同程序在对象自有帧执行过程中堪称优秀。协同程序在性能上没有更多的开销。StartCoroutine函数是立刻返回的,但是yield可以延迟结果。直到协同程序执行完毕。

除了延时功能,yield还有其他很多功能,下面来看另外一段代码,不用于延时功能:

public static IEnumerable<int> GenerateFibonacci()

{

yield return 0;

yield return 1;

int last0 = 0, last1 = 1, current;

while (true)

{

current = last0 + last1;

yield return current;

last0 = last1;

last1 = current;

}

}

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

游戏中需要使用yield的场景

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

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

  • 人物对话时,文字一个一个很快的出现,而不是一下突然出现

  • 10、9、8……0的倒计时

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

using UnityEngine;

using System.Collections;

public class dialog_yield : MonoBehaviour {

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

public float speed = 5.0f;

void Start ()

{

StartCoroutine(ShowDialog());

}

void Update ()

{

}

IEnumerator ShowDialog()

{

float timeSum = 0.0f;

while(guiText.text.Length < dialogStr.Length){

timeSum += speed * Time.deltaTime;

guiText.text = dialogStr.Substring(0,         System.Convert.ToInt32(timeSum));

yield return null;

}

}

}

yield return null可以让这段代码在下一帧继续执行。在ShowDialog()中,每次更新文字以后yield return null,直到这段文字被完整显示。

yield 后面可以有的表达式:

a) null - the coroutine executes the next time that it is eligible

b) WaitForEndOfFrame - the coroutine executes on the frame, after all of the rendering and GUI is complete

c) WaitForFixedUpdate - causes this coroutine to execute at the next physics step, after all physics is calculated

d) WaitForSeconds - causes the coroutine not to execute for a given game time period

e) WWW - waits for a web request to complete (resumes as if WaitForSeconds or null)

f) Another coroutine - in which case the new coroutine will run to completion before the yielder is resumed

值得注意的是 WaitForSeconds()受Time.timeScale影响,当Time.timeScale = 0f 时,yield return new WaitForSecond(x) 将不会满足。

以上其实说了这么多,就记住一个要点:当执行到yield语句的时候,代码权限就被交给调用它的父线程了,就把你给挂起了,外部根据你yield的条件,知道该什么时候去调用MoveNext(),当再次调用MoveNext()的时候,yield后面的代码就被执行到了(一定要理解这句话)。

我们知道,主线程做加载等阻塞活动的时候,游戏就没法玩了,我们需要多线程,需要不阻塞主线程,需要在加载资源的时候能够看见动画,能够打地鼠!在Unity中,当然是可以使用多线程的,因为我们用C#,能够使用C#中的多线程,只是有一个重要的条件罢了:你仅能从主线程中访问Unity3D的组件,对象和Unity3D系统调用。任何企图访问这些项目的第二个线程都将失败并引发错误,这是一个要重视的一个限制。所以若要访问Unity中的组件,我们可以去用协程,和线程差不多,但是原理有本质上的不同:协程仍然是在主线程中去执行的。协程不是线程,也不是异步执行的。协程和 MonoBehaviour 的 Update函数一样也是在MainThread中执行的。使用协程你不用考虑同步和锁的问题。

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

协程跟Update()其实一样的,都是Unity每帧对会去处理的函数(如果有的话)。如果MonoBehaviour 是处于激活(active)状态的而且yield的条件满足,就会协程方法的后面代码。

下面说两点重要的事项:

1. 从上图可以看出来,Coroutine是在每帧的LateUpdate之后执行的,至少在Unity5中还没有看到次序的改变。下面的代码来源于网络

using UnityEngine;

using System.Collections;

public class TestCoroutine : MonoBehaviour {

private bool isStartCall = false;  //Makesure Update() and LateUpdate() Log only once

private bool isUpdateCall = false;

private bool isLateUpdateCall = false;

// Use this for initialization

void Start () {

if (!isStartCall)

{

Debug.Log("Start Call Begin");

StartCoroutine(StartCoutine());

Debug.Log("Start Call End");

isStartCall = true;

}

}

IEnumerator StartCoutine()

{

Debug.Log("This is Start Coroutine Call Before");

yield return new WaitForSeconds(1f);

Debug.Log("This is Start Coroutine Call After");

}

// Update is called once per frame

void Update () {

if (!isUpdateCall)

{

Debug.Log("Update Call Begin");

StartCoroutine(UpdateCoutine());

Debug.Log("Update Call End");

isUpdateCall = true;

}

}

IEnumerator UpdateCoutine()

{

Debug.Log("This is Update Coroutine Call Before");

yield return new WaitForSeconds(1f);

Debug.Log("This is Update Coroutine Call After");

}

void LateUpdate()

{

if (!isLateUpdateCall)

{

Debug.Log("LateUpdate Call Begin");

StartCoroutine(LateCoutine());

Debug.Log("LateUpdate Call End");

isLateUpdateCall = true;

}

}

IEnumerator LateCoutine()

{

Debug.Log("This is Late Coroutine Call Before");

yield return new WaitForSeconds(1f);

Debug.Log("This is Late Coroutine Call After");

}

}

先在Update中调用 this.enabled = false; 得到的结果:

然后把 this.enabled = false; 注释掉,换成 this.gameObject.SetActive(false); 得到的结果如下:

MonoBehaviour.enabled = false 协程会照常运行,但gameObject.SetActive(false) 后协程却全部停止,即使在Inspector把  gameObject 激活还是没有继续执行。


       整理得到:通过设置MonoBehaviour脚本的enabled对协程是没有影响的,但如果 gameObject.SetActive(false) 则已经启动的协程则完全停止了,即使在Inspector把gameObject 激活还是没有继续执行。也就说协程虽然是在MonoBehvaviour启动的(StartCoroutine)但是协程函数的地位完全是跟MonoBehaviour是一个层次的,不受MonoBehaviour的状态影响,但跟MonoBehaviour脚本一样受gameObject 控制,也应该是和MonoBehaviour脚本一样每帧“轮询” yield 的条件是否满足。

yield 后面可以有的表达式:

a) null - the coroutine executes the next time that it is eligible

b) WaitForEndOfFrame - the coroutine executes on the frame, after all of the rendering and GUI is complete

c) WaitForFixedUpdate - causes this coroutine to execute at the next physics step, after all physics is calculated

d) WaitForSeconds - causes the coroutine not to execute for a given game time period

e) WWW - waits for a web request to complete (resumes as if WaitForSeconds or null)

f) Another coroutine - in which case the new coroutine will run to completion before the yielder is resumed

值得注意的是 WaitForSeconds()受Time.timeScale影响,当Time.timeScale = 0f 时,yield return new WaitForSecond(x) 将不会满足。

IEnumerator & Coroutine

协程其实就是一个IEnumerator(迭代器),IEnumerator 接口有两个方法 Current 和 MoveNext() ,前面介绍的 TaskManager 就是利用者两个方法对协程进行了管理,只有当MoveNext()返回 true时才可以访问 Current,否则会报错。迭代器方法运行到 yield return 语句时,会返回一个expression表达式并保留当前在代码中的位置。 当下次调用迭代器函数时执行从该位置重新启动。

Unity在每帧做的工作就是:调用 协程(迭代器)MoveNext() 方法,如果返回 true ,就从当前位置继续往下执行。

Hijack

这里在介绍一个协程的交叉调用类 Hijack(参见附件):

C#代码  
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using UnityEngine;
  5. using System.Collections;
  6. [RequireComponent(typeof(GUIText))]
  7. public class Hijack : MonoBehaviour {
  8. //This will hold the counting up coroutine
  9. IEnumerator _countUp;
  10. //This will hold the counting down coroutine
  11. IEnumerator _countDown;
  12. //This is the coroutine we are currently
  13. //hijacking
  14. IEnumerator _current;
  15. //A value that will be updated by the coroutine
  16. //that is currently running
  17. int value = 0;
  18. void Start()
  19. {
  20. //Create our count up coroutine
  21. _countUp = CountUp();
  22. //Create our count down coroutine
  23. _countDown = CountDown();
  24. //Start our own coroutine for the hijack
  25. StartCoroutine(DoHijack());
  26. }
  27. void Update()
  28. {
  29. //Show the current value on the screen
  30. guiText.text = value.ToString();
  31. }
  32. void OnGUI()
  33. {
  34. //Switch between the different functions
  35. if(GUILayout.Button("Switch functions"))
  36. {
  37. if(_current == _countUp)
  38. _current = _countDown;
  39. else
  40. _current = _countUp;
  41. }
  42. }
  43. IEnumerator DoHijack()
  44. {
  45. while(true)
  46. {
  47. //Check if we have a current coroutine and MoveNext on it if we do
  48. if(_current != null && _current.MoveNext())
  49. {
  50. //Return whatever the coroutine yielded, so we will yield the
  51. //same thing
  52. yield return _current.Current;
  53. }
  54. else
  55. //Otherwise wait for the next frame
  56. yield return null;
  57. }
  58. }
  59. IEnumerator CountUp()
  60. {
  61. //We have a local increment so the routines
  62. //get independently faster depending on how
  63. //long they have been active
  64. float increment = 0;
  65. while(true)
  66. {
  67. //Exit if the Q button is pressed
  68. if(Input.GetKey(KeyCode.Q))
  69. break;
  70. increment+=Time.deltaTime;
  71. value += Mathf.RoundToInt(increment);
  72. yield return null;
  73. }
  74. }
  75. IEnumerator CountDown()
  76. {
  77. float increment = 0f;
  78. while(true)
  79. {
  80. if(Input.GetKey(KeyCode.Q))
  81. break;
  82. increment+=Time.deltaTime;
  83. value -= Mathf.RoundToInt(increment);
  84. //This coroutine returns a yield instruction
  85. yield return new WaitForSeconds(0.1f);
  86. }
  87. }
  88. }

上面的代码实现是两个协程交替调用,对有这种需求来说实在太精妙了。

Unity协程(Coroutine)之yield和迭代原理分析相关推荐

  1. Unity 协程Coroutine综合测试

    Unity 协程Coroutine综合测试 1 using UnityEngine; 2 using System.Collections; 3 using System.Text; 4 5 publ ...

  2. 【Unity】Unity协程(Coroutine)的原理与应用

    文章目录 前言 一.什么是协程 二.应用场景 1.异步加载资源 2.将一个复杂程序分帧执行 3.定时器 三.协程的使用 注意事项 四.Unity协程的底层原理 1. 协程本体:C#的迭代器函数 2. ...

  3. 简单总结协程Coroutine及Yield常见用法

    原文地址:http://blog.csdn.net/qq_18995513/article/details/51944602 最近学习协程Coroutine,参考了别人的文章和视频教程,感觉协程用法还 ...

  4. Kotlin协程Channel中receive与send原理分析

    文章目录 0. 引言 1. runBlocking() 1.1. 开启协程 1.2. 同步阻塞式执行协程 2. receive() 2.1. 若receive操作时队列包含Send元素则异步唤醒sen ...

  5. Unity 协程(Coroutine)原理与用法详解

    前言: 协程在Unity中是一个很重要的概念,我们知道,在使用Unity进行游戏开发时,一般(注意是一般)不考虑多线程,那么如何处理一些在主任务之外的需求呢,Unity给我们提供了协程这种方式 为啥在 ...

  6. unity协程coroutine 简明教程

    本篇内容基于 https://gamedevbeginner.com/coroutines-in-unity-when-and-how-to-use-them/ 以及官方教程 为什么使用协程 协程非常 ...

  7. Unity协程(Coroutine)原理深入剖析再续

    Unity协程(Coroutine)原理深入剖析再续 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 前面已经介绍过对协程(Coroutine ...

  8. Unity协程(Coroutine)原理深入剖析

    Unity协程(Coroutine)原理深入剖析 By D.S.Qiu 尊重他人的劳动,支持原创,转载请注明出处:http.dsqiu.iteye.com 记得去年6月份刚开始实习的时候,当时要我写网 ...

  9. Unity 协程探究

    一.官方手册中的描述 1.Manual/Coroutines 函数在调用时, "从调用到返回" 都发生在一帧之内,想要处理 "随时间推移进行的事务", 相比Up ...

  10. Unity 协程 yield return的使用

    public void Start(){//开启协程Coroutine testCoroutine = StartCoroutine(Test());//停止指定协程StopCoroutine(tes ...

最新文章

  1. 学习CAS实现SSO单点登录
  2. labview生成HTML报表,LabVIEW201
  3. 数据结构源码笔记(C语言):二分查找
  4. Python 微信机器人:属于自己的微信机器人制作,简单易懂。图灵机器人接口api调用
  5. 在CentOS 7系统里使用465端口发送邮件
  6. 2021 EdgeX 中国挑战赛决赛入围名单公布
  7. C# 操作线程的通用类[测试通过]
  8. python基础之函数介绍进阶操作、全局变量局部变量
  9. Spring Boot 中使用WebJars引入javasript依赖
  10. 计算机组成原理统一试卷,计算机组成原理试卷(含答案).doc
  11. 16.2.4 登录到 SMTP 服务器
  12. wx僵尸粉检测,真实好友1.0(无障碍检测好友状态)
  13. PHPStorm 常用设置
  14. vue移动端开启键盘 页面底部样式乱了
  15. python爬虫——爬取豆瓣TOP250电影
  16. 机器学习之L1正则化和L2正则化(附源码解析)
  17. 因对某产品的一丝质疑而到被洗脑式怼,引起对于社交电商的一丝看法
  18. 笔记——嵌入式软件开发学习(一)
  19. 808操作系统 设备管理
  20. 线激光扫描三维重建 平移装置 光平面标定

热门文章

  1. 51job的城市编号
  2. 常州2021高考成绩查询,2021年常州高考各高中成绩及本科升学率数据排名及分析...
  3. 【音乐】后弦 - 笔墨侍候
  4. excel中vba操作文件
  5. raid卡组不同raid_Linux 软件阵列与低端硬件阵列卡性能对比
  6. GOF23设计模式之建造者模式
  7. (七)线程的优先级Priority和关键词:synchronized
  8. 中国历史人物传记数据库 CBDB 若干表简介
  9. 6个免费音乐网站,随便听随便下,都是好干货
  10. 康托尔集的物理意义1.2