月度归档:2015年06月

[译文] 使用 Coroutine 进行编程

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

序言

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

使用 Coroutines 进行编程

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

还是计时器

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

Coroutine 参数

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

嵌套 Coroutine

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

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

接下来我们再写一个 Coroutine:

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

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

控制对象的行为

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

移动到指定位置

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

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

按路径移动

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

另外,我还加了个 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 简介

原文链接在这里 [Unity Patterns | Introduction to Coroutines]

序言

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

Coroutine 简介

注意:这是关于 Coroutine 的教程中的第一篇,如果你有兴趣还可以读一下这个教程中的第二篇文章,地址在 这里

Unity 的 Coroutine 系统源自于 C#的 IEnumerator,IEnumerator 接口的功能简单又强大,我们可以很容易的为我们自己的集合类型定义自己的 enumarator。不过我们不用担心自定义 Enumerator 的实现是不是会太复杂,我们可以直接先来看一个例子,通过这个简单的例子我们来分析一下 Coroutine 的工作原理。首先,我们先看一段简单的代码。

计时器

这是一个非常简单的计时器脚本,计时器时间到 0 的时候,就输出一个 Log 信息,表明计时已经结束。

这段代码乍看上去还不错,代码简短同时也很好地实现了计时的需求。但是问题来了,如果我们有一些更加复杂的脚本(例如玩家角色或者怪物),这些脚本中都含有多个计时器呢?那么我们的代码最终就有可能会变成这个样子了。

其实如果能忍的话,这段代码也还算可以啦,并没有啥硬伤。但是就我个人而言,我实在不喜欢有这么多的 Timer 变量,搞得整个代码毫无美感。而且我当我想重启这些 Timer 的时候,我一定得重置这些 Timer 的值(而事实上我总忘),这简直不能忍啊,有木有。

那么如果我写个循环是不是能再优化一些呢?

嗯,看起来已经好多了,这回 Timer 变量成了循环内部的局部变量了,我也不需要再手动地设置每个循环中 Timer 的值了。

你可能已经知道接下来我要说的是啥了:对的,Coroutine 干的就是这个。

初尝 Coroutine

下面是一个跟前面我们写的计时器脚本功能一模一样的例子,只不过这个例子是使用了 Coroutine 来实现计时的功能。咱们最好实际动手敲一遍这个脚本,然后在 Unity 中运行一下,看看效果是不是一样的。

看上去跟之前的脚本还是有很大差别的呢,那么好吧,接下来我们就好好地来分析一下在这段代码里头都发生了些啥。

首先,这行代码启动了一个调用 Countdown 方法的 Coroutine。请注意在这里我直接传入了一个方法调用作为参数(这样可以有效地将 Countdown 方法的返回值当做参数传入到 StartCoroutine 方法中),而没有传入 Countdown 函数的引用。

Yielding

这个 Countdown 方法除了以下两部分内容,其实已经很好地完成了自解释的过程:

  • IEnumerator 类型的返回值
  • 循环中的 yield return 代码段

为了让 Countdown 方法能跨多帧执行(这个例子中,Countdown 方法会在三分钟之内的每一帧都执行),Unity 需要将整个函数执行的状态保存到内存的某个地方。而这个操作就是通过你使用 yield return 代码段返回的 IEnumerator 来实现的。当你“yield”一个方法的时候,实际上你就在告诉 Unity“暂时先停止执行这个方法,等到下一帧的时候再从这儿开始执行这个方法!”。

注意:yield return 代码段返回 0 或者 null 的意思就是告诉 Unity 等下一帧再从这里开始执行这个方法。但是你完全可以 yield return 其他的 Coroutine,这个我们会在下一个教程中谈到。

举些栗子

在我们刚刚开始接触 Corounines 的时候,确实比较容易被搞得稀里糊涂的,我已经看过不少挫逼和牛逼的程序员对这个 Coroutine 的语法愁眉不展了。所以捏,偶觉得还是需要通过一些栗子来让我们更快更好地去理解 Coroutine 这货:

说几次 “Hello”

首先我们要清楚一点,yield return 这货的作用就是告诉程序“运行到这里先停一下,等到程序执行到下一帧的时候再继续往下执行”,这就意味着我们可以这么搞:

不停地说 “Hello”

通过上面的代码,我们可以想象出在一个 while 循环中使用 Coroutine 的话,其实是可以达成死循环的效果滴。这个特性可以让我们很轻易的就模拟出 Update 方法的效果了,如下:

读个秒

其实我们不仅仅能模拟个 Update 方法的效果,我们还可以在 Coroutine 中做更牛逼的事情,例如:

这个方法向我们展示了 Coroutine 的一个非常骚比的特性,那就是这个方法执行体内部的所有状态都会被自动保存,包括方法体中定义的局部变量,都会在整个程序执行的过程中被自动保存起来,直到下一帧再次被调用。还记得文章开头的那些无聊的 timer 字段们吗?有了 Coroutine 之后,我们就不再需要那些货了,只需要把每个计时器需要的字段放到自己的计时方法内定义就 OK 了,是不是处女座程序猿童鞋的福音捏?

启动和结束 Coroutine

至此,我们已经学习了通过 StartCoroutine() 方法来启动一个 Coroutine:

如果我们想结束所有正在运行的 Coroutine 的话,我们可以通过 StopAllCoroutines() 这个方法来结束。但是需要注意的是,这个方法只会结束由调用 StopCoroutines() 方法的同一个 MonoBehaviour 组件启动的 Coroutine 们,对于通过其他 MonoBehaviour 组件启动的 Coroutine 就没有效果了。

如果我们通过下面的这种方式启动了两个 Coroutine:

然后我们又想单独结束两个 Timer 中的某一个,上面提到的方法就无法胜任了。如果我们想单独结束某一个指定的 Timer 的话,我们可以通过使用 Coroutine 的名称来作为 StartCoroutine() 和 StopCoroutine() 方法的参数,如下:

[译者自己加上的] 又或者这样,不使用字面常量:

扩展阅读

扩展阅读链接

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

欢迎打赏

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


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