基于委托封装的 消息机制
参考至https://theliquidfire.wordpress.com/2014/12/10/social-scripting-part-3/
要完成的目标:
1 任何对象都能发送任何消息
2 任何对象都能通过 handler 的直接引用 高效的观察到任何消息
3 消息监听者能监听一个特殊的消息发送者 或 多个
4 消息的传送可以携带一个自定义的EventArgs 参数
5 消息不用去处理,因此你不用去检查它的存在
6 处理器每个消息只会添加一次,即使你意外的添加了多次
这个机制是作者在做原生IOS开发实用 NSNotificationCenter.灵感乍现做的一个消息处理器。
创建一个单例类
public class NotificationCenter { #region 单例 public readonly static NotificationCenter instance = new NotificationCenter(); private NotificationCenter() { } #endregion
通过这种方式要保证任何时候只有一个NotificationCenter。并且在使用它的时候要保证他已经存在。如果类是继承Monobehaviour这种设计模式,那你就要注意它的实现了。在MonoBehaviour 里你不能使用构造器,而且你还要修改unity脚本的执行顺序(通过Edit->Project Setting->Script Execution)来确保其他类在使用它的时候他已经初始化创建了(这种情况也常见于 Awake 和 Start 函数的初始化使用上,Awake一般用来确保自身启动的一些指定,Start用来指定组件的赋值). 另一个方法就是在 创建的场景里创建一个物体挂上这个脚本,并且使用DontDestroyOnLoad这个函数,这样就一直能使用了,并且在创建场景的时候它就已经初始化了。
消息中心本质上来说就是一个匹配 int(消息名) 和 另一个表 匹配 object(消息发送者) 和 事件处理(消息接受者)的 队列 的一张大表。为了代码的简洁性可以使用一种简介的结构声明方式:
//相当于一个 类型,定义了它的结构 就是一个符合的字典,在实例它的时候 就比较简洁 using SenderTable=System.Collections.Generic.Dictionary<System.Object,System.Collections.Generic.List<System.EventHandler>>;
在引用部分如此引用就犹如定义了一个类,只是它的结构就像是继承与Dictionary的。那么在代码里就可以使用这个结构了:
所要做的就是对这张表的 的维护了。定义一个 消息发收 列表 的获取函数private SenderTable GetSenderTable(int notificationName), 以及事件处理队列的获取函数 private List<EventHandler> GetObservers(SenderTable subTable,objcet sender)
#region 消息列表获取 private /// <summary> /// string 是消息 object 是消息的发送者,List是消息接受者, 一个消息可以对应 对一个 消息发送者 ,一个发送者可以有多个接受者 /// </summary> private Dictionary<int , SenderTable> _table = new Dictionary<int , SenderTable>(); /// <summary> /// 获取一个消息 的事件处理列表(发送者 sender -- observer接受者( 这里体现的就是EventHandler 的委托事件 )) /// </summary> /// <param name="notifyID"></param> /// <returns></returns> private SenderTable GetSenderTable(int notificationName) { if (!_table.ContainsKey(notificationName)) _table.Add(notificationName , new SenderTable()); return _table[notificationName]; } /// <summary> /// 获取消息的事件处理 /// </summary> /// <param name="subTable"></param> /// <param name="sender"></param> /// <returns></returns> private List<EventHandler> GetObservers(SenderTable subTable , System.Object sender) { if (!subTable.ContainsKey(sender)) subTable.Add(sender , new List<EventHandler>()); return subTable[sender]; } #endregion
接下就是给消息中心添加 注册消息监听的功能了,也就是添加观察者。最少我们需要知道给 观察者消息的名字,这样在一个消息实际发送的时候就能处理注册的 EventHandler 委托指向的事件处理了。
也会重载一个函数 使用一个额外的参数,以便使用者仅仅只想处理一个指定的对象发送的消息。
#region 注册事件 public /// <summary> /// 添加观察者,注册事件 /// </summary> /// <param name="handler"></param> /// <param name="notificationName"></param> public void AddObserver(EventHandler handler , int notificationName) { AddObserver(handler , notificationName , null); } public void AddObserver(EventHandler handler , int notificationName , System.Object sender) { if (handler == null) { Debug.LogError("Can't add a null event handler for notification, " + notificationName); return; } //if (string.IsNullOrEmpty(notificationName)) //{ // Debug.LogError("Can't observe an unnamed notification"); // return; //} SenderTable subTable = GetSenderTable(notificationName); System.Object key = (sender != null) ? sender : this; List<EventHandler> list = GetObservers(subTable , key); if (!list.Contains(handler)) list.Add(handler); } #endregion
要注意的是 SenderTable是基于以一个objct作为key,所以一定要存在一个sender,即使发送消息的使用者(发送消息的类) 没有提供,那也要加一个发送者,就是这个类本生。所以没有指定消息发送者的注册,会把使用它的类的实例当成sender。
消息移除函数同样也需要重载,技术上来说只需要你想要移除的 处理参数( 注册的EvnetHandler的那个函数),也可以添加上你要移除的事件名,和你正在观察的发送者。当然 你也可以注册相同的事件处理在不同的 消息 和 sender上, the other options provide a simple means of unregistering all at once.。
#region 移除消息 public public void RemoveObserver(EventHandler handler) { int[] keys = new int[_table.Keys.Count]; _table.Keys.CopyTo(keys , 0); for (int i = keys.Length - 1 ; i >= 0 ; --i) RemoveObserver(handler , keys[i]); } public void RemoveObserver(EventHandler handler , int notificationName) { if (handler == null) { Debug.LogError("Can't remove a null event handler from notification"); return; } //if (string.IsNullOrEmpty(notificationName)) //{ // Debug.LogError("A notification name is required to stop observation"); // return; //} // No need to take action if we dont monitor this notification if (!_table.ContainsKey(notificationName)) return; System.Object[] keys = new object[_table[notificationName].Keys.Count]; _table[notificationName].Keys.CopyTo(keys , 0); for (int i = keys.Length - 1 ; i >= 0 ; --i) RemoveObserver(handler , notificationName , keys[i]); } public void RemoveObserver(EventHandler handler , int notificationName , System.Object sender) { //if (string.IsNullOrEmpty(notificationName)) //{ // Debug.LogError("A notification name is required to stop observation"); // return; //} // No need to take action if we dont monitor this notification if (!_table.ContainsKey(notificationName)) return; SenderTable subTable = GetSenderTable(notificationName); System.Object key = (sender != null) ? sender : this; if (!subTable.ContainsKey(key)) return; List<EventHandler> list = GetObservers(subTable , key); for (int i = list.Count - 1 ; i >= 0 ; --i) { if (list[i] == handler) { list.RemoveAt(i); break; } } if (list.Count == 0) { subTable.Remove(key); if (subTable.Count == 0) _table.Remove(notificationName); } } #endregion
要注意的是 这里不可以使用foreach变量table,因为要对它进行一些处理在遍历的时候。 无论什么时候当列表中最后一个事件处理委托为空的时候,它的实体也在消息中心也会被移除(sender),没有观察者了
最后 还需要能实际发送消息的功能:
#region 消息发送 public public void PostNotification(int notificationName) { PostNotification(notificationName , null); } public void PostNotification(int notificationName , System.Object sender) { PostNotification(notificationName , sender , EventArgs.Empty); } public void PostNotification(int notificationName , System.Object sender , EventArgs e) { //if (string.IsNullOrEmpty(notificationName)) //{ // Debug.LogError("A notification name is required to stop observation"); // return; //} // No need to take action if we dont monitor this notification if (!_table.ContainsKey(notificationName)) return; // Post to subscribers who specified a sender to observe SenderTable subTable = GetSenderTable(notificationName); if (sender != null && subTable.ContainsKey(sender)) { List<EventHandler> handlers = GetObservers(subTable , sender); for (int i = handlers.Count - 1 ; i >= 0 ; --i) handlers[i](sender , e); } // Post to subscribers who did not specify a sender to observe if (subTable.ContainsKey(this)) { List<EventHandler> handlers = GetObservers(subTable , this); for (int i = handlers.Count - 1 ; i >= 0 ; --i) handlers[i](sender , e); } } #endregion
最后 为了减少代码间的耦合性,使用拓展方法,将注册、移除、发送的接口封装成脚本原生方法
/// <summary> /// 两个好处 : /// 一是使得代码更简洁 /// 而是接口集中化 到后期要更改或移除时很方便就能找到引用 /// </summary> public static class ObjectExtensions { public static void PostNotification(this object obj , int notificationName) { NotificationCenter.instance.PostNotification(notificationName , obj); } public static void PostNotification(this object obj , int notificationName , EventArgs e) { NotificationCenter.instance.PostNotification(notificationName , obj , e); } public static void AddObserver(this object obj , EventHandler handler , int notificationName) { NotificationCenter.instance.AddObserver(handler , notificationName); } public static void AddObserver(this object obj , EventHandler handler , int notificationName , object sender) { NotificationCenter.instance.AddObserver(handler , notificationName , sender); } public static void RemoveObserver(this object obj , EventHandler handler) { NotificationCenter.instance.RemoveObserver(handler); } public static void RemoveObserver(this object obj , EventHandler handler , int notificationName) { NotificationCenter.instance.RemoveObserver(handler , notificationName); } public static void RemoveObserver(this object obj , EventHandler handler , int notificationName , System.Object sender) { NotificationCenter.instance.RemoveObserver(handler , notificationName , sender); } } public class EventArgsUs:EventArgs { public object param1; public object param2; public EventArgsUs(object param1,object param2) { this.param1 = param1; this.param2 = param2; } }
最最后测试
public class BulletStatus : MonoBehaviour { public float speed = 10f; public bool isTrack = true; public float acc;//加速度 /// <summary> /// 匀速运动 /// </summary> /// <param name="up">向上的速度</param> /// <param name="horizontal">左右的速度</param> void MoveLineUp(float up) { transform.Translate(Vector2.up * Time.deltaTime * speed); } void Update() { if (isTrack) { MoveLineUp(speed); } } #region 子弹碰撞 发送事件 void OnTriggerExit2D(Collider2D other) { Debug.Log(" boundary be hit"); if (other.gameObject.tag == ColliderType.boundary.ToString()) { this.PostNotification(EventDef.BoundaryCrash , new EventArgsUs(null , null)); Debug.Log(" boundary be hit"); } } #endregion } public class BulletMgr : MonoBehaviour { public Transform StartPoint;// 机体 public float intervalTime = 1f;//发射间隔 public GameObject bullet_0; public bool isFire=true; //是否发射子弹 //发射 IEnumerator FireBullet() { yield return new WaitForSeconds(3f); while (isFire) { Poolable bullet = BulletPool.Dequeue("bullet_0"); Vector3 bulletPos = new Vector3(StartPoint.position.x , StartPoint.position.y + 1 , 0); bullet.transform.position = bulletPos; bullet.gameObject.SetActive(true); instances.Add(bullet); yield return new WaitForSeconds(intervalTime); } } Vector2 start = new Vector2(); void Start () { //TODO 先更具数据 加载 对象池的预制体 ,都有一个估计范围的 BulletPool.AddEntry("bullet_0" , bullet_0 , 10 , 30); StartPoint = transform; start = StartPoint.position; StartCoroutine(FireBullet()); } void Update () { } #region 消息管理 void OnEnable() { this.AddObserver(OnEvent , EventDef.BoundaryCrash); } void OnDisable() { this.RemoveObserver(OnEvent , EventDef.BoundaryCrash); } List<Poolable> instances = new List<Poolable>(); //创建的 //子弹碰撞时的 处理事件 void OnEvent(object sender,EventArgs e) { if (instances.Count > 0) { Poolable obj = instances[0]; instances.RemoveAt(0); // 隐藏 对象池管理 BulletPool.Enqueue(obj); Debug.Log("solue notify!"); } } #endregion