【Qarth框架系列】事件系统

来源:互联网 发布:淘宝818会有活动吗 编辑:程序博客网 时间:2024/06/02 04:06

事件是解耦的利器,游戏开发中我们经常会碰到可以通过事件解决的问题。最常见的比如数据层的变化需要通知UI的变化,底层模块与上层模块的通信等等。

Unity引擎本身提供了一套SendMessage和ReceiveMessage的方法,但是该方法在实际项目中存在如下几个问题:

1. 必须是MonoBehaviour子类,且是场景内组件2. 基于方法名的机制,无法在根源上达到解耦的目的3. 只能是父子节点之间消息传递4. 性能问题

基于以上原因所以正式项目中一般很少使用Unity的SendMessage机制,而C#语言本身的delegate却可以很好的满足我们的需求。
Qarth中的EventSystem通过包装delegate的方式实现了简单的事件中心的功能,任何对象都可以通过简单的方法调用实现注册事件,取消注册,和消息发送的功能。EventSystem默认继承TSingleton提供全局单例访问,同时也可以通过new创建独立的EventSystem在模块内部使用。

代码示例

  • 注册事件
     private void InitEventListener()     {         EventSystem.S.Register(EventID.PasueGame, OnGamePause);     }     private void OnGamePause(int key, params object[] args)     {         SetCurrentStateByID(GameplayStateID.LevelPause);     }
  • 取消注册
     public void Stop()     {         EventSystem.S.UnRegister(EventID.PasueGame, OnGamePause);     }

Register和UnRegister方法的第一个参数都是事件ID,该ID可以是任何的枚举也可以是int值,
第二个参数是注册的回调方法,该方法的签名必须是void FuncName(int key, params object[] args)。
相同ID的事件触发后注册的方法将被调用,事件回调的方法中第一个参数是事件的ID,第二个是事件的可变参数列表。

  • 发送事件
     private void OnClickPauseButton()     {         EventSystem.S.Send(EventID.PasueGame);     }

调用Send方法发送事件,第一个参数是事件ID, 第二个参数是事件的参数列表。

注意:这里的问题在于发送方和接收方对参数的形式要统一,开发人员较少、沟通比较顺畅且项目较小的情况下这不会是问题,但团队规模和项目较大的情况下还是会存在发送和接收处理不统一的风险。

源码分析

  • ListenerWrap
    #region 事件接口    public delegate void OnEvent(int key, params object[] param);    #endregion    public class EventSystem : TSingleton<EventSystem>    {        private Dictionary<int, ListenerWrap> m_AllListenerMap = new Dictionary<int, ListenerWrap>(50);        #region 内部结构        private class ListenerWrap        {            private LinkedList<OnEvent>     m_EventList;            public bool Fire(int key, params object[] param)            {                if (m_EventList == null)                {                    return false;                }                LinkedListNode<OnEvent> next = m_EventList.First;                OnEvent call = null;                LinkedListNode<OnEvent> nextCache = null;                while (next != null)                {                    call = next.Value;                    nextCache = next.Next;                    call(key, param);                    //1.该事件的回调删除了自己OK 2.该事件的回调添加了新回调OK, 3.该事件删除了其它回调(被删除的回调可能有回调,可能没有)                    next = (next.Next == null) ? nextCache : next.Next;                }                return true;            }            public bool Add(OnEvent listener)            {                if (m_EventList == null)                {                    m_EventList = new LinkedList<OnEvent>();                }                if (m_EventList.Contains(listener))                {                    return false;                }                m_EventList.AddLast(listener);                return true;            }            public void Remove(OnEvent listener)            {                if (m_EventList == null)                {                    return;                }                m_EventList.Remove(listener);            }        }        #endregion    }

这里的核心结构是EventSystem的内部类ListenerWrap, 该内部类包含一个LinkedList<OnEvent> 类型的成员变量m_EventList, m_EventList保存着注册的某个事件ID的所有回调方法。
ListenerWrap内部类的Fire()方法是事件回调方法真正被调用的地方,通过遍历m_EventList列表中所有的保存的delegate实现回调。
注意:在ListenerWrap的Add方法中会判断同一个方法是否已经添加过,所以不存在多次注册同一个事件导致多次收到事件回调的问题。另外可以看到Add方法将最新的回调方法加在了队尾,而Fire方法则是由队头开始调用回调方法的,所以更早注册的事件回调将先被调用。
注意Fire方法注释中描述的问题2、3
2. 如果事件回调中添加该事件的另一个回调,那么新加的回调也会收到这次事件
3. 如果事件回调中取消注册了该事件的另一个回调,那么如果被取消的注册较晚,那么将无法收到这次事件

  • EventSystem
     public bool Register<T>(T key, OnEvent fun) where T : IConvertible     {         int kv = key.ToInt32(null);         ListenerWrap wrap;         if (!m_AllListenerMap.TryGetValue(kv, out wrap))         {             wrap = new ListenerWrap();             m_AllListenerMap.Add(kv, wrap);         }         if (wrap.Add(fun))         {             return true;         }         Log.w("Already Register Same Event:" + key);         return false;     }     public void UnRegister<T>(T key, OnEvent fun) where T : IConvertible     {         ListenerWrap wrap;         if (m_AllListenerMap.TryGetValue(key.ToInt32(null), out wrap))         {             wrap.Remove(fun);         }     }     public bool Send<T>(T key, params object[] param) where T : IConvertible     {         int kv = key.ToInt32(null);         ListenerWrap wrap;         if (m_AllListenerMap.TryGetValue(kv, out wrap))         {             return wrap.Fire(kv, param);         }         return false;     }

EventSystem中m_AllListenerMap则保存了ListenerWrap的集合, Register(),UnRegister(),Send()
都是泛型方法,通过IConvertible接口达到上层可以通过任意枚举类型或直接通过int调用的目的。

结语

  • 该EventSystem的实现并不包含延时事件,事件优先级功能。
  • 实际项目中并不推荐同时使用多个EventSystem。
  • EventSystem内部是使用int记录事件ID的,所以虽然可以同时使用多个不同的枚举定义事件ID,但并不能重复。
  • 框架内部使用了部分事件ID, 定义在EngineEventID中,最小值从1000000开始。
  • EventRegisterHelper提供了多个事件的注册和取消注册的封装,可以通过该类帮助解决遗漏取消注册的问题。

Qarth交流群:621838819