Unity3D协程介绍 以及 使用

 

做者ChevyRay ,2013年9月28日,snaker7译  原文地址:http://unitypatterns.com/introduction-to-coroutines/html

在Unity中,协程(Coroutines)的形式是我最喜欢的功能之一,几乎在全部的项目中,我都会使用它来控制运动,序列,以及对象的行为。在这个教程中,我将会说明协程是如何工做的,而且会附上一些例子来介绍它的用法。程序员

 

 

 

 

协程介绍数组

 

Unity的协程系统是基于C#的一个简单而强大的接口 ,IEnumerator,它容许你为本身的集合类型编写枚举器。这一点你没必要关注太多,咱们直接进入一个简单的例子来看看协程到底能干什么。首先,咱们来看一下这段简单的代码...多线程

 

倒计时器app

这是一个简单的脚本组件,只作了倒计时,而且在到达0的时候log一个信息。函数

 

[csharp]  view plain  copy
 
  1. using Unity Engine;  
  2. using System.Collections;  
  3.    
  4. public class Countdown : MonoBehaviour  
  5. {  
  6.     public float timer = 3;  
  7.    
  8.     void Update()  
  9.     {  
  10.         timer -= Time.deltaTime;  
  11.         if(timer <= 0)  
  12.             Debug.Log("Timer has finished!");  
  13.     }  
  14. }  

 

 

还不错,代码简短实用,但问题是,若是咱们须要复杂的脚本组件(像一个角色或者敌人的类),拥有多个计时器呢?刚开始的时候,咱们的代码也许会是这样的:工具

 

[csharp]  view plain  copy
 
  1. using UnityEngine;  
  2. using System.Collections;  
  3.    
  4. public class MultiTimer : MonoBehaviour  
  5. {  
  6.     public float firstTimer = 3;  
  7.     public float secondTimer = 2;  
  8.     public float thirdTimer = 1;  
  9.    
  10.     void Update()  
  11.     {  
  12.         firstTimer -= Time.deltaTime;  
  13.         if(firstTimer <= 0)  
  14.             Debug.Log("First timer has finished!");  
  15.    
  16.         secondTimer -= Time.deltaTime;  
  17.         if(secondTimer <= 0)  
  18.             Debug.Log("Second timer has finished!");  
  19.    
  20.         thirdTimer -= Time.deltaTime;  
  21.         if(thirdTimer <= 0)  
  22.             Debug.Log("Third timer has finished!");  
  23.     }  
  24. }  

 

 

尽管不是太糟糕,可是我我的不是很喜欢本身的代码中充斥着这些计时器变量,它们看上去很乱,并且当我须要从新开始计时的时候还得记得去重置它们(这活我常常忘记作)。oop

 

若是我只用一个for循环来作这些,看上去是否会好不少?post

 

[csharp]  view plain  copy
 
  1. for(float timer = 3; timer >= 0; timer -= Time.deltaTime)  
  2. {  
  3.     //Just do nothing...  
  4. }  
  5. Debug.Log("This happens after 5 seconds!");  

 

 

如今每个计时器变量都成为for循环的一部分了,这看上去好多了,并且我不须要去单独设置每个跌倒变量。学习

 

好的,你可能如今明白个人意思:协程能够作的正是这一点!

 

码入你的协程!

如今,这里提供了上面例子运用协程的版本!我建议你从这里开始跟着我来写一个简单的脚本组件,这样你能够在你本身的程序中看到它是如何工做的。

 

[csharp]  view plain  copy
 
  1. using UnityEngine;  
  2. using System.Collections;  
  3.    
  4. public class CoroutineCountdown : MonoBehaviour  
  5. {  
  6.     void Start()  
  7.     {  
  8.         StartCoroutine(Countdown());  
  9.     }  
  10.    
  11.     IEnumerator Countdown()  
  12.     {  
  13.         for(floattimer = 3; timer >= 0; timer -= Time.deltaTime)  
  14.             Yield return 0;  
  15.    
  16.         Debug.Log("This message appears after 3 seconds!");  
  17.     }  
  18. }  

 

 

这看上去有点不同,不要紧,接下来我会解释这里到底发生了什么。

 

 

[csharp]  view plain  copy
 
  1. StartCoroutine(Countdown());  

 

 

这一行用来开始咱们的Countdown程序,注意,我并无给它传入参数,可是这个方法调用了它本身(这是经过传递Countdown的return返回值来实现的)。

 

Yield

在Countdown方法中其余的都很好理解,除了两个部分:

l IEnumerator 的返回值

l For循环中的yield return

 

为了能在连续的多帧中(在这个例子中,3秒钟等同于不少帧)调用该方法,Unity必须经过某种方式来存储这个方法的状态,这是经过IEnumerator 中使用yield return语句获得的返回值,当你“yield”一个方法时,你至关于说了,“如今中止这个方法,而后在下一帧中从这里从新开始!”。

 

注意:用0或者null来yield的意思是告诉协程等待下一帧,直到继续执行为止。固然,一样的你能够继续yield其余协程,我会在下一个教程中讲到这些。

 

一些例子

协程在刚开始接触的时候是很是难以理解的,不管是新手仍是经验丰富的程序员我都见过他们对于协程语句束手无策的时候。所以我认为经过例子来理解它是最好的方法,这里有一些简单的协程例子:

 

屡次输出“Hello”

记住,yield return是“中止执行方法,而且在下一帧从这里从新开始”,这意味着你能够这样作:

 

[csharp]  view plain  copy
 
  1. //This will say hello 5 times, once each frame for 5 frames  
  2. IEnumerator SayHelloFiveTimes()  
  3. {  
  4.     Yield return 0;  
  5.     Debug.Log("Hello");  
  6.     Yield return 0;  
  7.     Debug.Log("Hello");  
  8.     Yield return 0;  
  9.     Debug.Log("Hello");  
  10.     Yield return 0;  
  11.     Debug.Log("Hello");  
  12.     Yield return 0;  
  13.     Debug.Log("Hello");  
  14. }  
  15.    
  16. //This will do the exact same thing as the above function!  
  17. IEnumerator SayHello5Times()  
  18. {  
  19.     for(inti = 0; i < 5; i++)  
  20.     {  
  21.         Debug.Log("Hello");  
  22.         Yield return 0;  
  23.     }  
  24. }  

 

 

每一帧输出“Hello”,无限循环。。。

经过在一个while循环中使用yield,你能够获得一个无限循环的协程,这几乎就跟一个Update()循环等同。。。

 

[csharp]  view plain  copy
 
  1. //Once started, this will run until manually stopped or the object is destroyed  
  2. IEnumerator SayHelloEveryFrame()  
  3. {  
  4.     while(true)  
  5.     {  
  6.         //1. Say hello  
  7.         Debug.Log("Hello");  
  8.    
  9.         //2. Wait until next frame  
  10.         Yield return 0;  
  11.    
  12.     }//3. This is a forever-loop, goto 1  
  13. }  

 

 

计时

...不过跟Update()不同的是,你能够在协程中作一些更有趣的事:

 

[csharp]  view plain  copy
 
  1. IEnumerator CountSeconds()  
  2. {  
  3.     int seconds = 0;  
  4.    
  5.     while(true)  
  6.     {  
  7.         for(float timer = 0; timer < 1; timer += Time.deltaTime)  
  8.             Yield return 0;  
  9.    
  10.         seconds++;  
  11.         Debug.Log(seconds +" seconds have passed since the Coroutine started.");  
  12.     }  
  13. }  

 

 

这个方法突出了协程一个很是酷的地方:方法的状态被存储了,这使得方法中定义的这些变量都会保存它们的值,即便是在不一样的帧中。还记得这个教程开始时那些烦人的计时器变量吗?经过协程,咱们不再须要担忧它们了,只须要把变量直接放到方法里面!

 

开始和终止协程

以前,咱们已经学过了经过 StartCoroutine()方法来开始一个协程,就像这样:

 

[csharp]  view plain  copy
 
  1. StartCoroutine(Countdown());  

 

若是咱们想要终止全部的协程,能够经过StopAllCoroutines()方法来实现,它的所要作的就跟它的名字所表达的同样。注意,这只会终止在调用该方法的对象中(应该是指调用这个方法的类吧)开始的协程,对于其余的MonoBehavior类中运行的协程不起做用。

 

若是咱们有如下这样两条协程语句:

 

[csharp]  view plain  copy
 
  1. StartCoroutine(FirstTimer());  
  2. StartCoroutine(SecondTimer());  

 

。。。那咱们怎么终止其中的一个协程呢?在这个例子里,这是不可能的,若是你想要终止某一个特定的协程,那么你必须得在开始协程的时候将它的方法名做为字符串,就像这样:

 

[csharp]  view plain  copy
 
  1. //If you start a Coroutine by name...  
  2. StartCoroutine("FirstTimer");  
  3. StartCoroutine("SecondTimer");  
  4.    
  5. //You can stop it anytime by name!  
  6. StopCoroutine("FirstTimer");  

 

 

更多关于协程的学习

即将为你带来:“Scripting with Coroutines”,一个更深刻的介绍,关于如何使用协程以及如何经过协程编写对象行为。

 

扩展连接

Coroutines – Unity Script Reference

若是你知道其余很棒的关于协程的Unity教程,或者相关的主题,请在回复中分享连接!固然,若是在教程有什么问题,好比连接无效或者其余一些问题,欢迎给我发邮件

 

 

 

 

 

做者ChevyRay ,2013年9月28日,snaker7译 原文地址:http://unitypatterns.com/scripting-with-coroutines/

请注意:这个关于协程的教程共有两部分,这是第二部分,若是您不曾看过第一部分——协程介绍,那么在阅读这部份内容以前建议您先了解一下。

计时器例子

第一个教程中,咱们已经了解了协程如何让一个方法“暂停”下来,而且让它yield直到某些值到达咱们给定的数值;而且利用它,咱们还建立了一个很棒的计时器系统。协程一个很重要的内容是,它可让普通的程序(比方说一个计时器)很容易地被抽象化而且被复用。

 

协程的参数

抽象化一个协程的第一个方法是给它传递参数,协程做为一个函数方法来讲,它天然可以传递参数。这里有一个协程的例子,它在特定的地方输出了特定的信息。

 

[csharp]  view plain copy
 
 
  1. Using UnityEngine;  
  2. Using System.Collections;  
  3.    
  4. Public class TimerExample : MonoBehaviour  
  5. {  
  6.     Void Start()  
  7.     {  
  8.         //Log "Hello!" 5 times with 1 second between each log  
  9.         StartCoroutine(RepeatMessage(5, 1.0f,"Hello!"));  
  10.     }  
  11.    
  12.     IEnumerator RepeatMessage(int count,float frequency,string message)  
  13.     {  
  14.         for(int i = 0; i < count; i++)  
  15.         {  
  16.             Debug.Log(message);  
  17.             for(float timer = 0; timer < frequency; timer += Time.deltaTime)  
  18.                 Yield return 0;  
  19.                
  20.         }  
  21.     }  
  22. }  

 

嵌套的协程

在此以前,咱们yield的时候老是用0(或者null),仅仅告诉程序在继续执行前等待下一帧。协程最强大的一个功能就是它们能够经过使用yield语句来相互嵌套。

眼见为实,咱们先来建立一个简单的Wait()程序,不须要它作任何事,只须要在运行的时候等待一段时间就结束。

 

[csharp]  view plain copy
 
 
  1. IEnumerator Wait(float duration)  
  2. {  
  3.     for(float timer = 0; timer < duration; timer += Time.deltaTime)  
  4.         Yield return 0;  
  5. }  

 

接下来咱们要编写另外一个协程,以下:

 

[csharp]  view plain copy
 
 
  1. Using UnityEngine;  
  2. Using System.Collections;  
  3.    
  4. Public class TimerExample : MonoBehaviour  
  5. {  
  6.     voidStart()  
  7.     {  
  8.         StartCoroutine(SaySomeThings());  
  9.     }  
  10.    
  11.     //Say some messages separated by time  
  12.     IEnumerator SaySomeThings()  
  13.     {  
  14.         Debug.Log("The routine has started");  
  15.         Yield return StartCoroutine(Wait(1.0f));  
  16.         Debug.Log("1 second has passed since the last message");  
  17.         Yield return StartCoroutine(Wait(2.5f));  
  18.         Debug.Log("2.5 seconds have passed since the last message");  
  19.     }  
  20.    
  21.     //Our wait function  
  22.     IEnumerator Wait(float duration)  
  23.     {  
  24.         for(float timer = 0; timer < duration; timer += Time.deltaTime)  
  25.             Yield return 0;  
  26.     }  
  27. }  

 

第二个方法用了yield,但它并无用0或者null,而是用了Wait()来yield,这至关因而说,“再也不继续执行本程序,直到Wait程序结束”。

如今,协程在程序设计方面的能力要开始展示了。

 

控制对象行为的例子

在最后一个例子中,咱们就来看看协程如何像建立方便的计时器同样来控制对象行为。协程不只仅可使用可计数的时间来yield,它还能很巧妙地利用任何条件。将它与嵌套结合使用,你会获得控制游戏对象状态的最强大工具。

运动到某一位置

对于下面这个简单脚本组件,咱们能够在Inspector面板中给targetPosition和moveSpeed变量赋值,程序运行的时候,该对象就会在协程的做用下,以咱们给定的速度运动到给定的位置。

 

[csharp]  view plain copy
 
 
  1. usingUnityEngine;  
  2. Using System.Collections;  
  3.    
  4. Public class MoveExample : MonoBehaviour  
  5. {  
  6.     ublic Vector3 targetPosition;  
  7.     ublic float moveSpeed;  
  8.    
  9.     Void Start()  
  10.     {  
  11.         StartCoroutine(MoveToPosition(targetPosition));  
  12.     }  
  13.    
  14.     IEnumerator MoveToPosition(Vector3 target)  
  15.     {  
  16.         while(transform.position != target)  
  17.         {  
  18.             transform.position = Vector3.MoveTowards(transform.position, target, moveSpeed * Time.deltaTime);  
  19.             Yield return 0;  
  20.         }  
  21.     }  
  22. }  

 

这样,这个程序并无经过一个计时器或者无限循环,而是根据对象是否到达指定位置来yield。

 

按指定路径前进

咱们可让运动到某一位置的程序作更多,不只仅是一个指定位置,咱们还能够经过数组来给它赋值更多的位置,经过MoveToPosition() ,咱们可让它在这些点之间持续运动。

 

[csharp]  view plain copy
 
 
  1. Using UnityEngine;  
  2. Using System.Collections;  
  3.    
  4. Public class MoveExample : MonoBehaviour  
  5. {  
  6.     ublic Vector3[] path;  
  7.     ublic float moveSpeed;  
  8.    
  9.     Void Start()  
  10.     {  
  11.         StartCoroutine(MoveOnPath(true));  
  12.     }  
  13.    
  14.     IEnumerator MoveOnPath(bool loop)  
  15.     {  
  16.         do  
  17.         {  
  18.             foreach(var point in path)  
  19.                 Yield return StartCoroutine(MoveToPosition(point));  
  20.         }  
  21.         while(loop);  
  22.     }  
  23.    
  24.     IEnumerator MoveToPosition(Vector3 target)  
  25.     {  
  26.         while(transform.position != target)  
  27.         {  
  28.             transform.position = Vector3.MoveTowards(transform.position, target, moveSpeed * Time.deltaTime);  
  29.             Yield return 0;  
  30.         }  
  31.     }  
  32. }  

 

 

我还加了一个布尔变量,你能够控制在对象运动到最后一个点时是否要进行循环。

把Wait()程序加进来,这样就能让咱们的对象在某个点就能够选择是否暂停下来,就像一个正在巡逻的AI守卫同样,这真是锦上添花啊!

 

注意:

若是你刚接触协程,我但愿这两个教程能帮助你了解它们是如何工做的,以及如何来使用它们。如下是一些在使用协程时须谨记的其余注意事项:

 

  • l 在程序中调用StopCoroutine()方法只能终止以字符串形式启动(开始)的协程;
  • l 多个协程能够同时运行,它们会根据各自的启动顺序来更新;
  • l 协程能够嵌套任意多层(在这个例子中咱们只嵌套了一层);
  • l 若是你想让多个脚本访问一个协程,那么你能够定义静态的协程;
  • l 协程不是多线程(尽管它们看上去是这样的),它们运行在同一线程中,跟普通的脚本同样;
  • l 若是你的程序须要进行大量的计算,那么能够考虑在一个随时间进行的协程中处理它们;
  • l IEnumerator类型的方法不能带ref或者out型的参数,但能够带被传递的引用;
  • l 目前在Unity中没有简便的方法来检测做用于对象的协程数量以及具体是哪些协程做用在对象上。

 

若是您发现教程中存在问题和错误的信息,或者有任何建议又或者您想要在这里看到其余须要的教程,能够发邮件或者在评论中留言。

相关文章
相关标签/搜索