1. 程式人生 > >Unity中設計模式應用(二):結合中介者模式的觀察者模式

Unity中設計模式應用(二):結合中介者模式的觀察者模式

一.觀察者模式簡介

觀察者模式(又被稱為釋出-訂閱(Publish/Subscribe)模式,屬於行為模式的一種,它定義了一種一對多的依賴關係,讓多個觀察者物件同時監聽某一個主題物件。當這個主題物件的狀態發生變化時,會通知所有此狀態的觀察者,這些觀察者會執行相應的更新。

二.簡單觀察者模式實現

public class ConcreteSubject:MonoBehaviour
{
    public static ConcreteSubject instance;
    public delegate void ChangeState();
    public event ChangeSate changeSateEventHandler;
    private
int mSate = 0; void Start() { instance = this; } public void ChangeMySate() { mSate ++; changeSate(); } } public class ConcreteObserver:MonoBehaviour { void Start() { ConcreteSubject.instance.changeSateEventHandler += OnChangeState; } public
void OnChangeState() { print("觀察到目標更新了狀態!"); } void OnDisable() { ConcreteSubject.instance.changeSateEventHandler -= OnChangeState; } }

是不是很簡單?

三.引入中介者

上面的觀察者模式實現起來雖然簡單,但是當多個物件相互觀察,就會使得相互之間的關係錯綜複雜,產生過渡耦合。

這裡寫圖片描述

如果是比較大的專案肯定是不能這麼做的。
當引入中介者模式後,物件之間的關係由網狀改為星狀。

這裡寫圖片描述

在觀察者模式中引入一箇中介者,作為事件處理的中心——EventCenter,所有事件的派發、訂閱都是通過這個中心進行的。

四.事件中心

using UnityEngine;
using System.Collections;
using System.IO;
using System.Collections.Generic;
using DG.Tweening;

public class UnityEventCenter{




    private static UnityEventCenter mInstance;

    public static UnityEventCenter Instance
    {
        get
        {
            if (mInstance == null) {
                mInstance = new UnityEventCenter ();
            }
            return mInstance;
        }
    }

    public delegate void OnNotification(IMessage msg);

    private static Dictionary<EnumDefine.EventKey,OnNotification> eventListeners = new Dictionary<EnumDefine.EventKey, OnNotification>();

    public static void AddEventListener(EnumDefine.EventKey eventKey,OnNotification notif)
    {
        if (notif == null || eventKey == null) {
            Debug.LogError ("註冊監聽時,引數不全!!!");
        }
        if (!eventListeners.ContainsKey (eventKey)) {
            eventListeners.Add (eventKey, notif);
        } 
        else 
        {
            eventListeners [eventKey] += notif;
        }
    }


    /// <summary>
    /// 分發事件
    /// </summary>
    /// <param name="eventKey">事件 key.</param>
    /// <param name="msg">通知的訊息.</param>
    public static void DispatchEvent(EnumDefine.EventKey eventKey,IMessage msg)
    {
        if (!eventListeners.ContainsKey (eventKey))
            return;
        eventListeners [eventKey] (msg);
    }

    /// <summary>
    /// 直接移除某一事件的全部監聽,慎用!
    /// </summary>
    /// <param name="eventKey">Event key.</param>
    public static void RemoveEventListener(EnumDefine.EventKey eventKey)
    {
        if (!eventListeners.ContainsKey (eventKey))
            return;
        eventListeners [eventKey] = null;
        eventListeners.Remove (eventKey);
    }

    /// <summary>
    /// 移除某一事件的特定監聽函式
    /// </summary>
    /// <param name="eventKey">Event key.</param>
    /// <param name="notif">要移除的監聽函式</param>
    public static void RemoveEventListener(EnumDefine.EventKey eventKey,OnNotification notif)
    {
        if (!eventListeners.ContainsKey (eventKey))
            return;
        eventListeners [eventKey] -= notif;
        if (eventListeners [eventKey] == null) {
            RemoveEventListener (eventKey);
        }
    }




}

五.戰鬥單位

熟悉爐石的獵人玩家都應該知道這張卡牌:

這裡是我DIY的卡牌

    隨從名稱:盛宴食屍鬼
    類別:生物
    攻擊力:1
    生命值:3
    費用:3
    技能:每當戰場上有生物死亡時,便獲得等值於目標生物最大基礎生命值的攻擊力加成,同時生命值+1

注意到,它的技能監聽的是生物的死亡,而它本身也是生物,所以戰鬥中每一個 盛宴食屍鬼 同時扮演者觀察者和被觀察者兩種角色。

戰鬥場景中它是這樣的:

這裡寫圖片描述

下面上該戰鬥單位的指令碼

using UnityEngine;
using System.Collections;
using UnityEngine.UI;
using DG.Tweening;
using UnityEngine.Events;
using UnityEngine.EventSystems;

public class LifeStealer : MonoBehaviour,IPointerClickHandler {

    Text atkTxt;
    Text hpTxt;

    Tweener atkTweener;
    Tweener hpTweener;

    Sequence atkTwSeq;
    Sequence hpTwSeq;

    GameObject dmgImg;
    DOTweenVisualManager dtwMgr;

    /// <summary>
    /// 最大基礎生命值
    /// </summary>
    [Range(0,99)]
    public int maxBaseHp =3;

    /// <summary>
    /// 最大基礎攻擊力
    /// </summary>
    [Range(0,99)]
    public int maxBaseAtk = 1;

    private int mAtk;
    public int atk {
        set { 
            mAtk = value < 0 ? 0 : value;

            atkTxt.transform.DOShakeScale (0.5f, 0.3f).SetAutoKill (true);

            atkTxt.text = mAtk.ToString ();
            if (mAtk < maxBaseAtk) {
                atkTxt.color = Color.red;
            } else if (mAtk > maxBaseAtk) {
                atkTxt.color = Color.green;
            } else {
                atkTxt.color = Color.white;
            }
        }
        get {
            return mAtk;
        }
    }

    private int mHp;
    public int hp {

        set
        { 
            mHp = value;
            hpTxt.transform.DOShakeScale (0.5f, 0.3f).SetAutoKill(true);
            hpTxt.text = value.ToString ();
            if (value <= 0) {
                OnSelfDead ();
            }
            if (mHp < maxBaseHp) {
                hpTxt.color = Color.red;
            } else if (mHp > maxBaseHp) {
                hpTxt.color = Color.green;
            } else {
                hpTxt.color = Color.white;
            }
        }
        get
        { 
            return mHp;
        }
    }


    //實現 UnityEngine.EventSystems名稱空間下的IPointerClickHandler介面
    public void OnPointerClick(PointerEventData data)
    {
        OnDamaged ();
    }



    void Start()
    {
        print ("Start!");
        Initialize ();
    }

    void OnEnable()
    {
        print ("Enable");
    }

    void Initialize()
    {
        //使用自定義的擴充套件方法尋找目標元件
        atkTxt = gameObject.FindComponentInChild<Text>("AtkTxt");
        hpTxt = gameObject.FindComponentInChild<Text> ("HpTxt");

        atk = maxBaseAtk;
        hp = maxBaseHp;

        dmgImg = GameObject.Find ("DmgImg");

        dtwMgr = dmgImg.GetComponent<DOTweenVisualManager> ();

        UnityEventCenter.AddEventListener (EnumDefine.EventKey.OnCreatureDead, OnCreatureDead);

    }

    public void OnDamaged()
    {
        hp -= 2;
        dtwMgr.enabled = false;
        dmgImg.transform.position = transform.position;
        dtwMgr.enabled = true;
    }

    /// <summary>
    /// 接收到其它單位死亡的資訊
    /// </summary>
    /// <param name="msg">Message.</param>
    public void OnCreatureDead(IMessage msg)
    {
        OnDeadMsg deadMsg = (OnDeadMsg)msg;

        //如果是自己死亡則不會對自身產生增益效果
        if (deadMsg.sender == gameObject)
            return;

        int deadUnitHp = deadMsg.iParam;

        BuffAtk (deadUnitHp);
        BuffHp ();

    }


    void BuffAtk(int amount)
    {
        atk += amount;
    }

    void BuffHp()
    {
        hp = hp + 1;
    }

    void OnSelfDead()
    {
        OnDeadMsg msg = new global::OnDeadMsg (gameObject, 0f, maxBaseHp);

        //自身死亡,事件中心派發生物死亡事件
        UnityEventCenter.DispatchEvent (EnumDefine.EventKey.OnCreatureDead, msg);

        //自身已經死亡,無法再繼續監聽生物死亡事件
        UnityEventCenter.RemoveEventListener (EnumDefine.EventKey.OnCreatureDead, OnCreatureDead);

        gameObject.SetActive (false);
    }

}

六.其它指令碼

(1)GameTools

很多時候會用到這個功能:
知道某一個物體的名字,但是該物體是兒子輩、孫子輩、曾孫子輩的,index是多少?這就不清楚了。
反正是想獲得該物體身上的元件,又不瞭解具體的目錄結構。
於是有了下面的擴充套件。

using UnityEngine;
using System.Collections;

static public class GameTools{


    /// <summary>
    /// 尋找父物體下的泛型約定的元件,該元件所在的物體的名字為childName
    /// </summary>
    /// <returns>The component in child.</returns>ASE
    /// <param name="parent">使用擴充套件方法的當前物體.</param>
    /// <param name="childName">該元件所掛的孩子的名字</param>
    /// <typeparam name="T">元件的型別</typeparam>
    public static T FindComponentInChild<T>(this UnityEngine.GameObject parent,string childName) where T:Component
    {
        T[] components = parent.GetComponentsInChildren<T> ();

        for (int i = 0; i < components.Length; i++) 
        {
            if (components [i].gameObject.name.Equals (childName)) {
                return components [i];
            }
        }
        return null;
    }
}

(2) eventKey
eventKey設定為列舉而不是字串,字串拼寫容易出錯。

using UnityEngine;
using System.Collections;

public static class EnumDefine{

    public enum EventKey
    {
        /// <summary>
        /// 生物死亡事件
        /// </summary>
        OnCreatureDead,
        /// <summary>
        /// 生物被召喚
        /// </summary>
        OnCreatureSummoned,
        None = -1,
    }
}

(3)msg
所有訊息繼承實現該介面

using UnityEngine;
using System.Collections;

public interface IMessage
{
    GameObject sender {
        get;
        set;
    }
}

2.結構體為訊息的最終實現

具體某一訊息的實現,使用了結構體,好處有以下幾點:
1.結構體儲存在堆疊(Stack)中,訪問速度快,適合輕量級的內容傳遞;
2.相比於Object等,結構體為值型別,省去了裝箱與拆箱的耗時操作;
(若將引用型別按值傳遞引數,則需要先將引用型別轉為值型別)

using UnityEngine;
using System.Collections;
using System.Security.Cryptography;

public struct OnDeadMsg : IMessage {

    public GameObject sender {
        get;
        set;
    }

    public float fParam 
    {
        get;
        set;
    }

    public int iParam 
    {
        get;
        set;
    }

    public Vector3 vParam 
    {
        get;
        set;
    }

    public Color cParam {
        get;
        set;
    } 

    public OnDeadMsg(GameObject s,float f =0.0f,int i = 0, Vector3 v = default(Vector3),Color c = default(Color))
    {
        this.sender = s;
        this.fParam = f;
        this.iParam = i;
        this.vParam = v;
        this.cParam = c;
    }

    public override string ToString ()
    {
        return string.Format ("[OnDeadMsg: sender={0}, fParam={1}, iParam={2}, vParam={3}, cParam={4}]", sender, fParam, iParam, vParam, cParam);
    }

}

七.場景搭建

這裡寫圖片描述

八.效果展示

九.Demo資源

http://pan.baidu.com/s/1mi5KkRI