基于委托封装的 消息机制

 参考至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

 

posted @ 2015-12-01 12:29  bambom  阅读(348)  评论(0编辑  收藏  举报