介绍

协程Coroutine在Unity中一直扮演者重要的角色。可以实现简单的计时器、将耗时的操作拆分成几个步骤分散在每一帧去运行等等,用起来很是方便。
但是,在使用的过程中有没有思考过协程是怎么实现的?为什么可以将一段代码分成几段在不同帧执行?
本篇文章将从实现原理上理解协程。

迭代器

在使用协程的时候,总是声明一个返回值为IEnumerator的函数,并在函数中包含yield return xxx或者yield break值类的语句:

private IEnumerator WaitAndPrint(float waitTime)
{yield return new WaitForSeconds(waitTime);print("Coroutine ended: " + Time.time + " seconds");
}

首先看下迭代器相关几个接口定义

    public interface IEnumerator{bool MoveNext();Object Current {get; }void Reset();}

Current属性为只读属性,返回枚举序列中的当前位的内容
MoveNext()把枚举器的位置前进到下一项,返回布尔值,新的位置若是有效的,返回true;否则返回false
Reset()将位置重置为原始状态

    public interface IEnumerable{IEnumerator GetEnumerator();}

迭代器在C#中是一个非常强大的功能,只要这个类继承了IEnumerable接口实现了GetEnumerator方法,就可以使用foreach区遍历这个类实例化的对象,遍历输出的结果是根据返回值IEnumerator 确定的。

yield return的作用是在执行到这行代码之后,将控制权立即交还给外部。yield return之后的代码会在外部代码再次调用MoveNext时才会执行,直到下一个yield return——或是迭代结束。

以下是一段简单的代码:

IEnumerator TestCoroutine()
{yield return null;              //返回内容为nullyield return 1;                 //返回内容为1yield return "sss";             //返回内容为"sss"yield break;                    //跳出,类似普通函数中的return语句yield return 999;               //由于break语句,该内容无法返回
}void Start()
{IEnumerator e = TestCoroutine();while (e.MoveNext()){Debug.Log(e.Current);       //依次输出枚举接口返回的值}
}/*运行结果:
Null
1
sss
*/

再看下Start函数中的代码,就是将yield return 语句中返回的值依次输出。
第一次MoveNext()后,Current位置指向了yield return 返回的null,该位置是有效的(这里注意区分位置有效和结果有效,位置有效是指当前位置是否有返回值,即使返回值是null;而结果有效是指返回值的结果是否为null,显然此处返回结果是无意义的)所以MoveNext()返回值是true;
第二次MoveNext()后,Current新位置指向了yield return 返回的1,该位置是有效的,MoveNext()返回true
第三次MoveNext()后,Current新位置指向了yield return 返回的"sss",该位置也是有效的,MoveNext()返回true
第四次MoveNext()后,Current新位置指向了yield break,无返回值,即位置无效,MoveNext()返回false,至此循环结束

原理

Unity协程的具体功能:

1.将协程代码中由yield return 语句分割的部分分配到每一帧执行。

2.yield return 后的值是等待类(WaitForSeconds、WaitForFixedUpdate)时需要等待相应时间。

3.yield return 后的值还是协程(Coroutine)时需要等待嵌套部分协程执行完毕才能执行接下来内容。

// case 1
IEnumerator Coroutine1()
{//do something xxx     //假如是第N帧执行该语句yield return 1;         //等一帧//do something xxx    //则第N+1帧执行该语句
}// case 2
IEnumerator Coroutine2()
{//do something xxx     //假如是第N秒执行该语句yield return new WaitForSeconds(2f);    //等两秒      //do something xxx      //则第N+2秒执行该语句
}// case 3
IEnumerator Coroutine3()
{//do something xxxyield return StartCoroutine(Coroutine1());  //等协程Coroutine1执行完           //do something xxx
}

分帧

Unity的事件方法生命周期

只需要将MoveNext()移到每一帧去执行一次不就实现分帧执行了吗!

既然要分配在每一帧去执行,那当然就是Update和LateUpdate了。这里我个人喜欢将实现代码放在LateUpdate之中,为什么呢?因为Unity中协程的调用顺序是在Update之后,LateUpdate之前,所以这两个接口都不够准确;但在LateUpdate中处理,至少能保证协程是在所有脚本的Update执行完毕之后再去执行。

IEnumerator e = null;
void Start()
{e = TestCoroutine();
}void LateUpdate()
{if (e != null){if (!e.MoveNext()){e = null;}}
}IEnumerator TestCoroutine()
{Log("Test 1");yield return null;              //返回内容为nullLog("Test 2");yield return 1;                 //返回内容为1Log("Test 3");yield return "sss";             //返回内容为"sss"Log("Test 4");yield break;                    //跳出,类似普通函数中的return语句Log("Test 5");yield return 999;               //由于break语句,该内容无法返回
}void Log(object msg)
{Debug.LogFormat("<color=yellow>[{0}]</color>{1}", Time.frameCount, msg.ToString());
}

延时等待

只需要在分帧的基础上加入计时器判断即可。

既然要识别自己的等待类,那当然要获取Current值根据其类型去判定是否需要等待。假如Current值是需要等待类型,那就延时到倒计时结束;而Current值是非等待类型,那就不需要等待,直接MoveNext()执行后续的代码即可。
这里着重说下“延时到倒计时结束”。既然知道Current值是需要等待的类型,那此时肯定不能在执行MoveNext()了,否则等待就没用了;接下来当等待时间到了,就可以继续MoveNext()了。可以简单的加个标志位去做这一判断,同时驱动MoveNext()的执行。

private void OnGUI()
{if (GUILayout.Button("Test"))       //注意:这里是点击触发,没有放在start里,为什么?{enumerator = TestCoroutine();}
}void LateUpdate()
{if (enumerator != null){bool isNoNeedWait = true, isMoveOver = true;var current = enumerator.Current;if (current is MyWaitForSeconds){MyWaitForSeconds waitable = current as MyWaitForSeconds;isNoNeedWait = waitable.IsOver(Time.deltaTime);}if (isNoNeedWait){isMoveOver = enumerator.MoveNext();}if (!isMoveOver){enumerator = null;}}
}IEnumerator TestCoroutine()
{Log("Test 1");yield return null;              //返回内容为nullLog("Test 2");yield return 1;                 //返回内容为1Log("Test 3");yield return new MyWaitForSeconds(2f);  //等待两秒           Log("Test 4");
}

协程嵌套等待

IEnumerator Coroutine1()
{//do something xxxyield return null;//do something xxxyield return StartCoroutine(Coroutine2());  //等待Coroutine2执行完毕//do something xxxyield return 3;
}IEnumerator Coroutine2()
{//do something xxxyield return null;//do something xxxyield return 1;//do something xxxyield return 2;
}

需要注意下协程嵌套时的执行顺序,先执行完内层嵌套代码再执行外层内容;即更新结束条件时要先更新内层协程(上例Coroutine2)在更新外层协程(上例Coroutine1)。

浅析Unity协程实现原理相关推荐

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

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

  2. Unity 协程的原理

    协程不是多线程,协程还是在主线程里面(注:在Unity中非主线程是不可以访问Unity资源的) 1.线程.进程和协程的区别 进程有自己独立的堆和栈,即不共享堆也不共享栈,进程由操作系统调度 线程拥有自 ...

  3. 浅析tornado协程运行原理

    转载:http://xidui.github.io/2016/01/26/%E6%B5%85%E6%9E%90tornado%E5%8D%8F%E7%A8%8B%E8%BF%90%E8%A1%8C%E ...

  4. Unity 协程底层原理解析

    1.协程 unity是单线程设计的游戏引擎,unity实际上有多条渲染线程,但对于unity调用我们编写的游戏脚本,都是放在一个主线程当中进行调度的.因此对于我们写的游戏脚本unity是单线程的. 协 ...

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

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

  6. Unity 的协程的原理

    Unity是一款非常强大的游戏引擎,它支持多种编程语言,其中最常用的语言是C#.在Unity中,协程是一种非常强大的功能,它可以让我们在游戏中实现各种各样的效果.本文将详细介绍Unity协程的原理,并 ...

  7. unity update 协程_Unity 协程的原理

    Unity 协程的原理 发布时间:2019-06-13 18:45, 浏览次数:1118 , 标签: Unity 协程不是多线程,协程还是在主线程里面(注:在Unity中非主线程是不可以访问Unity ...

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

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

  9. Unity(协程是什么,怎么用)

    c#协程 概:早就听闻Unity协程有必要学一手,但我还是搁置了好久,直到这个搓东西的时候被迫用了多线程发现解决不了,百度一手得知我不得不学一下协程了.在搜集了一些资料后发现知乎大佬的博文香的一批(我 ...

最新文章

  1. 全局变量名为 param1 var param1Value = webBrowser1.Document.InvokeScript(eval,new String[]{ param1}).To...
  2. 敏捷方法开发总结的点评记录
  3. 工业级交换机芯片选择的注意事项
  4. 图片|视频|音频文件扩展名(后缀)
  5. 996,活着抑或死亡
  6. 购物直播系统搭建 新型电商开发方案
  7. 电脑桌面计算机打开不显示硬盘信息,Win10电脑下移动硬盘不显示盘符如何解决...
  8. linux程序设计学习心得,几点学习Linux编程的建议
  9. iDrac6 虚拟控制台 连接失败
  10. springboot自定义ClassLoader实现同一个jar支持多版本的使用场景【附源码】
  11. STM32 定时器中断相关知识及配置
  12. python3 简单选课系统
  13. 内容算法:新闻“标题党”检测方法综述
  14. 采用GUID分區方法
  15. 前端 一年至三年工作经验必知
  16. 怎样用计算机算账快,算账比计算器还快 唐山古稀老人和“一掌金”
  17. 微信小程序开发(一)系统对接微信UGC类小程序内容安全接口JAVA版
  18. python语言关键字在异常处理结构中_python二级考试试题6
  19. 柯美服务器显示ff,柯尼卡美能达C故障代码解读.docx
  20. 【知识点】eCall是什么?

热门文章

  1. 【金融】财务管理公司金融 (二) 财务分析
  2. ASP.NET三层架构UI层(四)
  3. 毕业设计 opencv python 深度学习垃圾图像分类系统
  4. 20181018杂谈——身体是革命的本钱
  5. MYSQL数据库设计试卷b_MYSQL数据库试题
  6. java setdaemon_setDaemon的简单使用
  7. c++十大排序——堆排序
  8. 华为emui3.1 android,EMUI3.1——基于安卓5.0深度定制
  9. 《读书笔记》—–书单推荐
  10. 计算机word10怎么输入度数,Word2010怎么输入度数