本文原创,转载请注明http://blog.csdn.net/yangxun983323204/article/details/70170233
yangxun
通过这篇文章,让u3d协程的内部实现不再神秘。
本文目标,不使用StartCoroutine来执行协程,让多协程的合并不使用yield关键字,以避免先有鸡还是先有蛋的尴尬。
先上一张效果图~
再发一下测试代码的调用方式:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class TestCoroutineEX : MonoBehaviour { // Use this for initialization void Start () { var basetool = CoroutineWrapper.Inst; CoroutineEX .Run(CoroutineA("第1")) .Append(CoroutineB(666)) .Append(new TestIEnumerator(() => { Debug.Log("通过对IEnumerator接口的包装,还可以插入委托哦~"); })) .Append(CoroutineCombine(666)); } //第一个协程 IEnumerator CoroutineA(string a) { yield return new WaitForSeconds(1); Debug.LogFormat("{0}, time:{1}","我是第一:"+a,Time.time); yield return new WaitForSeconds(1); Debug.LogFormat("{0}, time:{1}", "我是第一:" + a, Time.time); } //第二个协程 IEnumerator CoroutineB(int b) { yield return new WaitForSeconds(1); Debug.LogFormat("{0}, time:{1}", "我是第二:" + b, Time.time); yield return new WaitForSeconds(1); Debug.LogFormat("{0}, time:{1}", "我是第二:" + b, Time.time); yield return new WaitForSeconds(1); Debug.LogFormat("{0}, time:{1}", "我是第二:" + b, Time.time); } //被内嵌的协程 IEnumerator CoroutineInline() { yield return new WaitForSeconds(1); Debug.LogFormat("{0}, time:{1}", "我是被嵌套协程:", Time.time); yield return new WaitForSeconds(1); Debug.LogFormat("{0}, time:{1}", "我是被嵌套协程:", Time.time); } //内嵌了协程的协程 IEnumerator CoroutineCombine(double d) { yield return new WaitForSeconds(1); Debug.LogFormat("{0}, time:{1}", "我是第三:" + d, Time.time); //内嵌其它协程 yield return new CoroutineEX(CoroutineInline()); //var iter = CoroutineInline(); //while(iter.MoveNext()) // yield return iter.Current; //// yield return new WaitForSeconds(1); Debug.LogFormat("{0}, time:{1}", "我是第三:" + d, Time.time); } /// <summary> /// 委托插入协程的包装 /// </summary> class TestIEnumerator : IEnumerator { System.Action callback; public TestIEnumerator(System.Action callback) { this.callback = callback; } bool runed = false; public object Current { get { if (!runed) return null; callback(); return "这就厉害了"; } } public bool MoveNext() { if (!runed) { runed = true; return true; } return false; } public void Reset() { } } }
大家都知道,在u3d中要启动一个协程,只能使用他的方法StartCoroutine,我这个就相当于基本实现了Unity的StartCoroutine,让这个运行时内部实现的方法完全展示在大家面前。
其中使用的类CoroutineWrapper在之前的文章中也说过了,只是挂接了untiy3d的update方法,毕竟要实现unity3d效果的协程,也就是在单线程中既有等待,又不阻塞线程,必然要通过update来完成。
先说IEnumerator,这个是C# .NET框架里的一个接口,所谓的迭代器接口,目地就是定义一个可迭代对象的规范,比如,你写的一个类里面有一个私有的数组成员array,然后你想你的类可以用foreach去遍历里面的数组,那么,你就可以继承并实现这个接口,先定义一个int index = -1来作数组下标,Current属性返回array[index],当然,在越界时返回null,MoveNext方法把index加1并返回true,当然,在越界时返回false;Reset方法把index重置为-1。这样就是这个接口的简单应用。
再来说yield,这个对众多unity3d开发者来说很熟悉了,协程函数里,至少得有一个yield,这个关键字可以这样去理解:它使函数变得像实现了IEnumerator的一个类,有自己的内部状态,就例如上面例子中的index,在第一次调用时,执行代码直到返回第一个yield的对象,在第二次调用时,继续执行剩下的代码直到返回第二个yield的对象。。。直到没有yield为止。
明白了上面的道理,就有了代码驱动unity3d协程的思路了:
在unity的update这个帧事件里去每帧调用一个协程直到协程不能再返回一个yield对象。
这个只是利用到了C#的yield特性,在实践中你会发现,所有的yield语句只等待了一帧,这是因为我们没有对yield返回的对象做一定的处理,比如WaitForSeconds,WaitForEndOfFrame,WWW,这三个实现了YieldInstruction的类型(本文也只支持了这三种,但是很好扩展)。
进一步的,我们需要对yield得到的对象做支持,比如:WaitForSeconds,我们就需要计时,update一直跳过调用,直到达到目标时间;WaitForEndOfFrame需要过一帧再调用,WWW需要在下载结束后再调用。
实现了对指定类型的特殊处理,就可以在协程中yield返回这些类型并等待类型所期望的时间。
多个协程的合并也是如此,只需要一个队列去存储所有的协程,并适时的移除已经执行过的。其它逻辑和上面说的一样。
所以,核心代码是CoroutineEX类和CoroutineEX.YieldWrapper。
CoroutineEX是实现了IEnumerator接口的,所以它本身也是可迭代的(内部是对协程队列的迭代),因此可以很容易的实现在协程内部再执行其它协程,并且内部调用也是不使用StartCoroutine的,我们目标是完全不用这个方法。
CoroutineEX.YieldWrapper是对各种yield对象的支持,只要扩展这个类,就可以支持各种的自定义等待类型,或我文章里还没有写的u3d类型。
下面是代码
using System.Collections; using System.Collections.Generic; using System.Reflection; using UnityEngine; /// <summary> /// 实现一个不依赖MonoBehaviour的协程方式,并且可以通过Append方法依次执行 /// </summary> public class CoroutineEX : IEnumerator { //一个用来处理多个协程的队列 List<IEnumerator> coroutineList = new List<IEnumerator>(); bool stop = true; //处理unity的各种中断 YieldWrapper unityYield = null; /// <summary> /// 静态方法,用来代替unity的MonoBehaviour.StartCoroutine方法 /// </summary> public static CoroutineEX Run(IEnumerator inst) { return new CoroutineEX(inst).Run(); } /// <summary> /// 实现一个不依赖MonoBehaviour的协程方式,并且可以通过Append方法依次执行 /// </summary> public CoroutineEX(IEnumerator inst) { coroutineList.Add(inst); } /// <summary> /// 运行协程 /// </summary> public CoroutineEX Run() { stop = false; CoroutineWrapper.Inst.OnPerFrame += updateIEnumerator; return this; } /// <summary> /// 停止协程 /// </summary> public void Stop() { CoroutineWrapper.Inst.OnPerFrame -= updateIEnumerator; stop = true; } /// <summary> /// 通过此方法,在后面附加其它协程 /// </summary> public CoroutineEX Append(IEnumerator later) { coroutineList.Add(later); return this; } void updateIEnumerator() { if (stop) { return; } if (unityYield!=null && !unityYield.IsCompleted) { return; } bool s = MoveNext(); if (!s) {//已经执行完成,移除驱动函数 CoroutineWrapper.Inst.OnPerFrame -= updateIEnumerator; } } #region 协程接口的实现与重写,通过这些方法,实现多个协程的衔接 public object Current { get { if (coroutineList.Count == 0) { return null; } else if (coroutineList.Count == 1) { return coroutineList[0].Current; } else { object curr = null; while (coroutineList.Count > 1) { var head = coroutineList[0]; curr = head.Current; if (curr == null) { coroutineList.Remove(head); head = coroutineList[0]; head.MoveNext(); curr = head.Current; if (curr!=null) { break; } } else { break; } } return curr; } } } public bool MoveNext() { unityYield = null; if (coroutineList.Count == 0) { return false; } else if (coroutineList.Count == 1) { var s = coroutineList[0].MoveNext(); if (s) unityYield = new YieldWrapper(Current); return s; } else { bool hasNext = false; while (coroutineList.Count > 1) { var head = coroutineList[0]; hasNext = head.MoveNext(); if (!hasNext) { coroutineList.Remove(head); head = coroutineList[0]; hasNext = head.MoveNext(); if (hasNext) { unityYield = new YieldWrapper(Current); break; } } else { unityYield = new YieldWrapper(Current); break; } } return hasNext; } } public void Reset() { coroutineList[0].Reset(); } #endregion #region 对Unity定义的各种中断或异步操作进行处理 //通过对unity实现的各种中断的封装,达到可以等待预期时间的效果 protected class YieldWrapper { Type type = Type.WaitForEndOfFrame; object unityYield = null; float startTime; //WaitForSeconds的非公开字段 float waitSeconds = 0; public bool IsCompleted { get { switch (type) { case Type.WaitForSeconds: float currTime = Time.time - startTime; if (currTime >= waitSeconds) { return true; } break; case Type.WaitForEndOfFrame: if (Time.time!=startTime) { return true; } break; case Type.WWW: return ((WWW)unityYield).isDone; case Type.NULL: return true; case Type.CoroutineEX: CoroutineEX ex = (CoroutineEX)unityYield; if (ex.unityYield == null || ex.unityYield.IsCompleted) { var s = ex.MoveNext(); if (!s) { return true; } } if (ex.unityYield == null) { return true; } return ex.unityYield.IsCompleted; default: return true; } return false; } } public YieldWrapper(object unityYield) { this.unityYield = unityYield; string yieldType = unityYield.GetType().ToString(); switch (yieldType) { case "UnityEngine.WaitForSeconds": type = Type.WaitForSeconds; //反射字段 float m_Seconds FieldInfo[] fis = unityYield.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public); foreach (var fieldInfo in fis) { if (fieldInfo.Name == "m_Seconds") { waitSeconds = (float)fieldInfo.GetValue(unityYield); } } break; case "UnityEngine.WaitForEndOfFrame": type = Type.WaitForEndOfFrame; break; case "UnityEngine.WWW": type = Type.WWW; break; case "CoroutineEX": type = Type.CoroutineEX; break; default: type = Type.NULL; break; } startTime = Time.time; } enum Type { WaitForSeconds, WaitForEndOfFrame, WWW, CoroutineEX, NULL } } #endregion }
这个主要是讲原理,当然也是可以实用的。
最后附上工具类,这个我都发了好几次了,真不好意思。。,本文中只是用了它的u3d update回调,没有其它关系。
using UnityEngine; using System.Collections; using System.Collections.Generic; using System; using System.Reflection; /// <summary> /// Э?????д??? /// </summary> public class CoroutineWrapper : MonoBehaviour { #region ???? static CoroutineWrapper inst; private readonly static object mutex = new object(); public static CoroutineWrapper Inst { get { if (inst == null) { var obj = new GameObject("CoroutineWrapper"); DontDestroyOnLoad(obj); inst = obj.AddComponent<CoroutineWrapper>(); inst.Init(); } return inst; } } void Init() { inst = this; } #endregion /// <summary> /// ???????????? /// </summary> /// <param name="frames"></param> /// <param name="ev"></param> /// <returns></returns> public IEnumerator ExeDelay(int frames,Action ev) { for (int i = 0; i < frames;i++ ) { yield return new WaitForEndOfFrame(); } ev(); } /// <summary> /// ???????????? /// </summary> /// <param name="sec"></param> /// <param name="ev"></param> /// <returns></returns> public IEnumerator ExeDelayS(float sec, Action ev) { yield return new WaitForSeconds(sec); ev();; } /// <summary> /// ???????????? /// </summary> /// <param name="sec"></param> /// <param name="ev"></param> public static void EXES(float sec, Action ev) { inst.StartCoroutine(inst.ExeDelayS(sec,ev)); } /// <summary> /// ???????????? /// </summary> /// <param name="frames"></param> /// <param name="ev"></param> public static void EXEF(int frames, Action ev) { inst.StartCoroutine(inst.ExeDelay(frames, ev)); } /// <summary> /// ?????????Э?? /// </summary> /// <param name="ien">Э?????</param> public static void EXEE(Func<IEnumerator> ien) { inst.StartCoroutine(ien()); } /// <summary> /// /// </summary> /// <param name="ien">Э?????</param> /// <param name="p">??????</param> public static void EXEE(Func<object[],IEnumerator> ien,params object[] p) { inst.StartCoroutine(ien(p)); } /// <summary> /// call fun with any type args,function must be public /// </summary> /// <param name="t"></param> /// <param name="FuncName"></param> /// <param name="inst"></param> /// <param name="p"></param> public static void ReflectCallCoroutine(Type t, string FuncName,object inst, params object[] p) { Type[] pt = null; if (p!=null) { pt = new Type[p.Length]; for (int i = 0; i < pt.Length; i++) { pt[i] = p[i].GetType(); } } MethodInfo method; if (pt!=null) { method = t.GetMethod(FuncName,pt); } else { method = t.GetMethod(FuncName); } if (method == null) { Debug.LogFormat("can`t find method {0} with give params",FuncName); return; } var u3dCoroutineCaller = inst.GetType().GetMethod("StartCoroutine", new Type[] { typeof(IEnumerator) }); if (u3dCoroutineCaller == null) { Debug.Log("can`t find u3d coroutine caller"); return; } u3dCoroutineCaller.Invoke(inst, new object[]{method.Invoke(inst, p)}); } /// <summary> /// this action will be called per frame /// </summary> public event Action OnPerFrame; void Update() { if (OnPerFrame != null) OnPerFrame(); } }
======================================================================
2017/4/14 14:41在协程中调用协程有些许问题,已经改正,涉及类CoroutineEX和TestCoroutineEX