前言:“在对象间定义一种一对多的依赖关系,以便当某对象的状态改变时,与它存在依赖关系的所有对象都能够接收到通知并自动进行更新。”----------摘自《游戏编程模式》。
因为我们的观察者模式将会用到单例,所以创建Singleton.cs脚本,实现单例泛型。
因为Unity是单线程,所以不必加锁,代码如下:
public class Singleton where T : class, new()
{
private static T _instance;
public static T Instance
{
get
{
if (_instance == null)
{
_instance = new T();
}
return _instance;
}
}
}
1.首先我们需要定义委托public delegate void OnEventHandler(params object[] param);
2.定义一个字典private Dictionary
3.分别创建AddEventListener(string key, OnEventHandler handler)、RemoveEventListener(string key, OnEventHandler handler)、Dispatch(string key, params object[] param)方法。依次为:添加监听、移出监听、派发消息。
4.代码如下:
using System.Collections.Generic;
public class EventDispatcher:Singleton
{
public delegate void OnEventHandler(params object[] param);
private Dictionary> m_Dic = new Dictionary>();
///
/// 添加监听
///
///
///
public void AddEventListener(string key, OnEventHandler handler)
{
//当前若存在key值对应的委托列表
if (m_Dic.ContainsKey(key))
{
m_Dic[key].Add(handler);
}
else
{
List listHandler = new List();
listHandler.Add(handler);
m_Dic.Add(key, listHandler);
}
}
///
/// 移出监听
///
///
///
public void RemoveEventListener(string key, OnEventHandler handler)
{
if (m_Dic.ContainsKey(key))
{
m_Dic[key].Remove(handler);
if (m_Dic[key].Count == 0)
{
m_Dic.Remove(key);
}
}
}
///
/// 派送消息
///
///
///
public void Dispatch(string key, params object[] param)
{
//若包含
if (m_Dic.ContainsKey(key))
{
List listHandler = m_Dic[key];
//遍历 该key值对应的所有委托
for (int i = 0; i < listHandler.Count; ++i)
{
if (listHandler[i] != null)
{
listHandler[i].Invoke(param);
}
}
}
}
}
1.脚本如下:
public class Events
{
public static string PlayerAttacked = "PlayerAttacked";
public static string PlayerChangeHp = "PlayerChangeHp";
}
1.当角色受到攻击,发送消息给Player.cs脚本。
2.注意GameController.cs脚本下的PlayerAttacked(int hp);
3.代码如下:
sing UnityEngine;
public class GameController : MonoBehaviour
{
public static GameController Instance;
private void Awake()
{
Instance = this;
}
private void Start()
{
}
private void Update()
{
//按下数字键 1 减血
if (Input.GetKeyDown(KeyCode.Alpha1))
{
if (!Player.Instance.PlayerDead())
{
PlayerAttacked(-20);
}
}
//按下数字键 2 加血
if (Input.GetKeyDown(KeyCode.Alpha2))
{
if (!Player.Instance.PlayerFullBlood())
{
PlayerAttacked(20);
}
}
}
///
/// 角色被攻击
///
private void PlayerAttacked(float hp)
{
//发送消息 角色被攻击
EventDispatcher.Instance.Dispatch(Events.PlayerAttacked, hp);
}
}
1.监听Gamecontroller.cs发送的消息
2.发送消息给所有与血量相关的对象(发送消息给所有监听player的血量的对象)
3.代码如下:
using UnityEngine;
public class Player : MonoBehaviour
{
public static Player Instance;
///
/// 满血血量
///
public float Max_Hp;
///
/// 当前血量
///
public float Current_Hp;
private void Awake()
{
Instance = this;
}
private void Start()
{
EventDispatcher.Instance.AddEventListener(Events.PlayerAttacked, OnPlayerAttacked);
}
private void Destroy()
{
EventDispatcher.Instance.RemoveEventListener(Events.PlayerAttacked, OnPlayerAttacked);
}
///
/// 角色被攻击回调
///
///
private void OnPlayerAttacked(object[] param)
{
float hp = (float)param[0];
Current_Hp += hp;
float b = Current_Hp / Max_Hp;
EventDispatcher.Instance.Dispatch(Events.PlayerChangeHp, Current_Hp, b);
}
///
/// 角色初始化信息
///
public void Init()
{
Max_Hp = 100;
Current_Hp = Max_Hp;
}
///
/// 判断当前角色是否死亡
///
///
public bool PlayerDead()
{
if (Current_Hp <= 0)
{
Current_Hp = 0;
return true;
}
return false;
}
///
/// 判断当前角色是否满血
///
///
public bool PlayerFullBlood()
{
if (Current_Hp >= Max_Hp)
{
Current_Hp = Max_Hp;
return true;
}
return false;
}
}
1.修改这三个脚本,让他们分别监听Player.cs发送过来的消息。
2.代码如下:
using UnityEngine;
using UnityEngine.UI;
public class UIRoot01 : MonoBehaviour
{
private Image m_HpImage;
private void Awake()
{
m_HpImage = transform.Find("Canvas/HP_Image/Image").GetComponent();
}
private void Start()
{
EventDispatcher.Instance.AddEventListener(Events.PlayerChangeHp, OnPlayerChangeHp);
}
private void Destroy()
{
EventDispatcher.Instance.RemoveEventListener(Events.PlayerChangeHp, OnPlayerChangeHp);
}
private void OnPlayerChangeHp(object[] param)
{
float b = (float)param[1];
m_HpImage.transform.localScale = new Vector3(b, 1.0f, 1.0f);
}
}
using System.Text;
using UnityEngine;
using UnityEngine.UI;
public class UIRoot02 : MonoBehaviour
{
private Text m_Text;
private void Awake()
{
m_Text = transform.Find("Canvas/PlayerInfo/Text").GetComponent();
}
private void Start()
{
EventDispatcher.Instance.AddEventListener(Events.PlayerChangeHp, OnPlayerChangeHp);
}
private void Destroy()
{
EventDispatcher.Instance.RemoveEventListener(Events.PlayerChangeHp, OnPlayerChangeHp);
}
public void OnPlayerChangeHp(params object[] param)
{
StringBuilder sb = new StringBuilder();
sb.Append("当前血量:");
sb.Append(param[0].ToString());
m_Text.text = sb.ToString();
}
}
using UnityEngine;
using UnityEngine.UI;
public class UIPlayer : MonoBehaviour
{
private Image m_HpImage;
private void Awake()
{
m_HpImage = transform.Find("Canvas/HP_Image/Image").GetComponent();
}
private void Start()
{
EventDispatcher.Instance.AddEventListener(Events.PlayerChangeHp, OnPlayerChangeHp);
}
private void Destroy()
{
EventDispatcher.Instance.RemoveEventListener(Events.PlayerChangeHp, OnPlayerChangeHp);
}
public void OnPlayerChangeHp(params object[] param)
{
float b = (float)param[1];
m_HpImage.transform.localScale = new Vector3(b, 1.0f, 1.0f);
}
}
1.使用方法特别的简单,只需要保留Events.cs和EventDispatcher.cs脚本。
2.当我们在游戏中,假如某个状态值改变,就马上发送消息:EventDispatcher.Instance.Dispatch(Events.key,需要传递的值);
3.而监听这个值的对象,在他们的Start方法中,监听消息:EventDispatcher.Instance.AddEventListener(Events.key, 方法回调);
4.在他们的Destroy方法中,移除监听:EventDispatcher.Instance.AddEventListener(Events.key, 方法回调);
4.在“方法回调”里,修改我们的值。
这是一种不太完善的使用方式,因为我们应该将view和Model区分开来,也就是说,这种模式常常被用于MVC(Model-View-Controller)框架的开发的应用中。
在接下来的博客中,我将逐步的去完成一个基于MVC架构的UI框架,这将会使用到我们的观察者模式。