Unity3D提供了一个工具叫做“协程”,所谓协程就是使用StartCoroutine()里面添加一个方法来调用该方法。对这个被调用的方法有如下规定:返回值必须是IEnumerator类型。那么为什么要使用协程呢?通常这是为了应付某一类需要,比如想要延时执行某一段代码,或者使用www进行一些请求和加载等阻塞操作。
协程与多线程没有关系。协程每一帧都执行,时间段位于LateUpdate之后。所以说它只不过是在主线程里面每帧执行的一个函数而已。协程使用的原理类似于foreach循环,都是使用迭代器来实现,因此它也包括IEnumerator接口的Current属性和MoveNext()方法。这两个东东都是迭代器比较重要的内容。
MSDN上对Current的解释为:Gets the current element in the collection,对MoveNext()的解释为:Advances the enumerator to the next element fo collection。从这两个解释可以看出来,Current返回的是集合当前的元素,MoveNext()推动循环向下一个元素。MoveNext()的返回值是bool,当返回true时,就向下走一级,返回false,则停止。假如循环到最后一个元素,肯定返回false。
对于协程来说,当进行至yield return语句时,检查yield return后面的条件是否满足,如果满足,则执行后面的代码。如果不满足,则记录下该位置,下一帧继续检查,直到满足为止。这里体现了协程的主要作用了,假如你用www下载一个数据,你肯定想知道什么时候下载完。只有下载完了yield return www的下一行才会得到执行。
下边看一个测试类,此类来自网络,通过一个协程内的循环来交替执行另外两个协程。注意,这些协程都是在主线程里面,可以随意访问Unity3D的对象和组件。
using System; using System.Collections.Generic; using System.Linq; using UnityEngine; using System.Collections; [RequireComponent(typeof(GUIText))] public class Hijack : MonoBehaviour { //This will hold the counting up coroutine IEnumerator _countUp; //This will hold the counting down coroutine IEnumerator _countDown; //This is the coroutine we are currently //hijacking IEnumerator _current; //A value that will be updated by the coroutine //that is currently running int value = 0; void Start() { //Create our count up coroutine _countUp = CountUp(); //Create our count down coroutine _countDown = CountDown(); //Start our own coroutine for the hijack StartCoroutine(DoHijack()); } void Update() { //Show the current value on the screen guiText.text = value.ToString(); } void OnGUI() { //Switch between the different functions if(GUILayout.Button("Switch functions")) { if(_current == _countUp) _current = _countDown; else _current = _countUp; } } IEnumerator DoHijack() { while(true) { //Check if we have a current coroutine and MoveNext on it if we do if(_current != null && _current.MoveNext()) { //Return whatever the coroutine yielded, so we will yield the //same thing yield return _current.Current; if(_current.Current!=null) Debug.Log(_current.Current.ToString());//wait for seconds OR null } else //Otherwise wait for the next frame yield return null; } } IEnumerator CountUp() { //We have a local increment so the routines //get independently faster depending on how //long they have been active float increment = 0; while(true) { //Exit if the Q button is pressed if(Input.GetKey(KeyCode.Q)) break; increment+=Time.deltaTime; value += Mathf.RoundToInt(increment); yield return null; } } IEnumerator CountDown() { float increment = 0f; while(true) { if(Input.GetKey(KeyCode.Q)) break; increment+=Time.deltaTime; value -= Mathf.RoundToInt(increment); //This coroutine returns a yield instruction yield return new WaitForSeconds(0.1f); } } }
现在简单解释下该类。首先定义了三个IEnumerator接口类型的变量用来接受协程的返回值,在Start方法里面调用其中一个DoHijack()协程。这个协程里面有一个无限循环,在该循环里面检查_current是否赋值了,如果有值就调用该IEnumerator接口里面的MoveNext()方法,相当于每次循环都执行一遍_current对应的方法。至于_current.Current则返回了两个协程CountUp和CountDown的返回值,输出一下得到值为Null或WaitForSeconds(),恰恰是yield return后跟的返回值。