【2015年12月注释】
在Unity 5.0中增加了一个新的基类CustomYieldInstruction,可以参加官方文档:https://docs.unity3d.com/ScriptReference/CustomYieldInstruction.html
官方的一篇Blog:http://blogs.unity3d.com/2015/12/01/custom-coroutines/
【使用4.x以下版本的同学可以继续往下看】
Unity的Coroutine机制无疑是这个引擎的一个亮点,可以把很多异步的逻辑用一种顺序的书写方式去实现,这个还是很重要的。
关于Coroutine的原理,说实话,这个没有源代码,不好说具体是怎么实现的。看他用到了IEnumerator,想象是使用了.Net的枚举机制。网上有不是探讨Coroutine实现原理的帖子,有兴趣的同学可以翻开看看。
Coroutine之所以强大,缺不了一系列YieldInstruction的派生类,包括:WaitForSeconds , WaitForFixedUpdate,AsyncOperation等等,我一直在琢磨能不能实现自己的YieldInstruction派生类呢?今天花时间尝试了一些,答案是“不能,也能”,哈哈。为什么说“不能”呢,是因为YieldInstruction这个类可没什么虚函数可以去override的,为什么说“能”呢!我们可以借用一个特殊的YieldInstruction派生类,来实现等同的功能,这个类就是Coroutine。
完整的演示工程下载:http://pan.baidu.com/s/1jGGhRKE
这个工程是这样一个例子:
假设我们想实现这样一个YieldInstruction:当一个动画播放完事儿之后,程序再继续。
具体的实现也很简单,首先我们需要一个IEnumerator的派生类,并实现其3个接口,具体代码如下:
using UnityEngine; using System.Collections; public class WaitForEndOfAnim : IEnumerator { AnimationState m_animState; public WaitForEndOfAnim(AnimationState animState) { m_animState = animState; } //-- IEnumerator Interface public object Current { get { return null; } } //-- IEnumerator Interface public bool MoveNext() { return m_animState.enabled; } //-- IEnumerator Interface public void Reset() { } }
有了这个类时候,如果我们在协程函数体中写:yield return new WaitForEndOfAnim(animState),发现并没有其作用。后来改为:yield return StartCoroutine(new WaitForEndOfAnim(animAttack));就OK了。完整的测试代码如下:
using UnityEngine; using System.Collections; public class UnitTest : MonoBehaviour { // Use this for initialization void Start() { } void OnGUI() { GUILayout.BeginArea(new Rect(6, 6, 200, 300)); GUILayout.BeginVertical(); GUILayout.Box("Conrountinue测试"); if (GUILayout.Button("启动")) { StartCoroutine(DoTest()); } GUILayout.EndVertical(); GUILayout.EndArea(); } IEnumerator DoTest() { Animation anim = GetComponentInChildren<Animation>(); AnimationState animAttack = anim["attack"]; animAttack.speed = 0.1f; AnimationState animHit = anim["hit"]; animHit.speed = 0.1f; AnimationState animDie = anim["die"]; animDie.speed = 0.1f; Debug.Log("1.开始播放攻击动画。" + Time.time * 1000); anim.Play(animAttack.name); yield return StartCoroutine(new WaitForEndOfAnim(animAttack)); Debug.Log("2.开始播放受击动画。" + Time.time * 1000); anim.Play(animHit.name); yield return StartCoroutine(new WaitForEndOfAnim(animHit)); Debug.Log("3.开始播放死亡动画。" + Time.time * 1000); anim.Play(animDie.name); yield return StartCoroutine(new WaitForEndOfAnim(animDie)); } }最后,运行结果看下图,注意时间戳: