自我介紹
廣東雙非一本的大三小白,計科專業,想在製作畢設前夯實基礎,畢設做出一款屬於自己的遊戲!
事件中心模塊
- 知識點:Dictionary,委託,觀察者設計模式
- 作用:降低程序耦合性,減少程序複雜度
簡單的一個事件中心繫統 EventCenter.cs
using System.Collections.Generic;
using UnityEngine.Events;
public class EventCenter : BaseSingleton<EventCenter>
{
// key對應事件的名字,value對應的是監聽這個事件對應的委託函數【們】
private Dictionary<string, UnityAction> eventDic = new Dictionary<string, UnityAction>();
// 添加事件監聽
public void AddEventListener(string name, UnityAction action)
{
if (eventDic.ContainsKey(name))
eventDic[name] += action;
else
eventDic.Add(name, action);
}
// 事件觸發
public void EventTrigger(string name)
{
if (eventDic.ContainsKey(name))
eventDic[name].Invoke();
}
// 一定要移除,不然可能會發生內存泄漏
public void RemoveEventListen(string name, UnityAction action)
{
if (eventDic.ContainsKey(name))
eventDic[name] -= action;
}
// 清空事件中心,主要用在場景切換時
public void Clear()
{
eventDic.Clear();
}
}
測試,比如怪物死亡,對應的玩家,分數事件之類的發生變化(以下代碼均放到遊戲物體上纔可觸發)
public class Monster : MonoBehaviour
{
public string name = "123";
private void Start()
{
Dead();
}
void Dead()
{
Debug.Log("怪物死亡");
// 觸發事件
EventCenter.GetInstance().EventTrigger("MonsterDead");
}
}
public class Player : MonoBehaviour
{
private void Start()
{
EventCenter.GetInstance().AddEventListener("MonsterDead", MonsterDeadDo);
}
private void OnDestroy() => EventCenter.GetInstance().RemoveEventListen("MonsterDead", MonsterDeadDo);
private void MonsterDeadDo() => Debug.Log("玩家得到獎勵");
}
public class Task : MonoBehaviour
{
private void Start()
{
EventCenter.GetInstance().AddEventListener("MonsterDead", TaskWaitMonsterDeadDo);
}
private void OnDestroy() => EventCenter.GetInstance().RemoveEventListen("MonsterDead", MonsterDeadDo);
public void TaskWaitMonsterDeadDo() => Debug.Log("任務 記錄");
}
爲什麼要在Destroy中移除事件呢,比如玩家死亡,怪物在玩家之後死亡,他要執行玩家裏的那個方法,所以玩家死亡的時候在內存裏不會真正消失,因爲怪物跟玩家有所關聯,這就可能造成內存泄漏
爲了讓事件中心繫統更加通用(處理多參數方法),我們需要升級我們的 EventCenter.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class EventCenter : BaseManager<EventCenter>
{
// key對應事件的名字,value對應的是監聽這個事件對應的委託函數【們】
private Dictionary<string, UnityAction<object>> eventDic = new Dictionary<string, UnityAction<object>>();
// 添加事件監聽
public void AddEventListener(string name, UnityAction<object> action)
{
if (eventDic.ContainsKey(name))
{
eventDic[name] += action;
}
else
{
eventDic.Add(name, action);
}
}
// 事件觸發
public void EventTrigger(string name, object info)
{
if (eventDic.ContainsKey(name))
{
eventDic[name]?.Invoke(info);
}
}
public void RemoveEventListen(string name, UnityAction<object> action)
{
if (eventDic.ContainsKey(name))
{
eventDic[name] -= action;
}
}
// 清空事件中心,主要用在場景切換時
public void Clear()
{
eventDic.Clear();
}
}
因爲用到object,所以會有裝箱拆箱的問題,不過爲了更加通用,只能在使用的時候儘量不傳遞值類型,就不會發生裝箱拆箱問題了
關鍵來了!我們在註冊這個事件的時候!
Monster.cs
void Dead()
{
Debug.Log("怪物死亡");
// 觸發事件
EventCenter.GetInstance().EventTrigger("MonsterDead", this);
}
可以看到這裏吧this傳進去,那麼怎麼用呢?
修改一下player的代碼,其他同理
public class Player : MonoBehaviour
{
private void Start()
{
EventCenter.GetInstance().AddEventListener("MonsterDead", MonsterDeadDo);
}
private void MonsterDeadDo(object info)
{
Debug.Log("玩家得到獎勵:" + (info as Monster).name);
}
private void OnDestroy()
{
EventCenter.GetInstance().RemoveEventListen("MonsterDead", MonsterDeadDo);
}
}
可以把這個object當成Monster然後調用name
優化事件中心模塊
現在的事件中心模塊因爲有object,會涉及到裝箱拆箱問題,所以會有一定的性能損耗,所以存在優化的空間
涉及到里氏轉換原則:基類裝子類
靈活使用里氏轉換原則,靈活運用空接口與泛型的配合,爲什麼要用空接口呢,因爲如果要穿個T過去,就必須是 public class EventCenter<T>
,但EventCenter只有一個,比如傳入了GameObject作爲T就不能再改了
關於實現無參方法,依然要繼承空接口,創建同名類即可(最好看下面代碼)
先創建接口並對接口進行繼承 EventInfo.cs
using UnityEngine.Events;
public interface IEventInfo { }
// 實現一個參數
public class EventInfo<T> : IEventInfo
{
public UnityAction<T> actions;
public EventInfo(UnityAction<T> action)
{
actions += action;
}
}
// 實現無參
public class EventInfo : IEventInfo
{
public UnityAction actions;
public EventInfo(UnityAction action)
{
actions += action;
}
}
升級 EventCenter.cs ,還添加了不需要參數觸發的(重載)也行
using System.Collections.Generic;
using UnityEngine.Events;
public class EventCenter : BaseSingleton<EventCenter>
{
// key對應事件的名字,value對應的是監聽這個事件對應的委託函數【們】
private Dictionary<string, IEventInfo> eventDic = new Dictionary<string, IEventInfo>();
// 添加事件監聽,一個參數的
public void AddEventListener<T>(string name, UnityAction<T> action)
{
if (eventDic.ContainsKey(name))
(eventDic[name] as EventInfo<T>).actions += action;
else
eventDic.Add(name, new EventInfo<T>(action));
}
// 添加事件監聽,無參數的
public void AddEventListener(string name, UnityAction action)
{
if (eventDic.ContainsKey(name))
(eventDic[name] as EventInfo).actions += action;
else
eventDic.Add(name, new EventInfo(action));
}
// 事件觸發,無參的
public void EventTrigger(string name)
{
if (eventDic.ContainsKey(name))
(eventDic[name] as EventInfo).actions?.Invoke();
}
//事件觸發,一個參數的
public void EventTrigger<T>(string name, T info)
{
if (eventDic.ContainsKey(name))
(eventDic[name] as EventInfo<T>).actions?.Invoke(info);
}
//移除監聽,無參的
public void RemoveEventListener(string name, UnityAction action)
{
if (eventDic.ContainsKey(name))
(eventDic[name] as EventInfo).actions -= action;
}
//移除監聽,一個參數的
public void RemoveEventListener<T>(string name, UnityAction<T> action)
{
if (eventDic.ContainsKey(name))
(eventDic[name] as EventInfo<T>).actions -= action;
}
// 清空事件中心,主要用在場景切換時
public void Clear()
{
eventDic.Clear();
}
}
使用,還是拿之前的player舉例
public class Player : MonoBehaviour
{
private void Awake()
{
EventCenter.GetInstance().AddEventListener<Monster>("MonsterDead", MonsterDeadDo);
}
private void MonsterDeadDo(Monster info)
{
Debug.Log("玩家得到獎勵:" + info.name);
}
private void OnDestroy()
{
EventCenter.GetInstance().RemoveEventListen<Monster>("MonsterDead", MonsterDeadDo);
}
}