文章目录
一、前言
二、学习使用协程
1.首先定义多个定时器,去实现游戏中的逻辑...
2.案例演示
3.开启和终止协程
4.协程的返回值
5.案例应用
三、总结
前言
协程在Unity开发中非常重要,但注意:协程跟多线程没有任何关系,不要将两者混为一谈,接下来就跟大家分享一下我对协程的理解及用法!
一、协程是什么?
协程是一段在主线程中执行的代码逻辑,协程不是多线程。Unity的协程在每帧结束后去检测yiled的条件是否满足。
二、学习使用协程
1.首先定义多个定时器,去实现游戏中的逻辑...
代码如下:
float timer1 = 3f;
float timer2 = 5f;
float timer3 = 8f;
void Update()
{
timer1 -= Time.deltaTime;
if (timer1 <= 0)
{
Debug.Log("3s过后...");
timer1 = 3f;
}
}
相信大家都写过类似代码,这种代码如果项目中需要多个定时器时,会显得非常臃肿,并且我们经常忘记做一件事情,比如忘记充值定时器...
我们都学过循环,for循环中是将变量i定义为局部变量,封装成一个代码块,那我们是否可以将定时器也封装成一个代码块呢?如果可以的话,那么代码应该是这样的:
for (float timer = 3; timer >= 0; timer -= Time.deltaTime)
{
}
现在每一个计时器变量都成为for循环的一部分了,这看上去好多了,而且我不需要去单独设置每一个迭代变量。 但是这段代码放在哪里去执行呢?start?update?显然都不可以,所以恰好协程可以做到这一点。我们回顾一下协程的概念:
为了能在连续的多帧中(在这个例子中,3秒钟等同于很多帧)调用该方法,Unity必须通过某种方式来存储这个方法的状态,
-这是通过IEnumerator 中使用yield return语句得到的返回值,当你“yield”一个方法时,你相当于说了,“现在暂停这个方法,
-然后在下一帧中从这里继续执行!”。
注意:用0或者null来yield的意思是告诉协程等待下一帧,直到继续执行为止。
当然,同样的你可以继续yield其他协程,我会在下一个教程中讲到这些。
代码如下:
IEnumerator CountDown(){
for (float timer = 3; timer >= 0; timer -= Time.deltaTime)
{
yield return 0;//现在停止这个方法,然后在下一帧中从这里继续执行!
}
Debug.Log("3s以后...");
}
2.案例演示
/*
* 接下来通过实例
* 1.实现打印5次--我要学游戏开发!
* 2.实现将这5次输出分到每一帧里去实现:每帧打印1次,共打印5次!
* 3.每一帧输出“我要学游戏开发!”,无限循环。。。
通过在一个while循环中使用yield,你可以得到一个无限循环的协程,这几乎就跟一个Update()循环等同。。。
*/
2.代码如下:
IEnumerator SayHello5Times()
{
for (int i = 0; i < 5; i++)
{
Debug.Log("我要学游戏开发!");
yield return 0;
}
3.类似Update,代码如下:
IEnumerator SayHello5Times()
{
while (true)
{
//1.输出结果
Debug.Log("我要学游戏开发!");
//2.等待下一帧
yield return 0;
}
//1.输出结果
Debug.Log("我要学游戏开发!");
//2.等待下一帧
//3. 这里永远没有机会执行
}
但是跟Update()不一样的是,你可以在协程中做一些更有趣的事:
接下来做一个定时器 每隔几秒完成某一件事
IEnumerator CountSeconds()
{
int seconds = 0;
while (true)
{
for (float timer = 0; timer < 1; timer += Time.deltaTime)
{
yield return 0;
}
seconds++;
Debug.Log("自协程启动以来已经过了"+ seconds+"秒");
}
}
这个方法突出了协程一个非常酷的地方:方法的状态被存储了,这使得方法中定义的这些变量都会保存它们的值,即使是在不同的帧中。还记得这个教程开始时那些烦人的计时器变量吗?通过协程,我们再也不需要担心它们了,只需要把变量直接放到方法里面!实际还有更优雅的实现方式!稍后会跟大家讲到。
3.开启和终止协程
之前,我们已经学过了通过 StartCoroutine()方法来开始一个协程。
如果我们想要终止所有的协程,可以通过StopAllCoroutines()方法来实现,
注意,这只会终止在调用该方法的对象中开始的协程,对于其他的MonoBehavior类中运行的协程不起作用。
那我们怎么终止其中的一个协程呢?在这个例子里,这是不能的,如果你想要终止某一个特定的协程,
那么你必须得在开始协程的时候将它的方法名作为字符串,就像这样:
1、以字符串开启/关闭,缺点:只能有一个参数
StartCoroutine("FirstTimer");
StopCoroutine("FirstTimer”);
2、开启带有参数的协程的两种方式:
StartCoroutine(Sayhi("hi"))
StartCoroutine("Sayhi","hi")
3、如何终止多个参数的协程呢?接受返回值
Coroutine stopCor_2 = StartCoroutine(Cor_2());
StopCoroutine(stopCor_2);
4、StopAllCoroutines
5、通知禁用或者销毁方式
gameObject.SetActive(false);
//通过销毁游戏对象方式和禁用同效果
//Destroy(gameobject)
4.协程的返回值
协程一旦被开启后 总是试图将方法内的代码执行完 之后停止
1.在此之前,我们yield的时候总是用0(或者null),仅仅告诉程序在继续执行前等待下一帧。协程最强大的一个功能就是它们可以通过使用yield语句来相互嵌套。
2.yield return new WaitForSeconds(n) 表示在n秒后执行后面的代码 但是会收到time.timescale 影响 ,如下代码:
//隔一定时间完成某件事
IEnumerator SaySomeThings()
{
Debug.Log("协程开始执行");
yield return StartCoroutine(Wait(1.0f));
Debug.Log("距离上一条消息已经过去1秒了");
yield return StartCoroutine(Wait(2.5f));
Debug.Log("距离上一条消息已经过去2.5秒了");
}
上述方法用了yield,但它并没有用0或者null,而是用了Wait()来yield,这相当于是说,“不再继续执行本程序,直到Wait程序结束”。
等待的方法还可以使用下面方式来实现:
IEnumerator Wait(float duration)
{
for (float timer = 0; timer < duration; timer += Time.deltaTime)
yield return 0;
}
3.在协程内 如果遇到yield return StartCoroutine(test) 剩余的代码将在子协程执行完毕后才能继续执行
4.如果遇到 yield return new WaitForFixedUpdate 表示剩余代码将在FixedUpdate 执行完毕后执行
5.如果遇到 yield return WWW 等待一个网络请求完成后继续向下执行
6.如果遇到 yield return gameObject; 表示在gameobj不为空时向下执行
5.案例应用
控制对象行为的例子
在最后一个例子中,我们就来看看协程如何像创建方便的计时器一样来控制对象行为。协程不仅仅可以使用计数的时间来yield,它还能很巧妙地利用任何条件。将它与嵌套结合使用,你会得到控制游戏对象状态的最强大工具。运动到某一位置,对于下面这个简单脚本组件,我们可以在Inspector面板中给targetPosition和moveSpeed变量赋值,程序运行的时候,该对象就会在协程的作用下,以我们给定的速度运动到给定的位置。
代码如下:
public Vector3 targetPosition;
public float moveSpeed;
void Start1()
{
StartCoroutine(MoveToPosition(targetPosition));
}
IEnumerator MoveToPosition(Vector3 target)
{
while (transform.position != target)
{
transform.position = Vector3.MoveTowards(transform.position, target, moveSpeed * Time.deltaTime);
yield return 0;
}
}
这样,这个程序并没有通过一个计时器或者无限循环,而是根据对象是否到达指定位置来yield。
我们可以让运动到某一位置的程序做更多,不仅仅是一个指定位置,我们还可以通过数组来给它赋值更多的位置,通过MoveToPosition() ,我们可以让它在这些点之间持续运动。
代码如下:
public Vector3[] path;
void Start2()
{
StartCoroutine(MoveOnPath(true));
}
IEnumerator MoveOnPath(bool loop)
{
do
{
foreach (var point in path)
yield return StartCoroutine(MoveToPosition(point));
}
while (loop);
}
还可以加一个布尔变量,你可以控制在对象运动到最后一个点时是否要进行循环。
课堂练习:尝试让物体在某个点停留3s
如果把Wait()程序加进来,这样就能让我们的对象在某个点就可以选择是否暂停下来,就像一个正在巡逻的AI守卫一样,并且这种实现方式看起来非常优雅!
三、总结
l 多个协程可以同时运行,它们会根据各自的启动顺序来更新;
l 协程可以嵌套任意多层(在这个例子中我们只嵌套了一层);
l 如果你想让多个脚本访问一个协程,那么你可以定义静态的协程;
l 协程不是多线程(尽管它们看上去是这样的),它们运行在同一线程中,跟普通的脚本一样;
l 如果你的程序需要进行大量的计算,那么可以考虑在一个随时间进行的协程中处理它们;
l IEnumerator类型的方法不能带ref或者out型的参数,但可以带被传递的引用;
l 协程有多种开启和终止的方法,但是最好用哪种方式开启,就是用哪种方式终止。
更多关于“Unity”的问题,欢迎咨询千锋教育在线名师。千锋教育多年办学,课程大纲紧跟企业需求,更科学更严谨,每年培养泛IT人才近2万人。不论你是零基础还是想提升,都可以找到适合的班型,千锋教育随时欢迎你来试听。