Unity全面入門筆記16-異步

異步

Invoke

  • Invoke方法
public void MonoBehaviour.Invoke(string methodName, float time);
public void MonoBehaviour.InvokeRepeating(string methodName, float time, float repeatRate);
public void MonoBehaviour.CancelInvoke();
public void MonoBehaviour.CancelInvoke(string methodName);
public bool MonoBehaviour.IsInvoking(string methodName);

Invoke是一組將函數交給Unity架構運作的方法,使用Invoke調用的函數是脫離組件生命週期的。

Invoke委派了在物理時間經過time秒後調用methodName方法。

InvokeRepeating委派了物理時間經過time秒後調用methodName方法,隨後每repeatRate秒再調用一次。

CancelInvoke可以停止由本腳本委派的所有Invoke函數和InvokeRepeating函數,如果加上參數,則取消對應類型的函數。

IsInvoking可以返回本腳本是否已經委派了至少一個Invoke函數。

以下是使用Invoke實現燈光閃爍的效果的例子:

public class LightBlink : MonoBehaviour
{
	Light light;
	private void Start()
	{
		light = GetComponent<Light>();
		TurnOnLight();
	}
	private void TurnOnLight()
	{
		light.enabled = true;
		Invoke("TurnOffLight",2);
	}
	private void TurnOffLight()
	{
		light.enabled = false;
		Invoke("TurnOnLight",2);
	}
}

以下是使用Invoke實現使物體逐漸縮小的效果的例子:

public class ShrinkEffect : MonoBehaviour
{
	private void Start()
	{
		InvokeRepeating("Shrink", 0, 0.1f);
		Invoke("StopShrink",5);
	}
	private void Shrink()
	{
		transform.localScale *= 0.95f;
	}
	private void StopShrink()
	{
		CancelInvoke("Shrink");
	}
}

如果Invoke時不能找到需要找到的函數,不會拋出運行時異常,但會在Console面板輸出一條信息。

Invoke的最大缺點在於被委派的函數只能是無參數函數。但由於Invoke的調用是脫離生命週期的,所以當組件enable爲false或物體activeSelf爲false時,Invoke可以繼續執行,相反的,協程是不可以的。如果需要在SetActive(true)前進行一些與本體無關的額外操作而需要推遲SetActive(true)時(如登場時的光效動畫),可以使用Invoke代替Coroutine。

Coroutine

  • 協程和線程

協程和多線程不同,協程由Unity框架提供,在組件生命週期中被執行,協程的處理在物理循環或邏輯循環之後,具體在何時處理由協程的構建方式決定;而線程則由操作系統調度。所有涉及組件生命週期的功能都應該使用協程而不是線程實現。

協程和線程都可以達成表面上的並行效果,但協程的切片是開發者手動進行的,可以預期切片的次數和位置,以及每個切片在每一幀的運行時間,可以保證在功能A所有語句運行完後再執行功能B的語句;多線程中切片由操作系統控制,線程是交錯運行的,難以保證語句的順序。

協程始終屬於同一個遊戲線程,所以使用大量協程時並不能充分利用硬件多核特性,而多線程可以。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-dauRy0gn-1569897360358)(C:\Users\UPiracy\AppData\Roaming\Typora\typora-user-images\1569516425528.png)]

使用線程而不是協程的情況:

(1) 在不用任何插件的情況下實現網絡通訊或http請求。

(2) 加載外部文件,調用其他軟件(比如內付調用支付軟件)。

(3) 十分耗時的,在單線程中難以保證流暢度的運算(大規模查找和排序等)。

  • 迭代器IEnumerator

我們通過迭代器來實現程序的切片,使用不同yield語句可以將切片分配給不同的處理方式。程序在運行到yield語句時會"掛起",等待調節合適時再繼續運行。

使用StartCoroutine來開啓一個協程:

public Coroutine MonoBehaviour.StartCorountine(IEnumerator routine);
public Coroutine MonoBehaviour.StartCorountine(string methodName; object value = null);

協程開啓後會由本組件的生命週期執行,開啓協程之後的語句不會受協程暫停。

在組件A中使用StartCoroutine調用組件B中的迭代器開啓協程,這個協程將會由組件A的生命週期執行。

構造一個迭代器,我們需要聲明一個返回值爲IEnumerator的方法,然後通過yield return語句來標記需要掛起的位置和喚醒的模式,下圖中的組件展示了協程的邏輯和執行順序:

public class CoroutineExample : MomoBehaviour
{
	private void Start()
	{
	    Debug.Log("Invoke corountine.");
	    StartCoroutine(RoutineExample());
	    Debug.Log("Finish invoke coroutine.");
	}

	private IEnumerator RoutineExample()
	{
	    Debug.Log("Coroutine Start.");
	    yield return new WaitForSeconds(3);
	    Debug.Log("Coroutine End");
	}
}

上述代碼中yield return new WaitForSeconds(3)可以將協程掛起,在3秒後再執行後續代碼。

yield return的方式不同,掛起的方式也不同

yield return 0;
yield return null;
yield return;

這三種方法可以將協程掛起,在下一個邏輯幀Update後繼續執行後續代碼。它們不會受timeScale影響。

yield return new WaitForFixedUpdate();

將協程掛起,在下一個物理幀FixedUpdate後繼續執行後續代碼。會受timeScale影響。

yield return new WaitForSeconds(float time);

將協程掛起,在物理循環經過time秒後繼續執行後續代碼。會受timeScale影響。

yield return new WaitForSecondsRealtime(float time);

將協程掛起,在邏輯循環經過time秒後繼續執行後續代碼。不會受timeScale影響。

yield return new WaitForEndOfFrame();

將協程掛起,在該邏輯幀的渲染結束後繼續執行後續代碼。

yield return new StartCoroutine(IEnumerator routine);

將該協程掛起,開啓目標協程,待目標協程結束後,繼續執行該協程的後續代碼。

yield return new WaitUntil(()=>bool condition);

每個邏輯幀Update之後檢測一次,當Lambda表達式返回ture時,繼續執行後續代碼。

yield return new WaitUntil(()=>bool condition);

每個邏輯幀Update之後檢測一次,當Lambda表達式返回false時,繼續執行後續代碼。

yield break;

結束協程,等同於普通函數中的return。

注:生命週期中的一些事件可以改爲IEnumerator來使用協程調用,如Start和Awake等。

  • 中斷協程

中斷協程時,所有該組件上正中被掛起的協程會被移除。中斷協程的方法包括:

public void MonoBehaviour.StopAllCoroutines();
public void MonoBehaviour.StopCoroutine(string methodName);
public void MonoBehaviour.StopCoroutine(Coroutine routine);

其中,使用string作爲參數的函數,不能用於停止不是由string作爲參數開啓的協程。

我們可以在開啓協程時將其保存下來以方便將來定向的將其停止:

public class StopCoroutineExample : MonoBehaviour
{
	public IEnumerator Start()
	{
		Coroutine routine = StartCoroutine(LogMessage());
		yield return new WaitForSeconds(1.5f);
		StopCoroutine(routine);
		Debug.Log("Coroutine end manually.");
	}
	
	public IEnumerator LogMessage()
	{
		Debug.Log("Coroutine start.");
		yield return new WaitForSeconds(3);
		Debug.Log("Coroutine end automatically.");
	}
}

  • 協程的優勢

使用協程有效的避免了一些垃圾變量和多餘函數,讓代碼更優雅。我們使用協程重新實現了上文中使用Invoke的兩個效果。

使用協程實現燈光閃爍的效果:

public class LightBlink : MonoBehaviour
{
	Light light;
	private void Start()
	{
		Coroutine controller = StartCoroutine(LightBlinking);
	}
	
	private IEnumerator LightBlinking()
	{
		for(;;)
		{
			light.enabled = true;
			yield return new WaitForSeconds(2);
			light.enabled = false;
			yield return new WaitForSeconds(2);
		}
	}
}

使用協程使物體逐漸縮小:

public class ShrinkEffect : MonoBehaviour
{
	private void Start()
	{
		StartCoroutine(Shrink(5,0.1f));
	}
	
	private IEnumerator Shrink(float time; float rate)
	{
		for(float t = 0; t <= time; t += rate)
		{
			transform.localScale *= 0.95f;
			yield return new WaitForSeconds(rate);
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章