IEnumerator、yield及unity3d协程的手动驱动与合并

本文原创,转载请注明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

猜你喜欢

转载自blog.csdn.net/yangxun983323204/article/details/70170233