Unity中的模式——协同程序介绍




原文:Introduction to Coroutines

Unity的系统程序系统的能力由C#的IEnumerator提供,IEnumerator是一个简单但是强大的接口,这个接口允许你写自己的可数集合类型。但是你不必在意这个,让我们直接跳到一个简单的例子,这个例子展示了协同程序可以做的事情。首先,让我们看一个简单的一小块代码:

The Countdown Timer
这里有一个简单的组件,组件仅仅减少它的timer字段,输出一个消息——timer到达了0。
using UnityEngine;
using System.Collections;
 
public class Countdown : MonoBehaviour
{
    public float timer = 3;
 
    void Update()
    {
        timer -= Time.deltaTime;
        if (timer <= 0)
            Debug.Log("Timer has finished!");
    }
}

不是太坏,非常少的代码达到了它的目标。但是有一个问题,如果我们有更多的组件(像一个Player或者Enemy类)它们有多个计时器,怎么办?我们的代码看起来就像这样:
using UnityEngine;
using System.Collections;
 
public class MultiTimer : MonoBehaviour
{
    public float firstTimer = 3;
    public float secondTimer = 2;
    public float thirdTimer = 1;
 
    void Update()
    {
        firstTimer -= Time.deltaTime;
        if (firstTimer <= 0)
            Debug.Log("First timer has finished!");
 
        secondTimer -= Time.deltaTime;
        if (secondTimer <= 0)
            Debug.Log("Second timer has finished!");
 
        thirdTimer -= Time.deltaTime;
        if (thirdTimer <= 0)
            Debug.Log("Third timer has finished!");
    }
}
这也不算太坏,但是就个人而言,我不喜欢这些计时的变量让我的代码乱作一团。感觉很脏,我总是不得不确保在重新计时的时候重置它们(但是我经常忘记)。
如果我用for循环是否会变得好些?
for (float timer = 3; timer >= 0; timer -= Time.deltaTime)
{
    //Just do nothing...
}
Debug.Log("This happens after 5 seconds!");
这看起来很整齐,因为现在每一个计时的变量仅仅作为循环体的一部分,我不需要重复设置这些变量了。
好的,你可能了解了我大概的想法:协同程序可以将这件事做的很漂亮!

进入协同程序
现在,这里有一个和上边完全相同的例子,但是使用了一个协同程序!我建议你写一个简单的组件并且从现在开始跟着我的步骤,你自己再看看发生的事情。
using UnityEngine;
using System.Collections;
 
public class CoroutineCountdown : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(Countdown());
    }
 
    IEnumerator Countdown()
    {
        for (float timer = 3; timer >= 0; timer -= Time.deltaTime)
            yield return 0;
 
        Debug.Log("This message appears after 3 seconds!");
    }
}
这里看起来有一点不同,我会解释这里发生了什么。
StartCoroutine(Countdown());
这一行开始了我们的Countdown方法。注意我没有给Countdown传入一个引用,而是调用这个函数自身(有效的传递一个倒计时的值)

Yielding
这个Countdown函数能很好的自行解释他自己,除了两个部分:
*IEnumerator返回值
*for循环中的yield返回值
为了在多个帧中运行这个方法(这里指的是3秒内的帧),Unity通过某些方式已经存储了这个方法的状态。它使用IEnumerator类型代表yield  return返回调用的值。当你yield一个方法的时候,就会说:“现在停止这个方法,在下一帧重新从这里开始执行!”

注意:yield 0或者null会告诉协同程序进入等待,直到下一帧继续之前。但是你也可以yield其他的协同程序,这个我会在一个课程中讲解。

一些例子
协同程序在一开始是相当让人困惑的,我看到新的和有经验的程序员在协同语法上都瞪大了他们的眼镜。所以,我会尽可能的通过例子来说明,这里有几个简单的协同程序的例子:

在一段时间里说“Hello”
记住,yield return 说:“停止这个函数,下一帧在继续执行”,这意味着可以做这些:
//This will say hello 5 times, once each frame for 5 frames
IEnumerator SayHelloFiveTimes()
{
    yield return 0;
    Debug.Log("Hello");
    yield return 0;
    Debug.Log("Hello");
    yield return 0;
    Debug.Log("Hello");
    yield return 0;
    Debug.Log("Hello");
    yield return 0;
    Debug.Log("Hello");
}
 
//This will do the exact same thing as the above function!
IEnumerator SayHello5Times()
{
    for (int i = 0; i < 5; i++)
    {
        Debug.Log("Hello");
        yield return 0;
    }
}
永远的....在每一帧说“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 0;
 
    } //3. This is a forever-loop, goto 1
}

记录 秒
...但是不像在Update()里,你可以协同程序里做一些花哨的事情,像这样:
IEnumerator CountSeconds()
{
    int seconds = 0;
 
    while (true)
    {
        for (float timer = 0; timer < 1; timer += Time.deltaTime)
            yield return 0;
 
        seconds++;
        Debug.Log(seconds + " seconds have passed since the Coroutine started.");
    }
}
这个函数的让协同程序亮了:这个函数的状态被存了起来,所以在函数体中任何定义的变量都会保持他们的值,甚至在两个帧之间。还记得在教程一开始的令人讨厌的计时变量吗?使用协同函数,我们不再需要他们,我们只需要在函数体中放置这些变量!

开始&&停止&&协同程序
之前,我们学习了通过调用StartCoroutine()方法启动一个协同程序,像这样:
StartCoroutine(Countdown());
如果我们想要停止所有的协同程序,我们可以使用StopAllCoroutines()函数,它做的就像它的名字中承诺的一样。注意它只会停止这些协同程序——被该物体启动的协同程序,而不是其他MonoBehaviours运行的协同函数。
但是,如果我们有两个协同程序在运行会怎样,像这样:
StartCoroutine(FirstTimer());
StartCoroutine(SecondTimer());
...我们想要停止他们中的一个,怎么办?这种情况是不行的。如果我们想要停止一个特定的协同程序,你必须使用一个string为参数的函数,像这样:
//If you start a Coroutine by name...
StartCoroutine("FirstTimer");
StartCoroutine("SecondTimer");
 
//You can stop it anytime by name!
StopCoroutine("FirstTimer");

额外的链接:





  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值