簡介
在游戲開發(fā)過程中,我們會大量使用事件系統(tǒng)。很多時候,比起直接調(diào)用對象組件的方法,使用事件觸發(fā)將很大程度上降低系統(tǒng)的耦合度,從而實現(xiàn)更為優(yōu)雅的系統(tǒng)設(shè)計。
封裝一個好用的事件系統(tǒng)將對我們的開發(fā)起到很大的幫助。
本文將基于Unity提供的ScriptableObject和UnityEvent來封裝一個我們自己的事件系統(tǒng)。隨后,我們可以自定義事件,并在監(jiān)聽器監(jiān)聽到事件后執(zhí)行對應(yīng)的程序邏輯。
我們將基于一個實際的需求來更好地說明這個事件系統(tǒng)是如何進(jìn)行工作的。假設(shè)我們現(xiàn)在正在開發(fā)關(guān)卡選擇頁面,這個頁面上將出現(xiàn)數(shù)量不定的關(guān)卡按鈕(關(guān)卡數(shù)量隨著開發(fā)的進(jìn)行需要不斷增加),點擊關(guān)卡按鈕后,需要加載對應(yīng)關(guān)卡的Scene。這個需求適合用事件系統(tǒng)來解決。我們將在按鈕UI的組件上綁定"StartScene"的GameEvent,點擊按鈕后將觸發(fā)這個事件,然后由MapSelectManager上的GameEventListener進(jìn)行監(jiān)聽,并執(zhí)行加載關(guān)卡的邏輯。
正文
GameEvent類
GameEvent類將繼承ScriptableObject類,這樣做可以讓我們能夠在Unity中為每個自定義事件創(chuàng)建一個數(shù)據(jù)對象。ScriptableObject是全局的,在跨場景的需求中尤其好用。
代碼
[CreateAssetMenu(menuName = "Scriptable/GameEvent")]
public class GameEvent : ScriptableObject
{
[HideInInspector] public List<GameEventListener> listeners = new List<GameEventListener>();
// Raise event through different methods signatures
public void Raise(Component sender, object data)
{
for (int i = 0; i < listeners.Count; i++)
{
listeners[i].OnEventRaised(sender, data);
}
}
public void RegisterListener(GameEventListener listener)
{
if (!listeners.Contains(listener))
{
listeners.Add(listener);
}
}
public void UnregisterListener(GameEventListener listener)
{
if (listeners.Contains(listener))
{
listeners.Remove(listener);
}
}
}
GameEvent負(fù)責(zé)做3件事:觸發(fā)事件、注冊監(jiān)聽器、取消注冊監(jiān)聽器。觸發(fā)事件時,所有注冊本事件的監(jiān)聽器都會調(diào)用OnEventRaised(sender, data)來響應(yīng)此事件。我們可以通過data參數(shù)來傳遞所需要的數(shù)據(jù),比如需要加載的關(guān)卡名稱。
GameEventListener類
GameEventListener負(fù)責(zé)監(jiān)聽GameEvent并執(zhí)行后續(xù)邏輯。
代碼
public class GameEventListener : MonoBehaviour
{
public GameEvent gameEvent;
public CustomGameEvent response;
private void OnEnable()
{
gameEvent.RegisterListener(this);
}
private void OnDisable()
{
gameEvent.UnregisterListener(this);
}
public void OnEventRaised(Component sender, object data)
{
response.Invoke(sender, data);
}
}
GameEventListener被Enable時在需要監(jiān)聽的GameEvent上進(jìn)行注冊,被Disable時取消注冊。GameEvent事件觸發(fā)時,OnEventRaised(Component sender, object data)被調(diào)用,執(zhí)行response.Invoke(sender, data)。response是外部傳入的,一個CustomGameEvent對象。
CustomGameEvent
它是Unity提供的UnityEvent的一個封裝,讓我們能將參數(shù)傳入UnityEvent。
代碼
public class CustomGameEvent : UnityEvent<Component, object> { }
使用監(jiān)聽器并綁定對應(yīng)執(zhí)行邏輯
創(chuàng)建一個GameObject用于掛載GameEventListener。
將自定義創(chuàng)建的StartGameScene傳入GameEvent變量。Response也可以傳入外部腳本并調(diào)用腳本中的方法,需要注意的是調(diào)用的方法必須要能夠接收Component和object參數(shù)。
加載場景代碼
public class MapSelectionManager : MonoBehaviour
{
private IEnumerator OnStartGameCoroutine(string sceneName)
{
...
}
public void LoadScene(Component sender, object data)
{
if (data is string sceneName)
{
StartCoroutine(OnStartGameCoroutine(sceneName));
}
}
}
事件的觸發(fā)
假設(shè)我們使用MapItemController來負(fù)責(zé)關(guān)卡按鈕的邏輯,那么當(dāng)按下按鈕時,將調(diào)用StartGameScene()方法,隨后觸發(fā)onStartGameScene的Raise(Component sender, object data)方法,完成事件的觸發(fā)。
public class MapItemController: MonoBehaviour
{
[SerializeField] private GameEvent onStartGameScene;
public void StartGameScene()
{
string sceneName = GetSceneName(); // 偽代碼,獲取需要加載的場景名稱
onStartGameScene.Raise(this, sceneName);
}
onStartGameScene變量中傳入的對象要與先前我們創(chuàng)建的GameEventListener中傳入的對象保持完全一致。
至此,我們就實現(xiàn)了一個松耦合的開始關(guān)卡的需求。
總結(jié)
我們基于ScriptableObject和UnityEvent封裝了一個自己的好用且便于理解的事件系統(tǒng)。文章來源:http://www.zghlxwxcb.cn/news/detail-785433.html
基于事件可以松耦合地實現(xiàn)各類游戲開發(fā)需求。文章來源地址http://www.zghlxwxcb.cn/news/detail-785433.html
到了這里,關(guān)于Unity基礎(chǔ) - 封裝一個好用的事件系統(tǒng)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!