到了叫我。

真實的生活裡

真實生活裡,有句話你我一定都說過或是被交代過:

“到了叫我。",

因為真的不知道對方/我們何時會到,若每幾分鐘就打電話詢問:

“到了沒?"

是不是很煩人?(但我相信有些朋友真的會做這種事,特別是女友、老婆等級的

在這裡講一下程式上要如何去做這件 “到了叫我" 的功能。

程式的世界裡

程式界裡大多稱這功能為 Callback、Delegates 或 Event, 我人們回想一下 Unity 裡我們好像常用到類似這樣的功能,如果你想到了 OnXXX系列(如: OnTriggerEnter、OnTriggerExit … 等等的一堆),那你的概念很接近了,程式上他們不是真正的 Callback、Delegates 或 Event,而且實作方法也不太像,但他們的確有那種 “到了叫我" 的感覺。

答案是:

狀況實例

我現在開著車,我一路上會經過三個休息站 Q、W、E ,在休息站裡我才能打手機過去說幾句話(string)。

這裡會用到:

  • event
  • System.Action

沒錯!只有這二個,夠簡單了吧!以下定義出事件的樣子,Action 後的 string 是宣告我會傳入一個 string 當參數。

public event Action<string> onArrivedQ;
public event Action<string> onArrivedW;
public event Action<string> onArrivedE;

在我到各休息站時,就看一下誰要求過我到休息站時要回報一下,若有,就打個電話過去。

	if (Input.GetKeyUp(KeyCode.Q))
	{
		if (onArrivedQ != null)
		{
			onArrivedQ("Arrived Q");
		}
	}
	else if (Input.GetKeyUp(KeyCode.W))
	{
		if (onArrivedW != null)
		{
			onArrivedW("Arrived W");
		}

	}
	else if (Input.GetKeyUp(KeyCode.E))
	{
		if (onArrivedE != null)
		{
			onArrivedE("Arrived E");
		}
	}

接下來,我的朋友說過:"到終點 E 時再叫我!",朋友身上有支跟我定義一樣是用一個 string 當參數的 function ,他可以把這支 function += 至我身上。

//MyDriver myDriver;
void Arrived(string msg)
{
	Debug.Log("friend get event: " + msg);
}

void OnEnable()
{
	myDriver.onArrivedE += Arrived;
}

void OnDisable()
{
	myDriver.onArrivedE -= Arrived;
}

為避免 event 重複累加,我們會在 OnEnable() 加入事件,在 OnDisable() 時移除事件。

然後,女朋友在我們出門時,總是問東問東管東西時時的關心我,但開車中不能接手機,只要到休息站回她個電話敷衍一下報個平安即可。她身上也有跟我定義一樣是用一個 string 當參數的 function,她也可以把這支 function += 至我身上。

//MyDriver myDriver;
void Arrived(string msg)
{
	Debug.Log("girl friend get event: " + msg);
}

void OnEnable()
{
	myDriver.onArrivedQ += Arrived;
	myDriver.onArrivedW += Arrived;
	myDriver.onArrivedE += Arrived;
}

void OnDisable()
{
	myDriver.onArrivedQ -= Arrived;
	myDriver.onArrivedW -= Arrived;
	myDriver.onArrivedE -= Arrived;
}

使用 += 可以讓事件一直累加,若是用 = 會把之前所有累加的事件全取代掉只剩目前這一個。

好了,以上就完成了 “到了叫我" 的功能,當我們到達 Q、W、E 時,  Log 會顯示出朋友只有在到 E 時才收到事件,而女友則是收到所有的事件。

這個功能可以應用在很多地方,比如之前講過的 設計我們的遊戲物理-簡易重力篇 ,當我們碰到地面時可以加入 event 用來告知撞到地面了,其它想知道這個事件的元件看是要放特效或做其它有意義的事(比如鏡頭震動)。

if (rhf.collider != null && rhf.collider.gameObject.tag == "ground")
{
	if (onGround != null)
	{
		onGround();
	}
}

應用在血量變化也不錯,誰需要這個就血量變化的事件就可以用 += 把自己的 fucntion 加進來。

if (lastHp != nowHp)
{
	if (onHpChange != null)
	{
		onHpChange(nowHp);
	}
}

if (nowHp <= 0)
{
	if (onHpZero != null)
	{
		onHpZero(nowHp);
	}
}

提醒一下,我們只用 string、int 當參數,其實事件可傳的參數是任意的(也可以不傳),如:

  • class
  • struct
  • System.object (什麼都能丟進去,只是接收者要對傳進來的參數做轉型)
  • 其它

只要對方身上的 function 參數也樣一樣,就可以用 += 累加進去。以下列幾個示範一下:

public event Action onNothingToSay;
public event Action<string> onArrivedQ;
public event Action<int> onHpChange;
public event Action<GameObject> onDoGameOject;
public event Action<BoxCollider> onDoCollider;
public event Action<someClass1, someClass2> onDoSomeSomething;

我覺得,有規劃的設計我們遊戲的事件,會讓我們的程式功能區分更清楚,才不會寫出某條件下寫了一堆呼叫別人的程式碼;但也別濫用過頭,不然寫一堆 +=、-=、OnEnable、OnDisable 其實也是很累人了。

完整程式碼:

  • MyDriver.cs
using UnityEngine;
using System.Collections;
using System;

public class MyDriver : MonoBehaviour {

public event Action onArrived;
public event Action<string> onArrivedQ;
public event Action<string> onArrivedW;
public event Action<string> onArrivedE;

// Use this for initialization
void Start ()
{

}

// Update is called once per frame
void Update ()
{
	if (Input.GetKeyUp(KeyCode.Q))
	{
		if (onArrivedQ != null)
		{
			onArrivedQ("Arrived Q");
		}
	}
	else if (Input.GetKeyUp(KeyCode.W))
	{
		if (onArrivedW != null)
		{
			onArrivedW("Arrived W";
		}

	}
	else if (Input.GetKeyUp(KeyCode.E))
	{
		if (onArrivedE != null)
		{
			onArrivedE("Arrived E");
		}
	}
	else if (Input.GetKeyUp(KeyCode.R))
	{
		if (onArrived != null)
		{
			onArrived();
		}
	}
}

  • MyFriend.cs
using UnityEngine;
using System.Collections;

public class MyFriend : MonoBehaviour {

public MyDriver myDriver;
// Use this for initialization

void Arrived(string msg)
{
	Debug.Log("friend get event: " + msg);
}
void ArrivedR()
{
	Debug.Log("friend get event R");
}

void OnEnable()
{
	myDriver.onArrivedE += Arrived;
	myDriver.onArrived += ArrivedR;
}
void OnDisable()
{
	myDriver.onArrivedE -= Arrived;
	myDriver.onArrived -= ArrivedR;
}

void Start ()
{

}

// Update is called once per frame
void Update () {

}
}

  • MyGirlFriend.cs
using UnityEngine;
using System.Collections;

public class MyGirlFriend : MonoBehaviour {

public MyDriver myDriver;
// Use this for initialization

void Arrived(string msg)
{
	Debug.Log("girl friend get event: " + msg);
}

void OnEnable()
{
	myDriver.onArrivedQ += Arrived;
	myDriver.onArrivedW += Arrived;
	myDriver.onArrivedE += Arrived;
}
void OnDisable()
{
	myDriver.onArrivedQ -= Arrived;
	myDriver.onArrivedW -= Arrived;
	myDriver.onArrivedE -= Arrived;
}

}

大家一起好好的設計我們的遊戲吧!

Chang-Pei Lee:宣告Action 時可以設定初始值為delegate{},就不用額外檢查是否為null喔~

對「到了叫我。」的一則回應

發表留言