1. 程式人生 > 其它 >Unity程式基礎框架(三)事件中心公共模組

Unity程式基礎框架(三)事件中心公共模組

------------恢復內容開始------------

事件中心模組

本篇文章只是用來記錄學習的筆記,老師上課教授,筆記內容有複製拷貝網上的資料、筆記。
知識點: Dictionary,委託,觀察者模式
委託和事件詳解
作用:降低程式耦合性,減小程式複雜度


按照這張圖來建立4個指令碼以及物件

Monster

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Monster : MonoBehaviour
{
    void Start()
    {
        Dead();
    }

    /// <summary>
    /// 死亡方法
    /// </summary>
    void Dead()
    {
        Debug.Log("怪物死亡");

        //玩家得獎勵
        GameObject.Find("Player").GetComponent<Player>().MonsterDeadDo();
        //任務記錄
        GameObject.Find("Task").GetComponent<Task>().TaskWaitMonsterDeadDo();
        //其它
        GameObject.Find("Other").GetComponent<Other>().OtherWaitMonsterDeadDo();
        //等等
        
    }
}

Player

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    /// <summary>
    /// 怪物死亡時要做些什麼
    /// </summary>
    /// <param name="info"></param>
    public void MonsterDeadDo()
    {
        Debug.Log("玩家得獎勵" );
    }
}

Task

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Task : MonoBehaviour
{
    public void TaskWaitMonsterDeadDo()
    {
        Debug.Log("任務,記錄");
    }
}

Other

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Other : MonoBehaviour
{
    /// <summary>
    /// 怪物死的時候要做的事
    /// </summary>
    /// <param name="info"></param>
    public void OtherWaitMonsterDeadDo()
    {
        Debug.Log("其它 各個物件要做的事情");
    }
}

將指令碼掛載到對應的物件上
執行結果如下:

問題:怪物死亡後,觸發另外三個指令碼。如果還需要其他很多事件都在monster類中寫的話耦合性會很高(關聯性高,如果獎勵、任務、或者是怪物死亡指令碼沒有寫好,就會互相影響到)

解決方法:引入事件中心的概念,由事件中心來處理收發事件。 之前在Monster裡面,要呼叫各個不同指令碼中的方法時,需要分別訪問,而有了事件中心的時候,就只需要訪問事件中心的EventTrigger


事件中心程式碼EventCenter

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 事件中心 單例模式物件
/// 1.Dictionary
/// 2.委託
/// 3.觀察者設計模式
/// </summary>
public class EventCenter :BaseManger<EventCenter> 
{
    //key對應的是事件的名字
    //value對應的是監聽這個事件對應的委託函式們
    private Dictionary<string, UnityAction> eventDic = new Dictionary<string, UnityAction>();
    
    /// <summary>
    ///新增事件監聽 
    /// </summary>
    /// <param name="name">事件的名字</param>
    /// <param name="action">準備用來處理事件的委託函式</param>
    public void AddEventListener(string name, UnityAction action)
    {
        //判斷字典裡有沒有對應這個事件,有就執行,沒有就加進去。
        if (eventDic.ContainsKey(name))
        {
            eventDic[name] += action;
        }
        else
        {
            eventDic.Add(name, action);
        }
    }
    /// <summary>
    /// 事件觸發
    /// </summary>
    /// <param name="name">哪一個名字的事件觸發了</param>
    public void EventTrigger(string name)
    {
        if (eventDic.ContainsKey(name))
        {
            // eventDic[name]();
            eventDic[name].Invoke();
        }
    }
}

Monster

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Monster : MonoBehaviour
{
    void Start()
    {
        Dead();
    }

    /// <summary>
    /// 死亡方法
    /// </summary>
    void Dead()
    {
        Debug.Log("怪物死亡");

        //觸發事件
        EventCenter.instance.EventTrigger("MonsterDead"); 
    }
}

Other

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Other : MonoBehaviour
{

     void Awake()
    {
        EventCenter.instance.AddEventListener("MonsterDead", OtherWaitMonsterDeadDo); 
    }


    /// <summary>
    /// 怪物死的時候要做的事
    /// </summary>
    /// <param name="info"></param>
    public void OtherWaitMonsterDeadDo()
    {
        Debug.Log("其它 各個物件要做的事情");
    }
}

Player

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    void Awake()
    {
        EventCenter.instance.AddEventListener("MonsterDead", MonsterDeadDo);
    }

    /// <summary>
    /// 怪物死亡時要做些什麼
    /// </summary>
    /// <param name="info"></param>
    public void MonsterDeadDo()
    {
        Debug.Log("玩家得獎勵" );
    }
}

Task

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Task : MonoBehaviour
{
    void Awake()
    {
        EventCenter.instance.AddEventListener("MonsterDead", TaskWaitMonsterDeadDo);
    }

    public void TaskWaitMonsterDeadDo()
    {
        Debug.Log("任務,記錄");
    }
}

問題:在例如比如玩家死亡,怪物在玩家之後死亡,他仍要執行玩家裡的那個方法,這是不對的,所以玩家死亡的時候在記憶體裡不會真正消失,因為怪物跟玩家有所關聯,這就可能造成記憶體洩漏,應該怎麼做呢?

解答:要在Destroy中移除事件

    /// <summary>
    /// 移除對應的實踐監聽
    /// </summary>
    /// <param name="name">事件的名字</param>
    /// <param name="action">對應之前新增的委託函式</param>
    public void RemoveEventListener(string name,UnityAction action)
    {
        if (eventDic.ContainsKey(name))
        {
            eventDic[name] -= action;
        }
    }

當物件銷燬時回執行Ondestory生命週期函式,移除監聽事件。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Task : MonoBehaviour
{
    void Start()
    {
        EventCenter.instance.AddEventListener("MonsterDead", TaskWaitMonsterDeadDo);
    }

    public void TaskWaitMonsterDeadDo()
    {
        Debug.Log("任務,記錄");
    }

    void OnDestroy()
    {
        EventCenter.instance.RemoveEventListener("MonsterDead", TaskWaitMonsterDeadDo); 
    }
}

某些時候忘記新增移除事件的函式,或者場景切換時出現問題時,監聽事件沒有移除乾淨,這時候就需要清空事件中心。

    /// <summary>
    /// 清空事件中心
    /// 主要用在場景切換時
    /// </summary>
    public void Clear()
    {
        eventDic.Clear();
    }

現在還有一個問題:當有多種怪物死亡時(多種觸發條件)無法具體判斷是哪種怪物從而導致事件的混亂,
方法:為了解決這個問題,使用泛型委託。所有物件都是繼承於object,用object可以代替很多型別變數。雖然會有拆箱裝箱的消耗,但是這樣就可以通用。
EventCenter

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

/// <summary>
/// 事件中心 單例模式物件
/// 1.Dictionary
/// 2.委託
/// 3.觀察者設計模式
/// </summary>
public class EventCenter :BaseManger<EventCenter> 
{
    //key對應的是事件的名字
    //value對應的是監聽這個事件對應的委託函式們
    private Dictionary<string, UnityAction<object>> eventDic = new Dictionary<string, UnityAction<object>>();
    
    /// <summary>
    ///新增事件監聽 
    /// </summary>
    /// <param name="name">事件的名字</param>
    /// <param name="action">準備用來處理事件的委託函式</param>
    public void AddEventListener(string name, UnityAction<object> action)
    {
        //判斷字典裡有沒有對應這個事件,有就執行,沒有就加進去。
        if (eventDic.ContainsKey(name))
        {
            eventDic[name] += action;
        }
        else
        {
            eventDic.Add(name, action);
        }
    }

    /// <summary>
    /// 移除對應的實踐監聽
    /// </summary>
    /// <param name="name">事件的名字</param>
    /// <param name="action">對應之前新增的委託函式</param>
    public void RemoveEventListener(string name,UnityAction<object> action)
    {
        if (eventDic.ContainsKey(name))
        {
            eventDic[name] -= action;
        }
    }

    /// <summary>
    /// 事件觸發
    /// </summary>
    /// <param name="name">哪一個名字的事件觸發了</param>
    public void EventTrigger(string name,object info)
    {
        if (eventDic.ContainsKey(name))
        {
            // eventDic[name]();
            eventDic[name].Invoke(info);
        }
    }

    /// <summary>
    /// 清空事件中心
    /// 主要用在場景切換時
    /// </summary>
    public void Clear()
    {
        eventDic.Clear();
    }
}

Monster

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Monster : MonoBehaviour
{
    public string Tite= "經驗到手";
    void Start()
    {
        Dead();
    }

    /// <summary>
    /// 死亡方法
    /// </summary>
    void Dead()
    {
        Debug.Log("怪物死亡");

        //觸發事件
        EventCenter.instance.EventTrigger("MonsterDead",this);
        
    }
}

Player

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Player : MonoBehaviour
{
    void Start()
    {
        EventCenter.instance.AddEventListener("MonsterDead", MonsterDeadDo);
    }

    /// <summary>
    /// 怪物死亡時要做些什麼
    /// </summary>
    /// <param name="info"></param>
    public void MonsterDeadDo(object info)
    {
        Debug.Log("玩家得獎勵"+(info as Monster).Tite );
    }

    void OnDestroy()
    {
        EventCenter.instance.RemoveEventListener("MonsterDead", MonsterDeadDo);
    }
}


公共Mono模組

問題:如果一個類沒有繼承MonoBehaviour 是沒有Update函式的,但是如果這個類有一些事件需要每幀都執行,應該怎麼辦?
(Unity中Inspector面板中的每一項都稱為元件,包括建立的指令碼),該指令碼是必須要繼承MonoBehaviour,否則無法新增為元件。

解決方案:
幀更新

單例模式基類

呼叫函式需要介面所以再建立一個供外部函式呼叫的類,繼承單例模式基類。
MonoMger管理MonoController裡面的程式碼
MonoMgr.cs

/*1.可以提供給外部新增幀更新事件的方法  2.可以提供給外部新增協程的方法*/
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using UnityEngine;
using UnityEngine.Events;

public class MonoMgr : Singleton<MonoMgr>
{
    private MonoController controller;

    public MonoMgr()
    {
        //保證了MonoController物件的唯一性(單例模式只有在為空時才會new一次 MonoMgr()建構函式也只會new一次)
        GameObject obj = new GameObject("MonoController");
        controller = obj.AddComponent<MonoController>();
    }

    /// <summary>
    /// 給外部提供的 新增幀更新事件的函式
    /// </summary>
    /// <param name="fun"></param>
    public void AddUpdateListener(UnityAction fun)
    {
        controller.AddUpdateListener(fun);
        
    }

    /// <summary>
    /// 提供給外部用於移除幀更新事件函式
    /// </summary>
    /// <param name="fun"></param>
    public void RemoveUpdateListener(UnityAction fun)
    {
        controller.RemoveUpdateListener(fun);
    }

    #region 以下為封裝 協程相關的介面
	
	
    public Coroutine StartCoroutine(string methodName)
    {
        return controller.StartCoroutine(methodName);
    }
    public Coroutine StartCoroutine(IEnumerator routine)
    {
        return controller.StartCoroutine(routine);
    }

    public Coroutine StartCoroutine(string methodName, [DefaultValue("null")] object value)
    {
        return controller.StartCoroutine(methodName, value);
    }


    public void StopAllCoroutines()
    {
        controller.StopAllCoroutines();
    }

    public void StopCoroutine(IEnumerator routine)
    {
        controller.StopCoroutine(routine);
    }

    public void StopCoroutine(Coroutine routine)
    {
        controller.StopCoroutine(routine);
    }

    public void StopCoroutine(string methodName)
    {
        controller.StopCoroutine(methodName);
    } 
    #endregion
}

筆記複製參考連結:
【Unity】事件中心

Unity3D程式基礎框架(上)