[译文]使用Coroutine进行编程

原文链接在这里[Unity Patterns | Scripting with Coroutines]

序言

在Unity中,Coroutine是我最喜欢的特性之一。我几乎在所有的项目中都会用它来控制角色的运动,连续的行为以及对象的行为等等。在这个教程中,我将会详细介绍Coroutine的工作原理,同时还会通过几个实际的小例子来演示Coroutine能如何被应用到实际的项目中。

使用Coroutines进行编程

注意:这是关于Coroutine的教程中的第二篇,如果你还没有读过这个教程中的第一篇文章的话,建议你看一下,地址在这里

还是计时器

上一篇教程中,我们学习了如何利用Coroutine的特性来暂停和继续一个方法的执行,并且让其在执行的过程中完成我们所需的操作,最终我们完成了一个非常棒的计时器。Coroutine最棒的一个特性就是,通过这些通用的程序逻辑,我们可以很容易地进行抽象和代码重用。

Coroutine参数

抽象一个Coroutine的第一步就是给它定义几个参数。因为Coroutine就是方法,所以理所当然的,它们都可以有参数。下面就是一个栗子,这个栗子会根据我们传入的频率来输出消息:

using UnityEngine;
using System.Collections;

public class TimerExample : MonoBehaviour
{
    void Start()
    {
        //Log "Hello!" 5 times with 1 second between each log
        StartCoroutine(RepeatMessage(5, 1.0f, "Hello!"));
    }

    IEnumerator RepeatMessage(int count, float frequency, string message)
    {
        for (int i = 0; i < count; i++)
        {
            Debug.Log(message);
            for (float timer = 0; timer < frequency; timer += Time.deltaTime)
                yield return 0;

        }
    }
}

嵌套Coroutine

直到现在我们在Coroutine中总是在yield return 0 或者 null,这意味着Coroutine会等到下一帧再从此处开始继续执行Coroutine的方法体。然而Coroutine牛逼之处远不止于此,这货还有一个最牛掰的技能,那就是可以通过yield return语句实现Coroutine的嵌套。

我们来看看这个嵌套Coroutine在实际代码中是如何应用的,首先让我们创建一个简单的Coroutine:Wait(),这货实际上啥也不做,就只是消耗传入时长的时间,然后结束并返回。

IEnumerator Wait(float duration)
{
    for (float timer = 0; timer < duration; timer += Time.deltaTime)
        yield return 0;
}

接下来我们再写一个Coroutine:

using UnityEngine;
using System.Collections;

public class TimerExample : MonoBehaviour
{
    void Start()
    {
        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 = 0; timer < duration; timer += Time.deltaTime)
            yield return 0;
    }
}

在这个新的方法中,我们没有再使用yield return 0或者null来返回,而是直接yield return Wait()方法,这个意思就是说在Wait()方法成功执行返回之前不继续执行SaySomeThings()方法中的代码,等到Wait()方法执行结束之后再继续执行。

接下来就是见证Coroutine真正牛逼之处的时刻。

控制对象的行为

接下来的这个栗子,我们会演示如何像创建计时器般,方便地通过Coroutine来控制目标对象的行为。Coroutine并非只能用于如此简单的计时器,结合嵌套Coroutine,我们能够随心所欲地控制游戏中各个对象的各种状态。

移动到指定位置

下面的这段代码中,我们有一个简单的脚本,脚本中有targetPosition和moveSpeed两个可以设置的变量。当我们运行游戏的时候,挂载这个脚本的对象会在场景中以我们设置的moveSpeed速度向targetPosition移动。

using UnityEngine;
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 0;
        }
    }
}

这个Coroutine就不再是等待某个计时器结束或者死循环了,它会在目标对象移动到指定目标位置之后结束。

按路径移动

我们还可以再扩展一下上面的这个栗子,例如不只是移动到某个指定的位置,而是让目标对象按照指定的路径在场景中移动,我们可以通过一组点来设定对象移动的路径。我们可以顺序遍历路径中的每个坐标点,然后依次调用MoveToPosition方法,让目标对象依次通过这些路径点。

using UnityEngine;
using System.Collections;

public class MoveExample : MonoBehaviour
{
    public Vector3[] path;
    public 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 0;
        }
    }
}

另外,我还加了个bool变量loop用于控制是否需要循环按照这个路径移动,如果loop为true,那么目标对象就会在移动到路径的重点之后,再次回到路径起点,然后再按照路径走一遍。

当然我们还可以用Wait()方法来给这个移动的方法做些锦上添花(或者说添枝加叶?)的事情,例如在每次移动到路径关键点之后调用一下Wait()方法来让目标对象休息一会儿,这样是不是就很类似于我们游戏中常见的巡逻的AI捏?

注意事项

如果你是刚接触到Coroutine这货,希望这两篇教程能帮助你对Coroutine的运行机制以及它能做些啥有个初步的了解。下面是一些在使用Coroutine的过程中可能需要注意的事项:

  • 你只能通过字面常量的Coroutine方法名来调用StopCoroutine()方法结束一个指定的Coroutine(译者注:不知道作者写这边文章的时候使用的Unity3D是哪个版本,Unity3D 4.6中已经可以通过Coroutine方法变量来结束一个指定的Coroutine了);
  • 可以同时有多个Coroutine在运行,它们会按照启动的顺序依次执行;
  • Coroutine可以无限嵌套,想怎么折腾就怎么折腾(在栗子中我们只嵌套了1层);
  • 如果你想在多个脚本中访问到某个Coroutine,你只需要将Coroutine方法声明为静态的就可以了;
  • 尽管这货看起来很像多线程的玩意儿,但实际上Coroutine并不是多线程的,它们跟你的脚本运行在同一个线程中;
  • 如果你的方法需要进行大量的计算,那么你可以考虑使用Coroutine来分步完成它;
  • IEnumerator方法不能有ref或out的参数,但是可以通过引用传入;
  • 目前没有什么好用的方法可以用来检测当前有多少个Coroutine在一个对象上执行;

如果你知道一些不错的关于Unity Coroutine的教程或者相关的教程的话,希望你能在评论中留一下这些教程的链接。
另外,如果你发现这个教程中有哪些不对的地方或者不可用的链接,或者有什么其他的问题,欢迎你联系我们

欢迎打赏

网站主机租赁需要费用,写教程呢,又是个颇费时间的活,所以你懂的。欢迎通过PayPal打赏,以便Unity Patterns能持续运营下去。 Chevy(译者注:原文作者名称)是个穷小子,给多给少都是莫大的帮助哦。


以上翻译未对原文做任何删减,酌情增加了一丢丢内容,如果翻译有错误的地方请指正,更欢迎大家用美金支持原文作者的这种分享,使用上方的PayPal链接就可以给原文作者进行捐赠支持了。

[译文]使用Coroutine进行编程》有一个想法

  1. Pingback引用通告: [译文]Coroutine简介 | 7dot9 Laputa

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据