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