游戏开放中我经常遇到这样的问题,游戏中发生一个事件,我们如何把这个事件通知给其他的游戏物体,比如游戏中发生了爆炸,我们需要在一定范围内的GameObject都能感知到这一事件。
有一种方法是将对该事件感兴趣的GameObject当做成员变量保存在触发该事件的GameObject的脚本组件里,那么我们把发生事件的GameObject当作Subject(被观察者),把对该事件感兴趣的GameObject当作Observer(观察者)。但这样做有个明显的缺点一旦增加一个Observer就需要修改Subject的代码,如果Observer移除了,还必须从Subject中移除这个成员变量。
所以我们引入c#内置的一个非常棒的事件/委托机制,这可以让我们非常方便的使用观察者模式。
常见的委托类型
// 该委托不传任何参数
public delegate void CallFunc();
// 该委托会传入发生事件的GameObject,即sender
public delegate void CallFuncO(GameObject sender);
// 该委托会传入发生事件的GameObject,即sender。和一个变长参数列表
public delegate void CallFuncOP(GameObject sender, EventArgs args);
我们下面要做的处理其实就是省略了对委托命名的过程并且将所有委托事件集中管理起来。通过字典的键值来寻找对应事件。首先,我们确定,基于消息机制的默认设定是消息发送是单向的,不存在返回值。所以我们就可以省略了有返回值的委托声明。然后,对于函数,一般分为有参和无参。所以,首先我们需要声明两个形式的委托。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//有参数
public delegate void NotificationDelegate(GameObject eventArgs);
//无参数
public delegate void NotificationNoArgDelegate();
public class DelegateMessageCenter : MonoBehaviour {
public static DelegateMessageCenter Instance = null;
private Dictionary<string, NotificationDelegate> messageListeners = new Dictionary<string, NotificationDelegate>();//有参数
private Dictionary<string, NotificationNoArgDelegate> messageNoArgsListeners = new Dictionary<string, NotificationNoArgDelegate>();//无参数
private void Awake()
{
Instance = this;
}
/// <summary>
/// 注册 有参数函数
/// </summary>
/// <param name="id">事件id</param>
/// <param name="notificationDelegate">有参数事件委托</param>
public void RegisterEvent(string id, NotificationDelegate notificationDelegate)
{
if (!messageListeners.ContainsKey(id)) messageListeners.Add(id, notificationDelegate);
else
{
NotificationDelegate notification = messageListeners[id];
notification += notificationDelegate;
}
}
/// <summary>
/// 注册委托 无参数函数
/// </summary>
/// <param name="id">事件id</param>
/// <param name="notificationNoArgsDelegate">无参数事件委托</param>
public void RegisterEvent(string id, NotificationNoArgDelegate notificationNoArgsDelegate)
{
if (!messageNoArgsListeners.ContainsKey(id)) messageNoArgsListeners.Add(id, notificationNoArgsDelegate);
else
{
NotificationNoArgDelegate notification = messageNoArgsListeners[id];
notification += notificationNoArgsDelegate;
}
}
/// <summary>
/// 移除监听
/// </summary>
/// <param name="id">事件id</param>
/// <param name="notificationDelegate">有参数事件委托</param>
public void RemoveEvent(string id, NotificationDelegate notificationDelegate)
{
if (messageListeners.ContainsKey(id))
{
NotificationDelegate notification = messageListeners[id];
notification -= notificationDelegate;
if (notification == null) messageListeners.Remove(id);
}
}
/// <summary>
/// 移除监听
/// </summary>
/// <param name="id">事件id</param>
/// <param name="notificationDelegate">无参数委托事件</param>
public void RemoveEvent(string id, NotificationNoArgDelegate notificationNoArgsDelegate)
{
if (messageListeners.ContainsKey(id))
{
NotificationNoArgDelegate notification = messageNoArgsListeners[id];
notification -= notificationNoArgsDelegate;
if (notification == null) messageNoArgsListeners.Remove(id);
}
}
/// <summary>
///消息事件分发
/// </summary>
/// <param name="id">事件ID</param>
/// <param name="eventArgs">事件参数</param>
public void DispatchMessage(string id, GameObject eventArgs)
{
if (!messageListeners.ContainsKey(id)) return;
messageListeners[id](eventArgs);
}
/// <summary>
///消息事件分发 无参数
/// </summary>
/// <param name="id">事件id</param>
public void DispatchMessage(string id)
{
if (!messageNoArgsListeners.ContainsKey(id)) return;
messageNoArgsListeners[id]();
}
}
上面是实现了我们核心的消息中心类。下面我们就可以在项目里应用他了。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MessageTest : MonoBehaviour {
private void Start()
{
//注册
DelegateMessageCenter.Instance.RegisterEvent("jump", Test1);
DelegateMessageCenter.Instance.RegisterEvent("jump1", Test2);
}
void Test1()
{
Debug.Log("选择了一个物体");
}
void Test2(GameObject args)
{
Debug.Log("选择了一个物体" + args.name);
}
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EventId : MonoBehaviour
{
public GameObject cube;
void Start()
{
}
// Update is called once per frame
void Update()
{
if (Input.GetMouseButtonDown(0))
{
DelegateMessageCenter.Instance.DispatchMessage("jump");
DelegateMessageCenter.Instance.DispatchMessage("jump1", cube);
}
}
}