協程的官方定義是一種具備暫停執行並將控制權返回給Unity,待下一幀時繼續執行。通俗點講就是,協程是一種能夠分部執行的函數,即該函數不是每次調用時都會執行函數體內的所有方法,而是隻調用其中部分代碼。寫到這裏不知道您有沒有發現,該定義有點像IEnumerator的延遲執行。舉一個例子:php
void Start () { IEnumerator numbers = YieldThreeNumbers (); for (int i = 0; i < 3; i++) { if(!numbers.MoveNext()) break; Debug.Log((int)numbers.Current); } } IEnumerator YieldThreeNumbers() { yield return 1; yield return 2; yield return 3; }
結果:html
能夠看到當咱們執行一次MoveNext方法,纔會取得當前當前的值,因此須要循環調用MoveNext才能將所有值取出。ios
協程也是一樣的方法:每一幀都會調用MoveNext方法,期間保存了下一次執行的位置,等到下一幀時會在該位置繼續執行。web
PS: 在C#中,yield和IEnumerator一塊兒使用時其實是實現了Itertor模式,詳情可參考這篇文章。http://csharpindepth.com/articles/chapter6/iteratorblockimplementation.aspx多線程
由此也能夠看到,協程其實與多線程一點關係都沒有。協程是在主線程中執行的,且每次只能執行一個協程。app
首先咱們須要定義一個返回IEnumerator的方法,如:ide
IEnumerator GetEnumerator() { Yield return null; }
而後在調用StarCoroutine方法,其簽名以下:函數
Coroutine StartCoroutine(string methodName,object value=null);性能
Coroutine StartCoroutine(IEnumerator routine);學習
在是使用第一個方法時,咱們直接將傳入上面定義的方法名:
StartCoroutine(「GetEnumerator」);
注意該方法的參數是IEnumerator,因此咱們能夠直接將調用上面定義的方法的返回值傳入:
StartCoroutine(GetEnumertor());
下面看一個來自官網的例子:
IEnumerator Fade() { for (float f = 1f; f >= 0; f -= 0.1f) { Color c = renderer.material.color; //減小a值,即透明度 c.a = f; renderer.material.color = c; yield return null; } } void Update() { if (Input.GetKeyDown("f")) { //沒按一次"f"鍵,gameObject的透明度都在減小,實現漸隱的效果 StartCoroutine("Fade"); }
固然,咱們不單單能夠yield null,還能夠yield其它的表達式:
1. 其它的數值,如0:
和null相似,不過不推薦。由於會存在裝箱,拆箱的問題,或多或少會影響性能。
2. WaitForEndOfFrame
等待至全部的Camera和GUI都呈現好了以後執行。
3. WaitForFixedUpdate
等待至全部物理都計算後執行
4. WaitForSeconds
在指定時間段內不執行
5. WWW
等待一個web請求完成
6. 另外一個協程
這是比較有意思的一點。由於StartCoroutine的返回值是Coroutine,因此咱們能夠yield另外一個協程。
void StopCoroutine(string methodName);
void StopCoroutine(IEnumerator routine);
其中StopCortouine(string methodName)只能中止由與之相對應的StarCoroutine(string methodName)啓動的協程。
還有其它的方法來中止協程,但都不是中止某個指定的協程,而是中止多個協程。
void StopAllCoroutines()
中止該behavior內的所有協程
void SetActive(bool value);
將behavior的active設爲false後,其內部的協程也都會中止。
協程能夠將一個方法,放到多個幀內執行,在很大程度上提升了性能。但協程也是有缺陷的:
下面咱們來解決前三個問題,爲協程添加返回值、異常處理和泛型。關於第四個問題的解決方式,請參考最下方的連接:
返回值:
public class ReturnValueCoroutine { private object result; public object Result { get {return result;} } public UnityEngine.Coroutine Coroutine; public IEnumerator InternalRoutine(IEnumerator coroutine) { while(true) { if(!coroutine.MoveNext()){ yield break; } object yielded = coroutine.Current; if(yielded != null){ result = yielded; yield break; } else{ yield return coroutine.Current; } } } } public class Demo : MonoBehaviour { IEnumerator Start () { ReturnValueCoroutine myCoroutine = new ReturnValueCoroutine (); myCoroutine.Coroutine = StartCoroutine (myCoroutine.InternalRoutine(TestNewRoutine())); yield return myCoroutine.Coroutine; Debug.Log (myCoroutine.Result); } IEnumerator TestNewRoutine() { yield return 10; } }
泛型:
public static class MonoBehaviorExt { public static Coroutine<T> StartCoroutine<T>(this MonoBehaviour obj, IEnumerator coroutine){ Coroutine<T> coroutineObject = new Coroutine<T>(); coroutineObject.coroutine = obj.StartCoroutine(coroutineObject.InternalRoutine(coroutine)); return coroutineObject; } } public class Coroutine<T>{ private T result; public T Result { get {return result;} } public Coroutine coroutine; public IEnumerator InternalRoutine(IEnumerator coroutine){ while(true){ if(!coroutine.MoveNext()){ yield break; } object yielded = coroutine.Current; if(yielded != null && yielded.GetType() == typeof(T)){ result = (T)yielded; yield break; } else{ yield return coroutine.Current; } } } } public class Demo : MonoBehaviour { Coroutine<int> routine; IEnumerator Start () { routine = this.StartCoroutine<int>(TestNewRoutine()); //Start our new routine yield return routine; // wait as we normally can } IEnumerator TestNewRoutine(){ yield return null; yield return new WaitForSeconds(2f); yield return 10; } void Update() { //由於延時,因此要等待一段時間才能顯示 Debug.Log(routine.Result); } }
異常處理:
public static class MonoBehaviorExt { public static Coroutine<T> StartCoroutine<T>(this MonoBehaviour obj, IEnumerator coroutine){ Coroutine<T> coroutineObject = new Coroutine<T>(); coroutineObject.coroutine = obj.StartCoroutine(coroutineObject.InternalRoutine(coroutine)); return coroutineObject; } } public class Coroutine<T>{ public T Result { get{ if(e != null){ throw e; } return result; } } private T result; private Exception e; public UnityEngine.Coroutine coroutine; public IEnumerator InternalRoutine(IEnumerator coroutine){ while(true){ try{ if(!coroutine.MoveNext()){ yield break; } } catch(Exception e){ this.e = e; yield break; } object yielded = coroutine.Current; if(yielded != null && yielded.GetType() == typeof(T)){ result = (T)yielded; yield break; } else{ yield return coroutine.Current; } } } } public class Demo : MonoBehaviour { IEnumerator Start () { var routine = this.StartCoroutine<int>(TestNewRoutineGivesException()); yield return routine.coroutine; try{ Debug.Log(routine.Result); } catch(Exception e){ Debug.Log(e.Message); // do something Debug.Break(); } } IEnumerator TestNewRoutineGivesException(){ yield return null; yield return new WaitForSeconds(2f); throw new Exception("Bad thing!"); } }
1. 使用lamada表達式接受返回值:http://answers.unity3d.com/questions/207733/can-coroutines-return-a-value.html
2. Wrapping Unity C# Coroutines for Exception Handling, Value Retrieval, and Locking:http://zingweb.com/blog/2013/02/05/unity-coroutine-wrapper/
3. CoroutineScheduler:http://wiki.unity3d.com/index.php?title=CoroutineScheduler
以上是本人的學習成果及平時收集的資料,若是您有其它更好的資源或是想法,請怒砸至評論區,多多益善!
參考資料:Coroutines – More than you want to know,http://twistedoakstudios.com/blog/Post83_coroutines-more-than-you-want-to-know