【U3D/簡單框架】3.事件中心模塊及其優化

自我介紹

廣東雙非一本的大三小白,計科專業,想在製作畢設前夯實基礎,畢設做出一款屬於自己的遊戲!

事件中心模塊

  • 知識點: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);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章