不管是UnityEvent、Event,基本上都是基於Delegate所產生變體,而一旦使用了Event後,便可以極大幅的減少腳本之間的依賴,也不用在苦惱要在start還是update中getcomponet了,因為幾乎都可以用註冊的方式直接完成這些操作。
而其中UnityEvent更是將其完全盡可能極簡化,特別是如果不需要有額外參數傳入的UnityEvevt更是可以直接在Editor中進行操作,其使用難度以及實用性可以說是必學一點都不為過!
接著先上一個小專案的codeing,參考自以下這支影片
接著附上加上了我可以理解的coding
其中的MainScript
using System.Collections;
using System.Collections.Generic;
using TMPro;
using System.Linq;
using UnityEngine;
using UnityEngine.Events;
public class Collector : MonoBehaviour
{
[SerializeField]
TMP_Text _text;
[SerializeField]
List<Collectible> _gatherables;//要蒐集的東西
[SerializeField]
UnityEvent OnCompleteEvent;
List<Collectible> _collectiblesRemaining;
void OnEnable()
{
_collectiblesRemaining = new List<Collectible>(_gatherables);
foreach (var collectible in _collectiblesRemaining) //逐一檢視collectiblesRemaining(Collectible新宣告)上的每個OnPick事件
{
collectible.OnPickup += HandlePickup;//在每個OnPickup中註冊新的HandlePickup
UpdateText();
}
}
void HandlePickup(Collectible collectible)
{
_collectiblesRemaining.Remove(collectible);
UpdateText();
if (_collectiblesRemaining.Count == 0)
{
OnCompleteEvent.Invoke();//開始執行事件,且因為 OnCompleteEvent為UnityEvent因此不需要以?.Invoke作為啟動,因為Unity不可能為null
}
}
void UpdateText() //text更新
{
_text.SetText($"{_collectiblesRemaining.Count}more...");
if (_collectiblesRemaining.Count == 0 || _collectiblesRemaining.Count == _gatherables.Count)
{
_text.enabled = false;
}
else
{
_text.enabled = true;
}
}
[ContextMenu("AutoFill Collectibles")]
void AutoFillCollectibles()
{
_gatherables = GetComponentsInChildren<Collectible>().Where(t => t.name.ToLower().Contains("red")).ToList();//可在該物件底下的子物件搜尋有<Collectible>的物件的name是否有Red並自動充進List,在編輯畫面中Inspector對著該script右鍵呼喚
}
}
接著是撿拾的東西上的腳本
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Collectible : MonoBehaviour
{
public event Action<Collectible> OnPickup;
private void OnTriggerEnter2D(Collider2D other)
{
var player = other.GetComponent<Player>();
if (player != null)
{
//if(OnPickup != null)
//{
//OnPickup(this)
//}
//↑原應該為上面這些字串
OnPickup?.Invoke(this);//由於該Event有可能為null,因此需?.Invoke
gameObject.SetActive(false);
}
}
}
在這個範例中,主要是示範了一般的Event以及UnityEvent在使用的一些差別,還有一個自動搜尋目標物件的酷炫功能示範,接著來試試看經過極簡化的UnityEvent
===============================================================================
一、不帶參數 No Parameter
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class UnityEventTest : MonoBehaviour
{
[SerializeField]
UnityEvent CubeCount1;
[SerializeField]
UnityEvent CubeCount2;
public int ClickTime = 0;
void Update()
{
if (ClickTime == 5)
{
CubeCount1.Invoke();
}
if (ClickTime == 8)
{
CubeCount2.Invoke();
}
}
public void CountUP() //註冊進下圖中的button
{
ClickTime++;
}
}
Editor中的畫面就是長得跟button很是相似,只要先需告好可在Editor中可見的UnityEvent就可以直接Editor中進行調選,甚至可以直接調用tag、setactive等這些常用的api連code都不用打,有夠方便。
二、有帶參數 Has Parameter
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class MyEvent : UnityEvent<int, bool> { }
//因為UnityEvent<T0,T1,T2...>(最多T3)是抽象類,所以需要聲明一個類來繼承它
public class UnityActionWithParameter : MonoBehaviour
{
public MyEvent myEvent = new MyEvent();//實例化
public UnityAction<int, bool> action;//聲明一個具有int,bool的unityaction並命名為action
private float nowtime;
int count = 0;
void Start()
{
action = new UnityAction<int, bool>(MyFunction);//將MyFunction註冊進action
action += MyFunction2;//繼續增加註冊的function
myEvent.AddListener(action);//讓myEvent監聽action
//myEvent.RemoveListener(xxxx); 則是反註冊其中的action
}
void Update()
{
if (Input.GetKeyDown(KeyCode.A))
{
print("按下A鍵後輸出");
myEvent.Invoke(10,true);
}
if (Input.GetKeyDown(KeyCode.B))
{
print("按下B鍵後輸出");
myEvent.Invoke(20, false);
}
}
public void MyFunction(int i,bool x)
{
if (x)
{
print(i);
}
}
public void MyFunction2(int i,bool x)
{
if (x == false)
{
print(i * 2);
}
}
}
看結果
差不多就這樣,UnityEvent可以說是把Event給再次經過魔改後變得超級無敵簡單好上手,雖然帶參數的UnityEvent跟一般Event較為相近,想使用上較為不直觀,但是在程式設計上則是提供了更多的發揮空間。
UnityEvent就差不多到這裡,接下來應該會記錄學習塔防遊戲以及格狀回合制遊戲的過程,卡牌遊戲那60多個教學影片光是看了就想吐,但是為了專案還是得要乖乖硬啃完。
加油吧!