Unity中协程的使用+自定义

Unity中的协程是模拟操作系统线程的(名称很相近),使用的感觉也相近。不同之处是:线程是操作系统管理的,而协程是Unity管理并运行在主线程中(每帧运行一次,每秒运行30或60次);

协程的优势

协程的使用方便且强大,在游戏编程中很常见。游戏编程的很多逻辑需要跨帧才能完成(尤其涉及到动画表现相关的逻辑),在不用协程的情况下,可能需要在Update的逻辑中进行(其中逻辑状态需要存放为类变量);而改用协程,代码就会变得很简单,且状态只需要维护为方法变量;举例:

1)Update的方法:

public class MyAnim1 : MonoBehaviour {
    public Vector3 startPos, endPos;
    public float speed;

    // 状态
    private float progress;

    void Start () {
        progress = 0;
    }
	
    void Update () {
        progress += Time.deltaTime * speed;
        transform.position = Vector3.Lerp(startPos, endPos, progress);
    }
}

2)用协程的方法:

public class MyAnim2 : MonoBehaviour {
    public Vector3 startPos, endPos;
    public float speed;

    void Start() {
        StartCoroutine(doAnim()); // 启动协程
    }

    private IEnumerator doAnim() { 
        var progress = 0f; // 状态
        while (true) {
            yield return null; // 等待一帧
            progress += Time.deltaTime * speed;
            transform.position = Vector3.Lerp(startPos, endPos, progress);
        }
    }
}

这个例子虽然简单,但是Update的方法的progress的维护要分散在代码的3处,而协程就在一个方法内,这就体现了协程的优势;如果动画有多个的话,使用协程的方便性和可维护性就会更加明显。

Unity的协程管理

 Unity管理协程的方式基于两点:1)游戏引擎每秒更新30帧(或则60帧等);2)C#语言对协程的支持;

自己管理协程

在游戏开发中,有些时候需要自己管理协程。运用场景举例:
1)一些帧同步的网游,驱动逻辑更新的源头是网络上过来的“逻辑帧”(非游戏刷新帧),网络上每到来一“逻辑帧”,逻辑就需要更新一次(帧同步频率一般会低于游戏刷新帧---比如帧同步频率是15帧/秒。但由于网络卡顿延迟,会把延后“逻辑帧”积累起来一起发送,这样游戏的一个刷新帧内会到来多个“逻辑帧”)。

2)自己想用协程的方式来管理一些游戏功能,比如状态机的简化处理等(以后有时间会写一些相关文章); 

在项目中,有的时候需要手动控制协程的更新,也就是重写StartCoroutine方法。具体如下:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Reflection;

public class CoroutineManager {
    public Clock clock { get; private set; }
    private HashSet cIters ;
    private List itAddTemp;
    private List itDelTemp;

    private CoroutineManager() {
        cIters = new HashSet();
        itAddTemp = new List();
        itDelTemp = new List();
        clock = new Clock();
    }

    private static CoroutineManager instance = null;
    public static CoroutineManager Instance {
        get {
            if (instance == null) {
                instance = new CoroutineManager();
            }
            return instance;
        }
    }

    public static float time {
        get {
            return Instance.clock.time;
        }
    }
    public CoroutineIter StartCoroutine(IEnumerator _it) {
        var iter = new CoroutineIter(_it);
        itAddTemp.Add(iter);
        return iter;
    }

    public void StopCoroutine(CoroutineIter cIter) {
        if (cIter != null) {
           itDelTemp.Add(cIter);
        }
    }

    public bool doUpdate(float dt) {
        if (itAddTemp.Count > 0) {  // 添加 
            itAddTemp.ForEach(it => { cIters.Add(it); });
            itAddTemp.Clear();
        }
        if (clock.tick(dt) && cIters.Count > 0) {
            foreach (var i in cIters) {
                i.doUpdate(dt);
            }
            cIters.RemoveWhere(i => i.isEnd);
        }
        if (itDelTemp.Count > 0) { // 删除
            itDelTemp.ForEach(it => { cIters.Remove(it); });
            itDelTemp.Clear();
        }
        return cIters.Count > 0;
    }

    public class Clock {
        public int frame { get; private set; }
        public float time { get; private set; }
        public float dt { get; private set; }
        public void reset() {
            time = dt = 0;
        }
        public bool tick(float _dt) {
            if (frame < Time.frameCount) {
                dt = _dt;
                time += _dt;
                frame = Time.frameCount;
                return true;
            }
            return false;
        }
    }
}

public class CoroutineIter {
    public bool isEnd { get; private set; }
    Stack stack = new Stack();
    IEnumerator it;
    public CoroutineIter(IEnumerator _it) {
        it = _it;
        isEnd = it == null;
    }

    public void doUpdate(float dt) {
        if (!isEnd) {
            if (it.MoveNext()) {
                dealCurrent(it.Current);
            } else {
                it = stack.Count > 0 ? stack.Pop() : null;
            }
            isEnd = it == null;
        }
    }

    private void dealCurrent(object cur) {
        if (it.Current is IEnumerator) {
            stack.Push(it);
            it = it.Current as IEnumerator;
        } else if (it.Current is WaitForSeconds) {
            stack.Push(it);
            it = new MyWaitForSecond(it.Current as WaitForSeconds);
        }
    }
}

class MyWaitForSecond : CustomYieldInstruction {
    private float duration;
    private float startTime;
    CoroutineManager cm;
    public MyWaitForSecond(WaitForSeconds wfs) {
        duration = GetPrivateFieldValue(wfs, "m_Seconds");
        cm = CoroutineManager.Instance;
        startTime = cm.clock.time;
    }

    public override bool keepWaiting {
        get {
            return (cm.clock.time - startTime) < duration;
        }
    }

    private static T GetPrivateFieldValue(object obj, string propName) {
        if (obj == null)
            throw new ArgumentNullException("obj");
        Type t = obj.GetType();
        FieldInfo fi = null;
        while (fi == null && t != null) {
            fi = t.GetField(propName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
            t = t.BaseType;
        }
        if (fi == null)
            throw new ArgumentOutOfRangeException("propName",
                                                  string.Format("Field {0} was not found in Type {1}", propName,
                                                                obj.GetType().FullName));
        return (T)fi.GetValue(obj);
    }
}

调用方法:

using System;
using System.Collections;
using UnityEngine;

public class Testtest : MonoBehaviour {
    CoroutineIter iter;
    void Start() {
        CoroutineManager.Instance.StartCoroutine(testx0());
        //StartCoroutine(test0());
    }

    void Update() {
        if (!CoroutineManager.Instance.doUpdate(Time.deltaTime)) {
            Debug.LogError("=======EEEEEEEE=======");
        }
    }

    private IEnumerator testx0() {
        Debug.Log("======={{{{ =======");
        yield return null;
        var handler = CoroutineManager.Instance.StartCoroutine(testx1());
        yield return new WaitForSeconds(3f);
        CoroutineManager.Instance.StopCoroutine(handler);
        Debug.Log("======= }}}} =======");
        yield return new WaitForSeconds(1f);
        Debug.Log("======= End =======");
    }

    private IEnumerator testx1() {
        int i = 0;
        while (true) {
            yield return new WaitForSeconds(0.3f);
            Debug.Log(">>> " + (i++));
        }
    }

    private IEnumerator test0() {
        yield return test1();
        Debug.Log("000000000000");
        yield return test2();
        Debug.Log("000000000000");
    }

    private IEnumerator test1() {
        Debug.Log("111111  : " + Time.time);
        yield return new WaitForSeconds(1f);
        Debug.Log("111111  : " + Time.time);
    }

    private IEnumerator test2() {
        Debug.Log("222222  : " + Time.time);
        yield return new WaitForSeconds(2f);
        Debug.Log("22222  : " + Time.time);
    }

    private IEnumerator testA() {
        for (int i = 0; i < 3; i++) {
			yield return new WaitForSeconds(i);
            Debug.Log("-1-   " + CoroutineManager.time);
        }
        Debug.Log("-2-");
        yield return null;
        Debug.Log("-3-");
        yield return new CC();
		Debug.Log("-4-  " + CoroutineManager.time);
        yield return new WaitUntil(delegate {
			return CoroutineManager.time > 5;
        });
		Debug.Log("-5-  "+ CoroutineManager.time);
        yield return new WaitWhile(delegate {
			return CoroutineManager.time < 8;
        });
		Debug.Log("-6-  "+ CoroutineManager.time);

    }

    class CC : CustomYieldInstruction {
        int i = 0;
        public override bool keepWaiting {
            get {
                Debug.Log("CC > " + i);
                return i++ < 10;
            }
        }
    }
}


另外,协程遍历的方法,其实是一个树的深度遍历:

using UnityEngine;  
using UnityEditor;  
using NUnit.Framework;  
using System;  
using System.Collections;  
using System.Collections.Generic;  
  
public class NewEditorTest {  
    [Test]  
    public void EditorTest() {  
        //Iterate (test01 (0));  
        Iterate(testA(), obj=>{  
            if(obj is YieldInstruction){  
                Debug.LogFormat("!!YI: " + obj.GetType().Name);  
            } else if(obj is CustomYieldInstruction) {  
                Debug.LogFormat("!!CYI: {0}, isIt? {1} ", obj.GetType().Name, obj is IEnumerator);  
            }  
        });  
    }  
          
    private void Iterate(IEnumerator _it, System.Action action = null) {  
        Stack stack = new Stack ();  
        var it = _it;  
        while (it != null) {  
            while (it.MoveNext ()) {  
                if (action != null)  
                    action (it.Current);  
                if (it.Current is IEnumerator) {  
                    stack.Push (it);  
                    it = it.Current as IEnumerator;  
                    continue;  
                }  
            }  
            it = stack.Count > 0 ? stack.Pop() : null;  
        }  
    }  
    float time = 0;  
    private IEnumerator testA() {  
        for (int i = 0; i < 3; i++) {  
            Debug.Log ("-1-");  
            yield return new WaitForSeconds (i);  
        }  
        Debug.Log ("-2-");  
        yield return null;  
        Debug.Log ("-3-");  
        yield return new CC ();  
        Debug.Log ("-4-");  
        yield return new WaitUntil (delegate {  
            time += 0.3f;  
            Debug.Log(">Time: " + time);  
            return time > 10;  
        });  
        Debug.Log ("-5-");  
        yield return new WaitWhile (delegate {  
            time += 0.3f;  
            Debug.Log(">Time: " + time);  
            return time < 15;  
        });  
    }  
  
    class CC : CustomYieldInstruction {  
        int i = 0;  
        public override bool keepWaiting {  
            get {  
                Debug.Log ("CC > " + i);  
                return i++ < 10;  
            }  
        }  
    }  
  
    private IEnumerator test01(int l) {  
        Debug.Log ("----test01-{{{");  
        print (1, l);  
        yield return test02 (l+1);  
        print (2, l);  
        yield return null;  
        print (3, l);  
        yield return test03(l+1);  
        print (4, l);  
        Debug.Log ("}}}test01-----");  
    }  
          
    private IEnumerator test02(int l) {  
        Debug.Log ("----test02-{{{");  
        print (1, l);  
        yield return test03 (l+1);  
        print (2, l);  
        yield return null;  
        print (3, l);  
        yield return test03(l+1);  
        print (4, l);  
        Debug.Log ("}}}test02-----");  
  
    }  
  
    private IEnumerator test03(int l) {  
        Debug.Log ("----test03-{{{");  
        print (1, l);  
        yield return null;  
        Debug.Log ("}}}test03-----");  
    }  
  
    private void print(int num, int level) {  
        string rt = "";  
        for (int i = 0; i < level; i++) {  
            rt += "   ";  
        }  
        Debug.LogFormat ("{0}{1}", rt, num);  
    }  
}   
  


你可能感兴趣的:(Unity中协程的使用+自定义)