Cocos2dx

cocos2dx 3.10 事件机制

2018-06-09  本文已影响0人  凉拌姨妈好吃

cocos2dx的事件机制里存在三类:Event、EventListener、EventDispatcher
先理解一下它们之间的关系
当我们按下按钮时(Event),会触发一个特定的事件(EventListener相当于回调函数),而这个特定的事件又存储在EventDispatcher里,可能按下这个按钮会触发多个事件,而事件的先后就是靠EventDispatcher来决定的。

Event的相关类

Event(基类), EventCustom(自定义事件), EventTouch(触摸事件), EventMouse(鼠标事件), EventKeyboard(键盘事件), EventFocus(控件获取焦点事件), EventAcceleration(加速计事件)


1. Event

1.1 事件是什么

当出现来自鼠标,键盘,触屏,摇杆等输入源的输入时,这个事实称之为事件

1.2 cocos2dx是如何处理事件

引擎无时无刻都在感受事件。

在循环中每一帧会调用pollEvents来检测外部事件,一旦有事件发生,就会调用EventDispatcher::dispatchEvent(Event* event),来判断事件是否需要处理,给谁处理的后续问题。

1.3 源码分析

Event

enum class Type    
   {    
       TOUCH,    
       KEYBOARD,    
       ACCELERATION,    
       MOUSE,    
       FOCUS,    
       CUSTOM    
   };    
   Type _type;     ///< Event type    
       
   bool _isStopped;       ///< whether the event has been stopped.    
   Node* _currentTarget;   

可以看出Event主要包含三个变量,一个是事件类型_type(也就是定义的枚举类型:触摸、键盘等),isStopped判断事件是否停止,只要事件停止,其相关的Listener都要停止callback调用。

EventTouch
它对应于四种触摸操作,不同的EventCode可以告诉Listener来调用不同的callback。

enum class EventCode    
   {    
       BEGAN,    
       MOVED,    
       ENDED,    
       CANCELLED    
   };    

EventCustom
它是用户自定义事件,userData记录用户自定义数据,另一个eventName是用户给事件取的别名

void* _userData;       ///< User data    
std::string _eventName;    

2. EventListener

std::function<void(Event*)> _onEvent;   /// Event callback function    
Type _type;    /// Event listener type    
ListenerID _listenerID;   /// Event listener ID    
bool _isRegistered;  /// Whether the listener has been added to dispatcher.    
int   _fixedPriority;   // The higher the number, the higher the priority, 0 is for scene graph base priority.    
Node* _node;            // scene graph based priority    
bool _paused;           // Whether the listener is paused    
bool _isEnabled;        // Whether the listener is enabled    

上面的源码都有英文注释,我就不多解释了,我只说一个最重要的_isRegistered,它判断事件有没有被注册,如果没有被注册就不会触发。(如何注册事件?将事件加入dispatcher)

3. EventDispatch

在讲它之前,我们先了解一下它的一个重要变量。

std::vector<EventListener*>* _fixedListeners;  
std::vector<EventListener*>* _sceneGraphListeners;   

sceneGraphListeners:一个事件(比如说触摸事件),需要按照一定的响应序列,依次对这些Node进行事件响应,所以该类型的事件都会绑定一个与此相关联的node,并且响应顺序是与node在scene下的zorder相关的。该类型下的事件优先级统一为0。(与渲染树有关)

fixedListeners:优先级根据 fixedPriority 的数值从小往大排序、

3.1 事件是如何分发的(非触摸事件)
  1. 先获取事件的监听ID
  2. 根据这个监听ID对这个事件的所有监听器进行排序
    先分发事件到 fixedPriority < 0 的监听器中,然后再分发到 = 0 的监听器(SceneGraphPriority)中,最后在分发到 > 0 的监听器中,如果中途出现 onEvent 返回为 true 的结果,则终止分发
  3. 获取事件的所有监听器,根据排序一个一个触发回调函数
  4. 如果对当前监听ID,新增加或删除监听器,那么DirtyFlag就会标记为true,该监听ID就需要重新进行排序。如果当时正在分发事件,会把当前需要添加的监听器添加到待添加向量(_toAddedListeners)中,在事件分发完毕之后监听器需要从toAddedListeners中转移到正式向量中。
void EventDispatcher::dispatchEvent(Event* event)
{
    ...
    // 先通过event获取到事件的标志ListenerID
    auto listenerID = __getListenerID(event);
    // 排序此事件的所有的监听器
    sortEventListeners(listenerID);
    // 分发事件逻辑的函数指针
    auto pfnDispatchEventToListeners = &EventDispatcher::dispatchEventToListeners;
    if (event->getType() == Event::Type::MOUSE) {
        // 如果是鼠标事件重新赋值分发事件的函数指针
        pfnDispatchEventToListeners = &EventDispatcher::dispatchTouchEventToListeners;
    }
    // 获取改事件的所有的监听器
    auto iter = _listenerMap.find(listenerID);
    if (iter != _listenerMap.end())
    {
        // 如果有,取出里面监听器的Vector
        auto listeners = iter->second;
        // 找到对应的监听器的时候会触发的回调函数
        auto onEvent = [&event](EventListener* listener) -> bool{
            event->setCurrentTarget(listener->getAssociatedNode());
            // 触发onEvent回调
            listener->_onEvent(event);
            return event->isStopped();
        };
        // 调用函数指针分发事件
        (this->*pfnDispatchEventToListeners)(listeners, onEvent);
    }
 
    ...
}
3.2 dispatchTouchEvent(触摸事件的分发机制)
  1. listener根据Node的globlal ZOrder优先级排序后,依次响应。
  2. 当进入TouchEvent Began后,所有监听事件都会依次影响Touch Began。然后再依次响应Touch Move等,而不是一个一个由Began->Move->End
3.3 cocos监听事件的bug

只要出现了删除,修改,添加监听器的时候,监听器列表需要重新排序,都需要设置相应的 DirtyFlag 操作。但是 Cocos-2dx v3.10 里面的 updateListeners 函数有删除监听器的操作,然而并没有设置相应的 DirtyFlag 操作。
会抛出下面的异常

CCASSERT(listeners->getGt0Index() <= static_cast<ssize_t>(fixedPriorityListeners->size()), "Out of range exception!");

Gt0Index() 方法其实就是获取到当前监听器中 fixedPriority == 0 的监听器在监听器向量中的位置,它只有在给 Listener 排序的时候会设置,但是如果更新了对应 ListenerID 的向量(EventListenerVector),但是没有重新排序,就会出现 _gt0Index 未及时更新的情况,导致抛出这个异常。

引用:
Cocos2dx游戏引擎(3.x)----新的事件分发机制
cocos2dx之event事件(一)
cocos2dx之event事件(三):事件分发器EventDispatcher
Cocos2dx-v3.10 事件分发机制源码解析

上一篇 下一篇

猜你喜欢

热点阅读