Unity中的协程(一)
这篇文章很不错的问题,推荐阅读英文原版:
Introduction to Coroutines
Scripting with Coroutines
这篇文章转自:http://blog.csdn.net/huang9012/article/details/38492937
协程介绍
在Unity中,协程(Coroutines)的形式是我最喜欢的功能之一,几乎在所有的项目中,我都会使用它来控制运动,序列,以及对象的行为。在这个教程中,我将会说明协程是如何工作的,并且会附上一些例子来介绍它的用法。
协程介绍
Unity的协程系统是基于C#的一个简单而强大的接口 ,IEnumerator,它允许你为自己的集合类型编写枚举器。这一点你不必关注太多,我们直接进入一个简单的例子来看看协程到底能干什么。首先,我们来看一下这段简单的代码...
倒计时器
这是一个简单的脚本组件,只做了倒计时,并且在到达0的时候log一个信息。
using Unity Engine;
using System.Collections; public class Countdown : MonoBehaviour
{
public float timer = ; void Update()
{
{
timer -= Time.deltaTime;
if(timer <= )
Debug.Log("Timer has finished!");
}
}
using Unity Engine;
using System.Collections; public class Countdown : MonoBehaviour
{
public float timer = ; void Update()
{
timer -= Time.deltaTime;
if(timer <= )
Debug.Log("Timer has finished!");
}
}
还不错,代码简短实用,但问题是,如果我们需要复杂的脚本组件(像一个角色或者敌人的类),拥有多个计时器呢?刚开始的时候,我们的代码也许会是这样的:
using UnityEngine;
using System.Collections; public class MultiTimer : MonoBehaviour
{
public float firstTimer = ;
public float secondTimer = ;
public float thirdTimer = ; void Update()
{
firstTimer -= Time.deltaTime;
if(firstTimer <= )
Debug.Log("First timer has finished!"); secondTimer -= Time.deltaTime;
if(secondTimer <= )
Debug.Log("Second timer has finished!"); thirdTimer -= Time.deltaTime;
if(thirdTimer <= )
Debug.Log("Third timer has finished!");
}
}
- 尽管不是太糟糕,但是我个人不是很喜欢自己的代码中充斥着这些计时器变量,它们看上去很乱,而且当我需要重新开始计时的时候还得记得去重置它们(这活我经常忘记做)。
如果我只用一个for循环来做这些,看上去是否会好很多?
for(float timer = ; timer >= ; timer -= Time.deltaTime)
{
//Just do nothing...
}
Debug.Log("This happens after 5 seconds!");
现在每一个计时器变量都成为for循环的一部分了,这看上去好多了,而且我不需要去单独设置每一个跌倒变量。
好的,你可能现在明白我的意思:协程可以做的正是这一点!
码入你的协程!
现在,这里提供了上面例子运用协程的版本!我建议你从这里开始跟着我来写一个简单的脚本组件,这样你可以在你自己的程序中看到它是如何工作的。
using UnityEngine;
using System.Collections; public class CoroutineCountdown : MonoBehaviour
{
void Start()
{
StartCoroutine(Countdown());
} IEnumerator Countdown()
{
for(floattimer = ; timer >= ; timer -= Time.deltaTime)
Yield return ; Debug.Log("This message appears after 3 seconds!");
}
}
这看上去有点不一样,没关系,接下来我会解释这里到底发生了什么。
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是“停止执行方法,并且在下一帧从这里重新开始”,这意味着你可以这样做:
//This will say hello 5 times, once each frame for 5 frames
IEnumerator SayHelloFiveTimes()
{
Yield return ;
Debug.Log("Hello");
Yield return ;
Debug.Log("Hello");
Yield return ;
Debug.Log("Hello");
Yield return ;
Debug.Log("Hello");
Yield return ;
Debug.Log("Hello");
}
//This will do the exact same thing as the above function!
IEnumerator SayHello5Times()
{
for(inti = ; i < ; i++)
{
Debug.Log("Hello");
Yield return ;
}
}
- 每一帧输出“Hello”,无限循环。。。通过在一个while循环中使用yield,你可以得到一个无限循环的协程,这几乎就跟一个Update()循环等同。。。
//Once started, this will run until manually stopped or the object is destroyed
IEnumerator SayHelloEveryFrame()
{
while(true)
{
//1. Say hello
Debug.Log("Hello"); //2. Wait until next frame
Yield return ; }//3. This is a forever-loop, goto 1
}
计时
不过跟Update()不一样的是,你可以在协程中做一些更有趣的事:
//This will do the exact same thing as the above function!
IEnumerator SayHello5Times()
{
isUpdate = true;
for (int i = ; i < ; i++)
{
Debug.Log("Hello");
yield return ;
}
isUpdate = false;
}
- 这个方法突出了协程一个非常酷的地方:
方法的状态被存储了,这使得方法中定义的这些变量都会保存它们的值,即使是在不同的帧中。
还记得这个教程开始时那些烦人的计时器变量吗?通过协程,我们再也不需要担心它们了,只需要把变量直接放到方法里面!
开始和终止协程
之前,我们已经学过了通过 StartCoroutine()方法来开始一个协程,就像这样:
StartCoroutine(Countdown());
如果我们想要终止所有的协程,可以通过StopAllCoroutines()方法来实现,它的所要做的就跟它的名字所表达的一样。注意,这只会终止在调用该方法的对象中(应该是指调用这个方法的类吧)开始的协程,对于其他的MonoBehavior类中运行的协程不起作用。如果我们有以下这样两条协程语句:
StartCoroutine(FirstTimer());
StartCoroutine(SecondTimer());
- 那我们怎么终止其中的一个协程呢?在这个例子里,这是不可能的,如果你想要终止某一个特定的协程,那么你必须得在开始协程的时候将它的方法名作为字符串,就像这样:
//If you start a Coroutine by name...
StartCoroutine("FirstTimer");
StartCoroutine("SecondTimer"); //You can stop it anytime by name!
StopCoroutine("FirstTimer");
更多关于协程的学习
即将为你带来:“Scripting with Coroutines”,一个更深入的介绍,关于如何使用协程以及如何通过协程编写对象行为。
扩展链接
l Coroutines – Unity Script Reference
第二部分
这个关于协程的教程共有两部分,这是第二部分,如果您未曾看过第一部分——协程介绍,那么在阅读这部分内容之前建议您先了解一下。
计时器例子
在第一个教程中,我们已经了解了协程如何让一个方法“暂停”下来,并且让它yield直到某些值到达我们给定的数值;并且利用它,我们还创建了一个很棒的计时器系统。协程一个很重要的内容是,它可以让普通的程序(比方说一个计时器)很容易地被抽象化并且被复用。
协程的参数
抽象化一个协程的第一个方法是给它传递参数,协程作为一个函数方法来说,它自然能够传递参数。这里有一个协程的例子,它在特定的地方输出了特定的信息。
Using UnityEngine;
Using System.Collections; Public class TimerExample : MonoBehaviour
{
Void Start()
{
//Log "Hello!" 5 times with 1 second between each log
StartCoroutine(RepeatMessage(, 1.0f,"Hello!"));
} IEnumerator RepeatMessage(int count,float frequency,string message)
{
for(int i = ; i < count; i++)
{
Debug.Log(message);
for(float timer = ; timer < frequency; timer += Time.deltaTime)
Yield return ; }
}
}
嵌套的协程
在此之前,我们yield的时候总是用0(或者null),仅仅告诉程序在继续执行前等待下一帧。
协程最强大的一个功能就是它们可以通过使用yield语句来相互嵌套。
眼见为实,我们先来创建一个简单的Wait()程序,不需要它做任何事,只需要在运行的时候等待一段时间就结束。
IEnumerator Wait(float duration)
{
for(float timer = ; timer < duration; timer += Time.deltaTime)
Yield return ;
}
接下来我们要编写另一个协程,如下:
Using UnityEngine;
Using System.Collections; Public class TimerExample : MonoBehaviour
{
voidStart()
{
StartCoroutine(SaySomeThings());
} //Say some messages separated by time
IEnumerator SaySomeThings()
{
Debug.Log("The routine has started");
Yield return StartCoroutine(Wait(1.0f));
Debug.Log("1 second has passed since the last message");
Yield return StartCoroutine(Wait(2.5f));
Debug.Log("2.5 seconds have passed since the last message");
} //Our wait function
IEnumerator Wait(float duration)
{
for(float timer = ; timer < duration; timer += Time.deltaTime)
Yield return ;
}
}
第二个方法用了yield,但它并没有用0或者null,而是用了Wait()来yield,这相当于是说,“不再继续执行本程序,直到Wait程序结束”。
现在,协程在程序设计方面的能力要开始展现了。
控制对象行为的例子
在最后一个例子中,我们就来看看协程如何像创建方便的计时器一样来控制对象行为。协程不仅仅可以使用可计数的时间来yield,它还能很巧妙地利用任何条件。将它与嵌套结合使用,你会得到控制游戏对象状态的最强大工具。
运动到某一位置
对于下面这个简单脚本组件,我们可以在Inspector面板中给targetPosition和moveSpeed变量赋值,程序运行的时候,该对象就会在协程的作用下,以我们给定的速度运动到给定的位置。
usingUnityEngine;
Using System.Collections; Public class MoveExample : MonoBehaviour
{
public Vector3 targetPosition;
public float moveSpeed; Void Start()
{
StartCoroutine(MoveToPosition(targetPosition));
} IEnumerator MoveToPosition(Vector3 target)
{
while(transform.position != target)
{
transform.position = Vector3.MoveTowards(transform.position, target, moveSpeed * Time.deltaTime);
Yield return ;
}
}
}
- 这样,这个程序并没有通过一个计时器或者无限循环,而是根据对象是否到达指定位置来yield。
按指定路径前进
我们可以让运动到某一位置的程序做更多,不仅仅是一个指定位置,我们还可以通过数组来给它赋值更多的位置,通过MoveToPosition() ,我们可以让它在这些点之间持续运动。
Using UnityEngine;
Using System.Collections; Public class MoveExample : MonoBehaviour
{
ublic Vector3[] path;
ublic float moveSpeed; Void Start()
{
StartCoroutine(MoveOnPath(true));
} IEnumerator MoveOnPath(bool loop)
{
do
{
foreach(var point in path)
Yield return StartCoroutine(MoveToPosition(point));
}
while(loop);
} IEnumerator MoveToPosition(Vector3 target)
{
while(transform.position != target)
{
transform.position = Vector3.MoveTowards(transform.position, target, moveSpeed * Time.deltaTime);
Yield return ;
}
}
}
我还加了一个布尔变量,你可以控制在对象运动到最后一个点时是否要进行循环。
把Wait()程序加进来,这样就能让我们的对象在某个点就可以选择是否暂停下来,就像一个正在巡逻的AI守卫一样,这真是锦上添花啊!
注意:
如果你刚接触协程,我希望这两个教程能帮助你了解它们是如何工作的,以及如何来使用它们。以下是一些在使用协程时须谨记的其他注意事项:
- l 在程序中调用StopCoroutine()方法只能终止以字符串形式启动(开始)的协程;
- l 多个协程可以同时运行,它们会根据各自的启动顺序来更新;
- l 协程可以嵌套任意多层(在这个例子中我们只嵌套了一层);
- l 如果你想让多个脚本访问一个协程,那么你可以定义静态的协程;
- l 协程不是多线程(尽管它们看上去是这样的),它们运行在同一线程中,跟普通的脚本一样;
- l 如果你的程序需要进行大量的计算,那么可以考虑在一个随时间进行的协程中处理它们;
- l IEnumerator类型的方法不能带ref或者out型的参数,但可以带被传递的引用;
- l 目前在Unity中没有简便的方法来检测作用于对象的协程数量以及具体是哪些协程作用在对象上。
目前来看,这篇文章是我在网络上找到的最好的协程入门文章,不过要完全理解协程还是要多思考一下。
Unity中的协程(一)的更多相关文章
- xlua 实现协程替换Unity中的协程
C#中的协程: IEnumerator ShowSpiritInfo() { UIMessageMgr.ShowMsgWait(true); DestroyUIModelInfo(); bool is ...
- Unity中的协程是什么?
什么是协程? 1.协程是一个分部执行,遇到条件(yield return 语句)会挂起,直到条件满足才会被唤醒继续执行后面的代码. 2.Unity在每一帧(Frame)都会去处理对象上的协程.Unit ...
- Unity中使用协程进行服务端数据验证手段
近期在做项目中的个人中心的一些事情,用户头像上传,下载,本地缓存,二级缓存,压缩,这些都要做,麻雀虽小五脏俱全啊,也是写的浑浑噩噩的, 当我们在上传用户头像的时候,向服务端发送上传头像请求之前,一般都 ...
- unity 中的协程
//The coroutine will continue after all Update functionshave been calledon the next frame. ; //Conti ...
- Unity C#笔记 协程
什么是协程 协同程序,在主程序运行的同时,开启另外一段逻辑处理,来协同当前程序的执行. 可能看了这段文字介绍还是有点模糊,其实可以用多线程来比较. 多线程 多线程,顾名思义,多条同时执行的线程. 最初 ...
- lua中的协程
lua中的协程和线程类似: 1. 协程拥有自己的独立的栈,局部变量,和指令: 2. 所有协程都可以共享全局变量: 3. 协程不能像线程那样并行执行,协程之间需要相互协调执行,同一个时刻只能运行一个协程 ...
- 深入tornado中的协程
tornado使用了单进程(当然也可以多进程) + 协程 + I/O多路复用的机制,解决了C10K中因为过多的线程(进程)的上下文切换 而导致的cpu资源的浪费. tornado中的I/O多路复用前面 ...
- python中的协程及实现
1.协程的概念: 协程是一种用户态的轻量级线程.协程拥有自己的寄存器上下文和栈. 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切换回来的时候,恢复先前保存的寄存器上下文和栈. 因此,协程能保留 ...
- fasthttp中的协程池实现
fasthttp中的协程池实现 协程池可以控制并行度,复用协程.fasthttp 比 net/http 效率高很多倍的重要原因,就是利用了协程池.实现并不复杂,我们可以参考他的设计,写出高性能的应用. ...
随机推荐
- jquery easy ui 1.3.4 快速入门(1)
什么是easyui jQuery EasyUI是一组基于jQuery的UI插件集合,而jQuery EasyUI的目标就是帮助web开发者更轻松的打造出功能丰富并且美观的UI界面.开发者不需要编写复杂 ...
- oracle的启动过程(不分模式启动)
Oracle数据库的完整启动过程包含以下3个步骤: 简单地说,就是:启动实例-->加载数据库-->打开数据库. -------------------------------------- ...
- 建模算法(九)——拟合
一.线性最小二乘法 1.基本思路 令,其r(x)是事先选定的一组线性无关的函数.ak是待定系数.然后拟合的准则就是使得yi与f(xi)的距离的平方和最小,称之为最小二乘准则 2.系数的确定 ,要使距离 ...
- 让html元素随浏览器的大小自适应垂直居中
转自:http://www.cnblogs.com/linjiqin/archive/2011/06/15/2081362.html 表格可以实现td中的元素垂直居中显示,但是前提条件必须定义td的高 ...
- Android广播机制概述
1.Android广播机制概述 Android广播分为两个方面:广播发送者和广播接收者,通常情况下,BroadcastReceiver指的就是广播接收者(广播接收器).广播作为Android组件间的通 ...
- Spark服务启动的一些总结
1.我理解常用的Spark部署方式有三种 1).本地服务,就是所谓的local,在IDE上本地跑程序,用于调试 2).Standalone,使用自己的master/worker进行服务的调度. 脱离 ...
- Apple Watch开发快速入门教程
Apple Watch开发快速入门教程 试读下载地址:http://pan.baidu.com/s/1eQ8JdR0 介绍:苹果为Watch提供全新的开发框架WatchKit.本教程是国内第一本A ...
- 4.1 avd
6.接着我们回到文件夹界面,运行 AVD Manager.exe. 7.打开 AVD Manager.exe后,点击“New"创建新的模拟器: 8.创建一个新的 Android Virtua ...
- 蒟蒻修养之tc蓝名计划
开一个新坑......(听说tc是智商高的人才能玩的QAQ显然我是被屠的... 1 [645DIV2]这个能说是裸模拟吗... 弃坑= =做了一些题感觉没必要放上来了= =等div1先吧....... ...
- SVN 中trunk、branches、tags
SVN 中trunk.branches.tags 我们在一些著名开源项目的版本库中,通常可以看到trunk, branches, tags等三个目录.由于SVN固有的特点,目录在SVN中并没有特别 ...