天天看點

IEnumerator/ IEnumerable/ yield return/ StartCoroutine 詳解

Update邏輯

IEnumerator/ IEnumerable/ yield return/ StartCoroutine 詳解

IEnumerator/ IEnumerable

public interface IEnumerable  
{  
    IEnumerator GetEnumerator();  
}  
   
public interface IEnumerator  
{  
    bool MoveNext();  
    void Reset();  
   
    Object Current { get; }  
}  
           

在兩者的使用上,有下面幾點需要注意

1、一個Collection要支援foreach方式的周遊,必須實作IEnumerable接口(亦即,必須以某種方式傳回IEnumerator object)。

2、IEnumerator object具體實作了iterator(通過MoveNext(),Reset(),Current)。

3、從這兩個接口的用詞選擇上,也可以看出其不同:IEnumerable是一個聲明式的接口,聲明實作該接口的class是“可枚舉(enumerable)”的,但并沒有說明如何實作枚舉器(iterator);IEnumerator是一個實作式的接口,IEnumerator object就是一個iterator。

4、IEnumerable和IEnumerator通過IEnumerable的GetEnumerator()方法建立了連接配接,client可以通過IEnumerable的GetEnumerator()得到IEnumerator object,在這個意義上,将GetEnumerator()看作IEnumerator object的factory method也未嘗不可。

yield return

使用yield語句可以暫停(pause)協同程式的執行,yield的傳回值指定在什麼時候繼續(resume)協同程式。

yield return null; //暫停協同程式,下一幀再繼續往下執行

yield new WaitForFixedUpdate (); //暫停協同程式,等到下一次調用FixedUpdate方法時再繼續往下執行

yield return new WaitForSeconds(2);//暫停協同程式,2秒之後再繼續往下執行

yield return StartCoroutine("SomeCortoutineMethod");//暫停此協同程式,開啟SomeCortoutineMethod協同程式,直到SomeCortoutineMethod裡的内容全部搞定。

StartCoroutine

在Unity3D中,使用MonoBehaviour.StartCoroutine方法即可開啟一個協同程式,也就是說該方法必須在MonoBehaviour或繼承于MonoBehaviour的類中調用。

在Unity3D中,使用StartCoroutine(string methodName)和StartCoroutine(IEnumerator routine)都可以開啟一個線程。差別在于使用字元串作為參數可以開啟線程并線上程結束前終止線程,相反使用IEnumerator 作為參數隻能等待線程的結束而不能随時終止(除非使用StopAllCoroutines()方法);另外使用字元串作為參數時,開啟線程時最多隻能傳遞 一個參數,并且性能消耗會更大一點,而使用IEnumerator 作為參數則沒有這個限制。

在Unity3D中,使用StopCoroutine(string methodName)來終止一個協同程式,使用StopAllCoroutines()來終止所有可以終止的協同程式,但這兩個方法都隻能終止該 MonoBehaviour中的協同程式。

還有一種方法可以終止協同程式,即将協同程式所在gameobject的active屬性設定為false,當再次設定active為ture時,協同程 序并不會再開啟;如是将協同程式所在腳本的enabled設定為false則不會生效。這是因為協同程式被開啟後作為一個線程在運作,而 MonoBehaviour也是一個線程,他們成為互不幹擾的子產品,除非代碼中用調用,他們共同作用于同一個對象,隻有當對象不可見才能同時終止這兩個線 程。然而,為了管理我們額外開啟的線程,Unity3D将協同程式的調用放在了MonoBehaviour中,這樣我們在程式設計時就可以友善的調用指定腳本 中的協同程式,而不是無法去管理,特别是對于隻根據方法名來判斷線程的方式在多人開發中很容易出錯,這樣的設計保證了對象、腳本的條理化管理,并防止了重 名。

示例1

public class GameManager : MonoBehaviour {

     void Start() 
    { 
       Debug.Log("Starting " + Time.time);
        StartCoroutine(WaitAndPrint(2));
        Debug.Log("Done " + Time.time);
    }

	IEnumerator WaitAndPrint(float waitTime) 
    { 
        yield return new WaitForSeconds(waitTime); 
        Debug.Log("WaitAndPrint " + Time.time);
    }
}
           

運作結果:

IEnumerator/ IEnumerable/ yield return/ StartCoroutine 詳解
public class GameManager : MonoBehaviour {

	IEnumerator Start() 
    {
		Debug.Log("Starting " + Time.time);
        yield return StartCoroutine(WaitAndPrint(2.0F));
		Debug.Log("Done " + Time.time);
    }
	IEnumerator WaitAndPrint(float waitTime) 
    { 
        yield return new WaitForSeconds(waitTime);
		Debug.Log("WaitAndPrint " + Time.time);
    } 
}
           

運作結果:

IEnumerator/ IEnumerable/ yield return/ StartCoroutine 詳解

再看一個yield return StartCoroutine嵌套的例子

void Start () {
        Debug.Log("start1");
        StartCoroutine(Test());
        Debug.Log("start2");
    }

    IEnumerator Test()
    {
        Debug.Log("test1");
        yield return StartCoroutine(DoSomething());
        Debug.Log("test2");
    }

    IEnumerator DoSomething()
    {
        Debug.Log("load 1");
        yield return null;
        Debug.Log("load 2");
    }
           

執行結果:

start1

test1

load1

start2

load2

test2

這種StartCoroutine中嵌套一個yield return StartCoroutine,第一個StartCoroutine會等到第二個StartCoroutine中所有代碼結束後再繼續執行,而第二個StartCoroutine中的yield語句會先傳回第一個,然後立即傳回他的調用處,也就是調用處會繼續執行,而第一個StartCoroutine會等待第二個執行完再繼續執行。

示例二

場景如下,一個球一個平面。

IEnumerator/ IEnumerable/ yield return/ StartCoroutine 詳解

球的控制器

using UnityEngine;
using System.Collections;

public class SphereController : MonoBehaviour {
	private Vector3 target;
	public float smoothing = 7f;

	public Vector3 Target
	{
		get { return target; }
		set
		{
			target = value;

			StopCoroutine("Movement");
			StartCoroutine("Movement", target);
			Debug.Log("Move!");
		}
	}

	IEnumerator Movement(Vector3 target)
	{
		while(Vector3.Distance(transform.position, target) > 0.05f)
		{
			transform.position = Vector3.Lerp(transform.position, target, smoothing * Time.deltaTime);
			yield return null;
		}
	}

}
           

下面的腳本拖拽到地面上

using UnityEngine;
using System.Collections;

public class ClickSetPosition : MonoBehaviour {
	public SphereController sphereController;
	// Update is called once per frame
	void Update()
	{
		if (Input.GetMouseButtonDown(0))
		{
			Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
			RaycastHit hit;

			Physics.Raycast(ray, out hit);

			if (hit.collider.gameObject == gameObject)
			{
				Debug.Log("Clicked!");
				Vector3 newTarget = hit.point + new Vector3(0, 0.5f, 0);
				sphereController.Target = newTarget;
			}
		}
	}
}
           

運作結果

IEnumerator/ IEnumerable/ yield return/ StartCoroutine 詳解

參考

Unity協程(Coroutine)原理深入剖析再續 - http://dsqiu.iteye.com/blog/2049743

協同的了解 - http://www.cnblogs.com/shawnzxx/archive/2013/01/01/2841451.html

yield(C# 參考) - https://msdn.microsoft.com/zh-cn/library/9k7k7cf0.aspx

COROUTINES Unity Tutorial - http://unity3d.com/learn/tutorials/modules/intermediate/scripting/coroutines

Unity StartCoroutine 和 yield return 深入研究 -  http://www.cnblogs.com/fly-100/p/3910515.html

Coroutines document - https://docs.unity3d.com/Manual/Coroutines.html