基本原理
在事件传递的过程中设置一个中间对象,负责管理监听者和发送者,以达到解耦合的目的。
首先我们定义一个方法来注册监听对象,该对象包含节点(即作用域)、事件名和事件函数,然后我们在事件触发的时候调用触发函数,来触发所有监听该事件的对象。触发函数包含事件名和参数。
此处我们以字典 _eventDictionary 来存储监听对象,以事件名作为字典的key,在字典里我们以作用域为key定义另一个字典,防止同名事件冲突以及同作用域事件重复添加。
同时,由于我们需要一个清除节点上所有事件的方法,因此我们单独设置一个字典 _eventNode 用来保存节点和事件的关系。在清除节点上所有事件的时候只需要遍历该字典 _eventNode 中对应作用域(传入的node)的事件并且从事件字典 _eventDictionary 中查找对应的事件对象及其作用域即可。
具体的调用过程如下图所示:
这就是最基本的事件系统。
粘性通知
为了防止部分情况中出现通知不到位的情况(即先通知后监听),这里我们引入了粘性通知的功能。
此处我们定义一个字典 _stickyDic 来保存通知的内容,这里我们分为两种情况,一种情况是该粘性通知只需要通知一次,则我们只需要让新的通知覆盖旧的通知;另一种是需要多次通知,则我们设置一个数组来保存多次通知的内容,所以我们增设一个字典 _stickyArrayFlag 用于保存粘性通知类型。
粘性通知和普通通知的触发事件逻辑相似,可以说前半部分的代码和普通通知一样,只不过我们增设一个通知标识符,用于判断是否通知到位,若没有通知到位,则进入粘性通知内容保存的流程。
注册监听者的部分代码也有改动,在注册完成后,若从粘性通知字典 _stickyDic 取到对应事件名的内容,则立刻进行一次通知,通知结束后删除对应的内容。
简单来说,粘性通知就是在监听和通知这两个过程之后增加一个判断,对于监听者来说,就是要判断有无通知的内容,而对于通知来说,就是判断是否有监听者监听过该通知内容。
代码
事件数据类
using System;
using System.Collections;
namespace Game
{
public class EventData
{
private Action<ArrayList> _event = null;
private string _eventName = string.Empty;
public EventData(string eventName, Action<ArrayList> eventCallback) {
_eventName = eventName;
_event = eventCallback;
}
public void SetEventCallBack(Action<ArrayList> eventCallback)
{
_event = eventCallback;
}
public void RemoveEvent()
{
_event = null;
}
public void Triggered(ArrayList obj)
{
if (_event != null)
{
_event.Invoke(obj);
}
}
}
}
事件管理类
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Game
{
public class EventManager
{
/// <summary>
/// 事件字典
/// </summary>
private static Dictionary<string, Dictionary<string, EventData>> _eventDictionary = new Dictionary<string, Dictionary<string, EventData>>();
/// <summary>
/// 节点字典
/// </summary>
private static Dictionary<string, Dictionary<string, bool>> _eventNode = new Dictionary<string, Dictionary<string, bool>>();
/// <summary>
/// 粘性通知字典
/// </summary>
private static Dictionary<string, ArrayList> _stickyDic = new Dictionary<string, ArrayList>();
/// <summary>
/// 存储当前事件的粘性通知是否为队列通知
/// </summary>
private static Dictionary<string, bool> _stickyArrayFlag = new Dictionary<string, bool>();
/// <summary>
/// 在需要监听某个事件的脚本中,调用这个方法来监听这个事件
/// </summary>
/// <param name = "id">当前节点id</param>
/// <param name="eventName">事件名</param>
/// <param name"Action">注册监听的函数</param>
public static void AddListening(string id, string eventName, Action<ArrayList> action)
{
EventData eventData = null;
if (!_eventDictionary.ContainsKey(eventName))
{
_eventDictionary.Add(eventName, new Dictionary<string, EventData>());
}
Dictionary<string, EventData> eventDic = _eventDictionary[eventName];
eventDic.TryGetValue(id, out eventData);
if (eventData != null)
{
eventData.SetEventCallBack(action);
}
else
{
eventData = new EventData(eventName, action);
eventDic.Add(id, eventData);
}
if (_eventNode.ContainsKey(id))
{
// _eventNode[id][eventName] = true;
}
else
{
Dictionary<string, bool> dicNode = new Dictionary<string, bool>
{
{ id, true }
};
_eventNode.Add(id, dicNode);
}
//触发粘性通知
ArrayList stickyArray;
_stickyDic.TryGetValue(eventName, out stickyArray);
if (stickyArray != null)
{
//有粘性通知的情况下一定能获取到是否为通知队列
bool isArray = _stickyArrayFlag[eventName];
if (isArray)
{
for (int i = 0, len = stickyArray.Count; i < len; i++)
{
TriggerEvent(eventName, stickyArray[i] as ArrayList);
}
}
else
{
TriggerEvent(eventName, stickyArray);
}
//完成通知,移除数据
_stickyArrayFlag.Remove(eventName);
_stickyDic.Remove(eventName);
}
}
/// <summary>
/// 在不需要监听的时候停止监听
/// </summary>
/// <param name = "id">当前节点id</param>
/// <param name="eventName">事件名</param>
public static void RemoveListening(string id, string eventName)
{
EventData eventData = null;
Dictionary<string, EventData> eventDic = null;
_eventDictionary.TryGetValue(eventName, out eventDic);
if (eventDic != null)
{
eventDic.TryGetValue(id, out eventData);
if (eventData != null)
{
eventData.RemoveEvent();
//移除事件字典中的事件对应的作用域
eventDic.Remove(id);
//如果事件字典中事件的作用域为0 则移除该事件
if (eventDic.Count <= 0)
{
_eventDictionary.Remove(eventName);
}
//如果有事件存在,则一定在对应的node字典中存在
_eventNode[id].Remove(eventName);
Debug.Log("移除事件" + id + eventName);
}
}
}
/// <summary>
/// 移除节点上的所有事件
/// </summary>
/// <param name="id">节点</param>
public static void RemoveAll(string id)
{
if (_eventNode.ContainsKey(id))
{
foreach (var data in _eventNode[id])
{
Dictionary<string, EventData> eventDic = null;
_eventDictionary.TryGetValue(data.Key, out eventDic);
if (eventDic != null)
{
EventData eventData = null;
eventDic.TryGetValue(id, out eventData);
if (eventData != null)
{
//移除事件
eventData.RemoveEvent();
//移除事件字典中的事件对应的作用域
eventDic.Remove(id);
//如果事件字典中事件的作用域为0 则移除该事件
if (eventDic.Count <= 0)
{
_eventDictionary.Remove(data.Key);
}
}
}
}
//移除节点字典中的对应节点数据
_eventNode.Remove(id);
}
}
/// <summary>
/// 触发某个事件
/// </summary>
/// <param name="eventName">事件名</param>
/// <param name="obj">参数列表,可以为空,但是记得在回调函数里面对该参数进行判空处理</param>
public static void TriggerEvent(string eventName, ArrayList obj)
{
if (_eventDictionary.ContainsKey(eventName))
{
Dictionary<string, EventData> eventDic = null;
_eventDictionary.TryGetValue(eventName, out eventDic);
if (eventDic != null)
{
EventData triggerEvent = null;
foreach (var data in eventDic)
{
triggerEvent = data.Value;
triggerEvent.Triggered(obj);
}
}
}
}
/// <summary>
/// 粘性通知
/// </summary>
/// <param name="eventName">通知名</param>
/// <param name="obj">数据</param>
/// <param name="isArray">是否队列</param>
public static void TriggerEventSticky(string eventName, ArrayList obj, bool isArray = false)
{
if (_eventDictionary.ContainsKey(eventName))
{
Dictionary<string, EventData> eventDic = null;
_eventDictionary.TryGetValue(eventName, out eventDic);
if (eventDic != null)
{
EventData triggerEvent = null;
foreach (var data in eventDic)
{
triggerEvent = data.Value;
triggerEvent.Triggered(obj);
}
}
}
else
{
//保存通知内容
bool arrayFlag;
_stickyArrayFlag.TryGetValue(eventName, out arrayFlag);
if (isArray)
{
arrayFlag = true;
ArrayList res;
_stickyDic.TryGetValue(eventName, out res);
if (res == null)
{
res = new ArrayList();
_stickyDic.Add(eventName, res);
_stickyArrayFlag.Add(eventName, arrayFlag);
}
else
{
_stickyDic[eventName] = res;
_stickyArrayFlag[eventName] = arrayFlag;
}
res.Add(obj);
}
else
{
arrayFlag = false;
_stickyDic.Add(eventName, obj);
if (arrayFlag)
{
_stickyArrayFlag[eventName] = arrayFlag;
}
else
{
_stickyArrayFlag.Add(eventName, arrayFlag);
}
}
}
}
}
}