Unity3D中的Coroutine具体解释

本文太乱,推荐frankjfwang的:全面解析Coroutine技术

Unity中的coroutine是通过yield expression;来实现的。官方脚本中到处会看到这种代码。

疑问:

yield是什么?

Coroutine是什么?

unity的coroutine程序运行流程怎么那么奇怪?

unity中的coroutine原理是什么,怎么实现的?

使用unity的coroutine须要注意什么问题?

一、yield的在几种语言中的程序运行特性

Lua中的yield是使得协同函数执行->挂起而且传递參数给resume。resume使得协同函数挂起->执行而且传递參数给协同函数。

C#中yield return/break是用于函数查询集合生成器里面的值(类似迭代)返回,并记录当前现场,下次查询时从上一次记录的yield现场处,继续往下运行,直到继续往下运行没有了。那么退出这段yield的逻辑。

yield break会终止掉yield迭代逻辑并跳出。

YieldImplementation:

1).Caller callsfunction

2).Caller requestsitem 按需请求一个元素

3).Next itemreturned 返回请求的元素

4).Goto step #2

Python中的yield expression, 有yield的函数就变成了一个生成器,调用该函数返回的是迭代器对象,用迭代器对象调用next方法(或者循环中会自己主动调用next方法)。才開始运行函数,运行到yield expression处则中断。返回迭代器当前的值。并保留现场,下次调用next则从现场处開始运行,迭代完了就停止了。能够看出Python中的yield和C#中的yield是类似的,用于创建生成器,运行时中断返回迭代器值,并记录现场,下次从现场处继续运行。

Unity中的yield就是和C#,python中的类似。由于unity是基于.net框架的,且unity脚本開始是用Boo(Python的一个变种)写的。

仅仅是unity中多了coroutine特性类型。和StartCoroutine的coroutine管理类。StartCoroutine不是启动了一个新的线程。而是开启一个协同程序,默认unity全部代码都在一个线程中(http://answers.unity3d.com/questions/280597/new-thread-vs-startcoroutine.html)。

coroutine语言层面的原理:

在两年前,协程似乎是一个非常高级的东西,随后大多数语言或多或少都支持协程。我比較熟悉的有Python的gevent,Lua的coroutine,Go的goroutine。尤其是Lua和Go,语言本身就支持协程。协程也被叫做轻量级线程。

通俗点讲就是定义一大堆任务。然后通过一个线程轮着对每一个任务都执行一下,协作执行。它的厉害之处在于每执行到一个任务的时候。它都能够从这个任务上一次中断的地方開始执行。

在我们一般的印象中,仅仅有操作系统对线程进行调度的时候才会干这种事情,进行各种进栈,保存状态。

而协程,总共也仅仅是执行在一个线程中。要是使用线程本身的系统栈,早就暴了。因此在这里,实现的时候是用内存来模拟栈的操作。详细实现,我想复杂度一定会不小。

我们知道,线程比进程轻量级,因此产生一个线程消耗的资源比进程少,上下文切换也比进程节约。而协程比线程更加轻量级。上下文切换更是迅速。

于是在server编程方面给人无限想象。虽然眼下还没有出现一款主流的採用协程的webserver。可是Go语言开发的web服务的性能已经崭露头角了。

二、Unity的Coroutine运行现象

第一种方法:

voidStart()

{

print("Starting " +Time.time);----------------------------------------1

StartCoroutine(WaitAndPrint(2));-------------------------------------2

print("Done " +Time.time);-------------------------------------------3

}

IEnumerator WaitAndPrint(float waitTime)

{

yield return new WaitForSeconds(waitTime);------------------------4

print("WaitAndPrint " + Time.time);----------------------------------5

}

该段代码的运行顺序是12435

运行到4协程注冊了事件,控制权交给外部线程。外部线程运行到3。事件发生,程序分段运行机制goto到协程处记录了堆栈信息运行5语句。

IEnumerator Start()

{

print("Starting " +Time.time);----------------------------------------1

yield return StartCoroutine(WaitAndPrint(2.0F));------------------------2

print("Done " +Time.time);------------------------------------------3

}

IEnumerator WaitAndPrint(float waitTime)

{

yield return new WaitForSeconds(waitTime);----------------------------4

print("WaitAndPrint " + Time.time);-----------------------------------------5

}

该段代码的运行顺序是12453

Why?这么奇怪的运行方式。

程序运行到4。运行yield return表达式注冊事件交出控制权给外部,由于外部还要交出控制权也须要运行yield return后面的表达式语句因此会重入WaitAndPrint函数接着协程当前状态下一步运行所以运行到5,yield return 后面表达式语句运行完成控制权全然交出,之后才运行3,根本原因是yield return 不能直接嵌套后面须要跟一个表达式(事件)。

三、Unity官方文档对coroutine的解释

Normal coroutine updates are run after theUpdate function returns. Acoroutine is a function that can suspend its execution (yield) until the givenYieldInstruction finishes. Different
uses of Coroutines:

yield; The coroutine will continue after all Update functionshave been calledon the next frame.

yield WaitForSeconds(2); Continueafter a specified time delay, after all Update functions have been called for
theframe.

yield WaitForFixedUpdate(); Continue afterall FixedUpdate has been called on all scripts.

yield WWWContinue aftera WWW download has completed

yield StartCoroutine(MyFunc);
Chains the coroutine, and will wait for the MyFunc coroutine to completefirst.

C#要在yield coroutine之间加上returnkeyword。

四、Unity中的Coroutine原理推測

     虚拟机分段运行机制。 同类型嵌套用栈存放实现串行运行:.NET虚拟机在每一帧循环中, 会依次进入每一个编译器提前定义好的入口。对于Coroutine类型,编译器须要产生一些代码。在Coroutine类型指定的时间或事件完毕后(.net的虚拟机用函数指针进行标记管理现场和在流程中每帧检查时间或者事件满足后发送消息,将cpu全部权交给yield中断的现场。或是通过包括不同Coroutine迭代器的多个管理类管理各个coroutine,
每帧用coroutine子类通过多态检查时间或事件到达。将cpu全部权交给coroutine子类中断的现场)。从yield中断后的代码处继续往下运行, 这样就形成了我们看到的一个function能分段运行的机制。

而对于嵌套Coroutine类型,会串行的运行而不是并行的,可能.net虚拟机对于同coroutine类型用栈存放,栈顶的先运行,从而实现串行运行,假设外层的不使用yield return,那么不会串行运行,而是并行运行。

于是就能够解释上面样例中的运行次序问题。

原理图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvQmx1ZXMxMDIx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" >

见:http://www.richardfine.co.uk/2012/10/unity3d-monobehaviour-lifecycle/

事实上yield WaitForSeconds/null/WaitForEndOfFrame都是通过整个游戏While循环来驱动的,Yield仅仅是交出控制权给外部主线程且Coroutine设置事件挂起Coroutine协程(不是线程仅仅是分段运行机制)。当事件发生时候(不用下一帧到达。可是要在固定时间段内Update前或者后或Render后,超过了就会到下一帧)则进入协程里面的代码。

五、Unity中使用Coroutine须要注意的问题:

1.使用的地方和不能使用的地方:

必须在MonoBehaviour或继承于MonoBehaviour的类中调用 yield coroutine。

yield不能够在Update或者FixedUpdate里使用。

2.开启协程:

StartCoroutine(string methodName)和StartCoroutine(IEnumeratorroutine)都能够开启一个协程。

差别:

使用字符串作为參数时,开启协程时最多仅仅能传递一个參数,而且性能消耗会更大一点; 而使用IEnumerator 作为參数则没有这个限制。

3.删除协程:

1).在Unity3D中,使用StopCoroutine(stringmethodName)来终止该MonoBehaviour指定方法名的一个协同程序,使用StopAllCoroutines()来终止全部该MonoBehaviour能够终止的协同程序。

包含StartCoroutine(IEnumerator routine)的。

2).另一种方法能够终止协同程序,即将协同程序所在gameobject的active属性设置为false,当再次设置active为ture时,协同程序并不会再开启。

如是将协同程序所在脚本的enabled设置为false则不会生效。

4.js和C#中使用差别:

在C#中要使用 yield return而不是yield。

C#中yield(中断)语句必需要在IEnumerator类型里,C#方法的返回类型为IEnumerator。返回值如(eg:yield return new WaitForSeconds(2); 或者 yield returnnull);

5.协程函数返回值和參数类型。组合的设计模式:

协同程序的返回类型为Coroutine类型。在Unity3D中,Coroutine类继承于YieldInstruction,所以,协同程序的返回类型仅仅能为null、等待的帧数(frame)以及等待的时间。

协同程序的參数不能指定ref、out參数。可是,我们在使用WWW类时会常常使用到协同程序。因为在协同程序中不能传递參数地址(引用),也不能输出对象。

这使得每下载一个WWW对象都得重写一个协同程序。解决问题的方法是建立一个基于WWW的类(用组合模式来解决-事实上就是不通过函数传參全局关联一个对象了),并实现一个下载方法。例如以下:

using UnityEngine;

using System.Collections;

public class WWWObject : MonoBehaviour

{

public WWW www;

public WWWObject(string url)

{

if(GameVar.wwwCache)

www = WWW.LoadFromCacheOrDownload(url, GameVar.version);

else

www = new WWW(url);

}

public IEnumerator Load()

{

Debug.Log("Start loading : " + www.url);

while(!www.isDone)

{

if(GameVar.gameState == GameState.Jumping || GameVar.gameState ==GameState.JumpingAsync)

LoadScene.progress = www.progress;

yield return 1;

}

if(www.error != null)

Debug.LogError("Loading error : " + www.url + "\n" +www.error);

else

Debug.Log("End loading : " + www.url);

}

public IEnumerator LoadWithTip(string resourcesName)

{

Debug.Log("Start loading : " + www.url);

LoadScene.tipStr = "Downloading  resources<" + resourcesName + "> . . .";

while(!www.isDone)

{

if(GameVar.gameState == GameState.Jumping || GameVar.gameState ==GameState.JumpingAsync)

LoadScene.progress= www.progress;

yield return 1;

}

if(www.error != null)

Debug.LogError("Loading error : " + www.url + "\n" +www.error);

else

Debug.Log("End loading : " + www.url);

}

}

调用:

using UnityEngine;

using System.Collections;

using System.Collections.Generic;

public class LoadResources : MonoBehaviour

{

static string url ="http://61.149.211.88/Package/test.unity3d";

public static WWW www = null;

IEnumerator Start()

{

if(!GameVar.resourcesLoaded)

{

GameVar.gameState = GameState.Jumping;

WWWObject obj = new WWWObject(url);

www = obj.www;

yield return StartCoroutine(obj.LoadWithTip("Textures"));

GameVar.resourcesLoaded = true;

GameVar.gameState = GameState.Run;

}

}

}

參考文章:

http://game.ceeger.com/forum/read.php?tid=13148

http://www.zhihu.com/question/23895384

http://blog.csdn.net/tkokof1/article/details/11842673

http://blog.csdn.net/tkokof1/article/details/12834939

http://www.richardfine.co.uk/2012/10/unity3d-monobehaviour-lifecycle/

http://dsqiu.iteye.com/blog/2029701

时间: 2024-10-28 16:04:56

Unity3D中的Coroutine具体解释的相关文章

Unity3D中的Coroutine详解

Unity中的coroutine是通过yield expression;来实现的.官方脚本中到处会看到这样的代码. 疑问: yield是什么? Coroutine是什么? unity的coroutine程序执行流程怎么那么奇怪? unity中的coroutine原理是什么,怎么实现的? 使用unity的coroutine需要注意什么问题? 一.yield的在几种语言中的程序执行特性: Lua中的yield是使得协同函数运行->挂起并且传递参数给resume.resume使得协同函数挂起->运行

【Unity3D/C#】Unity3D中的Coroutine详解

Unity中的coroutine是通过yield expression;来实现的.官方脚本中到处会看到这样的代码. 疑问: yield是什么? Coroutine是什么? unity的coroutine程序执行流程怎么那么奇怪? unity中的coroutine原理是什么,怎么实现的? 使用unity的coroutine需要注意什么问题? 一.yield的在几种语言中的程序执行特性: Lua中的yield是使得协同函数运行->挂起并且传递参数给resume.resume使得协同函数挂起->运行

【吐血推荐】简要分析unity3d中剪不断理还乱的yield

在学习unity3d的时候很容易看到下面这个例子: 1 void Start () { 2 StartCoroutine(Destroy()); 3 } 4 5 IEnumerator Destroy(){ 6 yield return WaitForSeconds(3.0f); 7 Destroy(gameObject); 8 } 这个函数干的事情很简单:调用StartCoroutine函数开启协程,yield等待一段时间后,销毁这个对象:由于是协程在等待,所以不影响主线程操作.一般来说,看到

【转】简要分析unity3d中剪不断理还乱的yield

在学习unity3d的时候很容易看到下面这个例子: 1 void Start () { 2 StartCoroutine(Destroy()); 3 } 4 5 IEnumerator Destroy(){ 6 yield return WaitForSeconds(3.0f); 7 Destroy(gameObject); 8 } 这个函数干的事情很简单:调用StartCoroutine函数开启协程,yield等待一段时间后,销毁这个对象:由于是协程在等待,所以不影响主线程操作.一般来说,看到

简要分析unity3d中剪不断理还乱的yield

在学习unity3d的时候非常easy看到以下这个样例: 1 void Start () { 2 StartCoroutine(Destroy()); 3 } 4 5 IEnumerator Destroy(){ 6 yield return WaitForSeconds(3.0f); 7 Destroy(gameObject); 8 } 这个函数干的事情非常easy:调用StartCoroutine函数开启协程.yield等待一段时间后,销毁这个对象:因为是协程在等待.所以不影响主线程操作.

【转】Unity3D中脚本的执行顺序和编译顺序

支持原文,原文请戳: Unity3D中脚本的执行顺序和编译顺序 在Unity中可以同时创建很多脚本,并且可以分别绑定到不同的游戏对象上,它们各自都在自己的生命周期中运行.与脚本有关的也就是编译和执行啦,本文就来研究一下Unity中脚本的编译和执行顺序的问题. 事件函数的执行顺序 先说一下执行顺序吧. 官方给出的脚本中事件函数的执行顺序如下图:  我们可以做一个小实验来测试一下: 在Hierarchy视图中创建三个游戏对象,在Project视图中创建三条脚本,如下图所示,然后按照顺序将脚本绑定到对

【Unity3d游戏开发】Unity3D中的3D数学基础---向量

向量是2D.3D数学研究的标准工具,在3D游戏中向量是基础.因此掌握好向量的一些基本概念以及属性和常用运算方法就显得尤为重要.在本篇博客中,马三就来和大家一起回顾和学习一下Unity3D中那些常用的3D数学知识. 一.向量概念及基本定义 1.向量的数学定义 向量就是一个数字列表,对于程序员来说一个向量就是一个数组. 向量的维度就是向量包含的"数"的数目,向量可以有任意正数维,标量可以被认为是一维向量. 书写向量时,用方括号将一列数括起来,如[1,2,3] 水平书写的向量叫行向量 垂直书

Unity3D中中 rect[2] == rt-&gt;GetGLWidth() &amp;&amp; rect[3] == rt-&gt;GetGLHeight()错误的原因及解决方法

首先说明下:这种错误只在Unity3D发生,不会在打包的游戏中发生. 官方解释: Camera with image effects throws error when certain game view aspect ratios are used To reproduce:1. Create a camera with Clear Flags set to "Depth Only" or "Don't Clear"2. Attach an image effec

Unity3D中的第三人称镜头的脚本控制

原地址:http://blog.csdn.net/mobanchengshuang/article/details/27591271 好久没有敲Blog了,谢谢大家的留言.关注.私信等支持,但是我好像已经没有办法让自己继续写以前的博客系列了,因为我发现网上关于unity3D的内容太少了,所以我无法自拔地想写U3D相关的文章!!! 第三人称视角 第三人称视角是什么?很简单,CS就是一种第一人称视角游戏,玩家没有办法看到自己的角色形象,只能观察除开自己之外的游戏内容.第三人称视角那么就明显是能够看到