Cocos2d-X3.0 刨根问底七----- 事件机制Event源码分析


Cocos2d-X3.0 刨根问底(七)----- 事件机制Event源码分析

这一章,我们来分析Cocos2d-x 事件机制相关的源码, 根据Cocos2d-x的工程目录,我们可以找到所有关于事件的源码都存在放在下图所示的目录中。

从这个event_dispatcher目录中的文件命名上分析 cocos2d-x与事件相关的类一共有四种, Event,EventListener,EventDispatcher,Touch分别为 事件,事件侦听器,事件分发器,触摸

我们先从Event类开始。

打开CCEvent.h文件

/**
 *   Base class of all kinds of events.
 */
class Event : public Ref
{
:
    enum class Type
    {
        TOUCH,KEYBOARD,ACCELERATION,MOUSE,CUSTOM
    };
    
protected* Constructor */
    Event(Type type);
* Destructor */
    virtual ~Event();

    * Gets the event type 
    inline Type getType() const { return _type; };
    
    * Stops propagation for current event 
    inline void stopPropagation() { _isStopped = true; };
    
    * Checks whether the event has been stopped bool isStopped()  _isStopped; };
    
    * @brief Gets current target of the event
     *  @return The target with which the event associates.
     *  @note It onlys be available when the event listener is associated with node. 
     *        It returns 0 when the listener is associated with fixed priority.
     
    inline Node* getCurrentTarget() {  _currentTarget; };
    
* Sets current target void setCurrentTarget(Node* target) { _currentTarget = target; };
    
    Type _type;     ///< Event type
    
    bool _isStopped;       < whether the event has been stopped.
    Node* _currentTarget;  < Current target
    
    friend  EventDispatcher;
};

这个类并且不复杂,先看一下类的注释,Event类是所有事件类的基类。

类定义的最上面有一个枚举,定义了事件的类型

事件的各类分别为 ,触摸事件, 键盘事件, 加速器事件,鼠标事件, 用户自定义事件。

再看一下Event类的成员变量

_type 描述当前对象的事件类型。

_isStopped 描述当前事件是否已经停止

_currentTarget 是侦听事件的Node类型的对象

就这三个成员变量,含义很简单,Event类的几个方法也没什么特别的,就是成员变量的get set方法。

下面我们再看一下用户自定义事件 EventCustom 类的定义,来了解一下。

class EventCustom : Event { EventCustom(const std::string& eventName); * Sets user data void setUserData(void* data) { _userData = data; }; * Gets user data void* getUserData() _userData; }; * Gets event name string& getEventName() _eventName; }; void* _userData; < User data std::string _eventName; };

在自定义事件类中,多出了两个成员变量 一个是 _userData用来记录用户自定义数据,另一个_eventName用户给这个事件起的别名。

其它的关于Event的子类,大家可以自己看一下,内容都差不多。

下面我们来分析 EventListener 这个类。

这个类看着挺长,其实没什么内容,先看下类的定义。

EventListener也同样定义了一个类型

Type { UNKNOWN,TOUCH_ONE_BY_ONE,TOUCH_ALL_AT_ONCE,CUSTOM };

这个类型与Event的类型有一小点不同,就是将触摸事件类型分成了 One by One (一个接一个) 与 All At Once (同时一起)两种。

再看 EventListener 的属性

std::function<void(Event*)> _onEvent;   // 用来记录侦听器回调函数

    Type _type;                              侦听器的类型
    ListenerID _listenerID;                  侦听器的ID 其实是个字符串
    bool _isRegistered;                      标记当前侦听器是否已经加入到了事件分发器中的状态变量

    int   _fixedPriority;    侦听器的优先级别,数值越高级别越高.默认为0
    Node* _node;             场景结点(这里这个变量的作用还没能理解好,后面我们再进行分析)
    bool _paused;            标记此侦听器是否为暂停状态。
    bool _isEnabled;         标记此侦听器是否有效
上面分析了属性的功能 。EventListener的很简单,大部分都是属性的读写方法(get/set)这里就不多说了,下面我们重点看一下init方法及实现。
bool EventListener::init(Type t,const ListenerID& listenerID,255)">const std::function<void(Event*)>& callback) { _onEvent = callback; _type = t; _listenerID = listenerID; _isRegistered = false; _paused = ; _isEnabled = ; return ; }

这个init函数 也很简单,就是一些成员变量的赋值操作。

在EventListener定义中有两个纯虚函数,我们看一下。

* Checks whether the listener is available. virtual bool checkAvailable() = 0;

    * Clones the listener,its subclasses have to override this method. virtual EventListener* clone() = 0;

通过注释了解这两个函数 一个是验证listener是否有效 别一个是clone方法。

EventListener是抽象类,那么咱们找一个它的子类的具体实现。

我们看一下EventListenerCustom这个类的定义。

class EventListenerCustom : EventListener { * Creates an event listener with type and callback. * @param eventType The type of the event. * @param callback The callback function when the specified event was emitted. static EventListenerCustom* create(string& eventName,255)">void(EventCustom*)>& callback); /// Overrides bool checkAvailable() override; virtual EventListenerCustom* clone() ; CC_CONSTRUCTOR_ACCESS: EventListenerCustom(); * Initializes event with type and callback function bool init(const ListenerID& listenerId,0)"> callback); : std::function<void(EventCustom*)> _onCustomEvent; friend LuaEventListenerCustom; };

EventListenerCustom又增加了一个成员变量,_onCustomEvent 接收一个EventCustom类型的事件为参数的回调函数。

我们可以看到,这个类的构造函数不是public形式的,所以这个类不能直接被实例化,

找到了EventListenerCustom提供了一个静态函数 create 所以实例化这个在的对象一定要使用这个create方法。

我们看一下create方法。

EventListenerCustom* EventListenerCustom::create( callback)
{
    EventListenerCustom* ret = new EventListenerCustom();
    if (ret && ret->init(eventName,callback))
    {
        ret->autorelease();
    }
    else
    {
        CC_SAFE_DELETE(ret);
    }
     ret;
}

这个Create函数的结构,与Node的Create结构一样,新创建的EventListener加入到了autorelease列表里面,在Create的时候调用了init函数,我们再看一下EventListenerCustom::init方法。

bool EventListenerCustom::init( callback) { bool ret = ; _onCustomEvent = callback; auto listener = [this](Event* event){ if (_onCustomEvent != nullptr) { _onCustomEvent(static_cast<EventCustom*>()); } }; if (EventListener::init(EventListener::Type::CUSTOM,listenerId,listener)) { ret = ; } ret; }

这个函数也没什么特别的,但值得注意的是,CustomEvent的回调与基类的回调函数是怎么关联的。

在EventListenerCustom类中有一个成员变量_onCustomEvent它是一个函数指针来记录事件触发后的回调函数。

在EventListener类中也有一个_onEvent成员变量来记录事件触发的时候的回调函数。

EventListenerCustom::init函数中 有一个匿名函数,listener 在这个匿名函数中 判断了_onCustomEvent是否为空,如果不为空那么调用_onCustomEvent。

后面调用基类的init方法,把这个匿名函数传递给了基类的_onEvent,这样基类的回调函数_onEvent与子类的_onCustomEvent就关联起来了。

下面我们看一下抽象方法在子类中怎么实现的

EventListenerCustom* EventListenerCustom::clone()
{
    EventListenerCustom* ret = init(_listenerID,_onCustomEvent))
    {
        ret-> ret;
}

clone的操作与Create方法很象,其实就是重新创建了一个对象,它的_listenerID 、 _onCustomEvent值与原对象一样。返回的是新创建的对象指针。

事件类Event有了,侦听器类EventListener 有了,那么下面我们来分析事件的分发器EventDispatcher。

打开CCEventDispatcher.h文件

* This class manages event listener subscriptions and event dispatching. The EventListener list is managed in such a way that event listeners can be added and removed even from within an EventListener,while events are being dispatched. class EventDispatcher : Ref {

这个类也是Ref的子类,从注释上面我们先整体的了解EventDispatcher的功能及作用。这个类管理事件侦听脚本及事件的分发处理。有一个事件侦听器列表,来记录所有侦听的事件。分析这个类我们首先还是从这个类的属性上开始。

/** 保存所有事件侦听器的列表 std::unordered_map<EventListener::ListenerID,EventListenerVector*> _listenerMap; * 有脏标记的侦听器列表,具体什么是脏标记后面再分析。 _priorityDirtyFlagMap; * Node类型结点的事件侦听器列表 std::unordered_map<Node*,std::vector<EventListener*>*> _nodeListenersMap; * Node结点与事件级别的列表,看到这里还不太明白这个列表是什么作用,后面着重寻找答案int> _nodePriorityMap; * 记录结点的ZOrder与结点的指针列表 也不太知道这是干什么用的,后面找答案 std::unordered_map<float,std::vector<Node*>> _globalZOrderNodeMap; * 事件分必后加入的侦听器列表 std::vector<EventListener*> _toAddedListeners; * 与场景优先级侦听器相关联的node结点。 std::set<Node*> _dirtyNodes; * 描述事件分发器是否在分发事件 int _inDispatch; * 标记是否开启事件分发bool _isEnabled;
* 优先级索引*/ 
     _nodePriorityIndex;
    
    * 内部自定义侦听器索引*/ 
    std::set<std::string> _internalCustomListenerIDs;

上面针对这个类的属性做了字面上的分析,大部分属性目前还不知道具体功能,不过没关系,后面我们一个一个去破解它的含义。

现在 我们来分析一下这个类的构造函数

EventDispatcher::EventDispatcher() : _inDispatch(),_isEnabled() { _toAddedListeners.reserve(50); fixed #4129: Mark the following listener IDs for internal use. Therefore,internal listeners would not be cleaned when removeAllEventListeners is invoked. _internalCustomListenerIDs.insert(EVENT_COME_TO_FOREGROUND); _internalCustomListenerIDs.insert(EVENT_COME_TO_BACKGROUND); }

构造函数不复杂,除了初始几个变量,我们可以看到,向_internalCustomListenerIDs加入了两个自定义的事件,这里还有一行注释,说明,在清除所有事件侦听器的时候内容侦听器是不会被清除的。

我们看一下这两个内容自定义的事件,一个是程序返回到后台,一个是程序返回到前台。估计这在这两个事件里面要做一些暂停的工作。

从EventDispatcher的成员变量上看,都是围绕着侦听器列表来定义的,那么我们就看一下把侦听器加入到侦听器列表的方法。

第一个add方法

* Adds a event listener for a specified event with the priority of scene graph. * @param listener The listener of a specified event. * @param node The priority of the listener is based on the draw order of this node. * @note The priority of scene graph will be fixed value 0. So the order of listener item * in the vector will be ' <0,scene graph (0 priority),>0'. void addEventListenerWithSceneGraphPriority(EventListener* listener,Node* node);

从注释上可以知道这个方法的作用是,将一个指定的事件侦听器依照场景图的优先级顺序加入到侦听器列表里面, 这个方法与场景图的绘制顺序有关系,

场景的结点渲染顺序也就是zOrder的顺序,场景中的结点优先级一般都是0,侦听器的存放顺序就是 小于0 等于0 大于0这样一个顺序 。

我们看一下这个方法的实现

void EventDispatcher::addEventListenerWithSceneGraphPriority(EventListener* listener,Node* node) { CCASSERT(listener && node,"Invalid parameters."); CCASSERT(!listener->isRegistered(),0)">The listener has been registered.if (!listener->checkAvailable()) ; listener->setAssociatedNode(node); listener->setFixedPriority(); listener->setRegistered(); addEventListener(listener); }

这个方法内容也简单,

1. 先检查了侦听器是否有效

2. 将结点与侦听器做了关联

3. 设置优先级为0,从注释上我们已经得到这个信息了,这个方法加入的侦听器都是显示对象的,所以优先级都为0

4. 设置侦听器已经注册状态

5. 调用了addEventListener方法,将侦听器加入到EventDispatcher的侦听器列表里。

下面我们看一下addEventListener方法,了解是将侦听器加入到侦听器管理列表里的过程

void EventDispatcher::addEventListener(EventListener* listener) { if (_inDispatch == ) { forceAddEventListener(listener); } { _toAddedListeners.push_back(listener); } listener->retain(); }

这个方法判断了当前 是否在分发消息,如果没有分发消息那么就调用 forceAddEventListener 把侦听器加入到侦听器列表里面。

如果_indispatch不为0证明现在正在分发消息那么新加入的侦听器就放到了临时数组_toAddedListeners里面

不管管理器是不是在分发消息listener都有一个归宿,那么最后增加了listener一次引用计数。

下面我们看一下forceAddEventListener方法。

void EventDispatcher::forceAddEventListener(EventListener* listener) { EventListenerVector* listeners = nullptr; EventListener::ListenerID listenerID = listener->getListenerID(); auto itr = _listenerMap.find(listenerID); if (itr == _listenerMap.end()) { listeners = EventListenerVector(); _listenerMap.insert(std::make_pair(listenerID,listeners)); } { listeners = itr->second; } listeners->push_back(listener); if (listener->getFixedPriority() == ) { setDirty(listenerID,DirtyFlag::SCENE_GRAPH_PRIORITY); auto node = listener->getAssociatedNode(); CCASSERT(node != nullptr,0)">Invalid scene graph priority!); associateNodeAndEventListener(node,listener); if (node->isRunning()) { resumeEventListenersForTarget(node); } } { setDirty(listenerID,DirtyFlag::FIXED_PRIORITY); } }

这个类里面涉及到了一个EventDispatcher的内部类 EventListenerVector 这样一个数据结构,

这个结构在这里不多分析了,很简单,这个结构里封装了两个数组,_fixedListeners 与_sceneGraphListeners ,分别保存优先级不为0的侦听器指针与优先级为0的侦听器指针。

我们看一下强制将一个侦听器加入到管理列表的过程

  1. _listenerMap是按照侦听器ID来做分类的,每个侦听器ID都有一个EventListenerVector 数组。在_listenerMap中找 listenerID与要加入的listener相同的侦听器列表
  2. 如果没找到就他那天个listenerID为listener->getListenerID();项加入到_listenerMap中。找到了就拿到这个ID的列表指针。
  3. 将要加入管理的侦听器放到列表中。
  4. 根据加入的侦听器的优先级别是不是0进行设置脏标记操作。
  5. 当优先级标记为0时肯定这个侦听器是与场景显示对象对象绑定的,找到这个绑定的Node对象与listener做了关联,调用了associateNodeAndEventListener方法,将结点与侦听器加入到了_nodeListenersMap列表里面。
  6. 因为侦听器有了增加,所以原侦听器列表就不是最新的了,cocos2d-x认为那就是脏数据,这样设置了关于这个侦听器ID的脏标记。

通过上述分析,我们可以进一步理解到EventDispatcher类内的几个侦听器列表变量的作用。

_listenerMap 用以侦听器类型(就是侦听器的ID)索引,值是一个数组,用来储存侦听同一侦听器ID的所有侦听器对象。

_priorityDirtyFlagMap 用来标记一类ID的侦听器列表是对象是否有变化,侦是侦听器ID,值为侦听级别。

_nodeListenersMap 用来记录结点类型数据的侦听器列表,通俗点说就是以结点为索引所有侦听的事件都存在这个map里面。

我们注意这里判断了node->isRunning()属性如果结点是在运行的结点,那么调用了resumeEventListenersForTarget方法。下面看下这个方法都做了些什么。

void EventDispatcher::resumeEventListenersForTarget(Node* target,255)">bool recursive = false ) { auto listenerIter = _nodeListenersMap.find(target); if (listenerIter != _nodeListenersMap.end()) { auto listeners = listenerIter->second; for (auto& l : *listeners) { l->setPaused(); } } setDirtyForNode(target); (recursive) { const auto& children = target->getChildren(); for (const auto& child : children) { resumeEventListenersForTarget(child,); } } }

这个函数两个参数,第一个是目标结点对象,第二个参数是是否递归进行子对象调用。

这个函数过程,先在结点列表中找是否已经有这个结点了,找到之后将它的每个侦听器的暂停状态都 取消。然后设置这个结点为脏结点标记。

上面提到过设置脏的侦听器,这里看一下设置脏结点函数。

void EventDispatcher::setDirtyForNode(Node* node) { Mark the node dirty only when there is an eventlistener associated with it. if (_nodeListenersMap.find(node) != _nodeListenersMap.end()) { _dirtyNodes.insert(node); } Also set the dirty flag for node's children const auto& children = node->getChildren(); child : children) { setDirtyForNode(child); } }

这个函数虽然没有递归参数来控制,但从实现 上来分析这经会递归 node结点的子结点,都设置成了脏结点。

这里出现了_dirtyNodes这个类成员变量,现在可以理解什么是脏结点了,就是侦听器有变化的结点。

下面我们分析EventDispatcher类的另一个加入侦听器的方法。

* Adds a event listener for a specified event with the fixed priority. * @param listener The listener of a specified event. * @param fixedPriority The fixed priority of the listener. * @note A lower priority will be called before the ones that have a higher value. * 0 priority is forbidden for fixed priority since it's used for scene graph based priority. void addEventListenerWithFixedPriority(EventListener* listener,255)">int fixedPriority);

从注释我们先来一个整体的了解。

这个函数的作用是将一个指定优先级的侦听器加入到管理列表里面。

这里强调了,0这个优先级不能被使用,因为这是显示对象侦听器优先级别。如果小于0的优先级那么这个侦听器事件会在画面渲染之前被触发,大于0的优先级会在显示对象渲染之后触发事件回调。

好了,下面看实现过程。

void EventDispatcher::addEventListenerWithFixedPriority(EventListener* listener,0)"> fixedPriority) { CCASSERT(listener,); CCASSERT(fixedPriority != 0,0)">0 priority is forbidden for fixed priority since it's used for scene graph based priority.setAssociatedNode(nullptr); listener->setFixedPriority(fixedPriority); listener->setRegistered(); listener->setPaused(); addEventListener(listener); }

与addEventListenerWithSceneGraphPriority方法大同小异,就是对listener进行了一些参数赋值,后面还是调用的addEventListener方法。这里就不多说了,值得注意的一点是,这个listener初始也是设置成暂停的,上面分析到在addEventListener调用后会将暂停状态取消的。

继续向下看,还有一个自定义事件的侦听器注册方法。

* Adds a Custom event listener. It will use a fixed priority of 1. @return the generated event. Needed in order to remove the event from the dispather EventListenerCustom* addCustomEventListener(string &eventName,255)">void(EventCustom*)>& callback);

这个用户自定义事件的侦听器注册方法。参数为一个事件名称与一个回调函数。

从注释里面可以了解这个侦听器的优先级会被设置成1与就是在场景渲染之后事件才被处理。

EventListenerCustom* EventDispatcher::addCustomEventListener( callback)
{
    EventListenerCustom *listener = EventListenerCustom::create(eventName,callback);
    addEventListenerWithFixedPriority(listener,128)">1);
     listener;
}

这个函数返回了一个EventListenerCustom对象,并且通过实现过程可以看出这个listener返回的对象已经被注册到了EventDispatcher管理列表里面。

看过了注册侦听器的方法,现在我们集中看一下注销侦听器的几个重载方法。

void EventDispatcher::removeEventListener(EventListener* listener) 注销指定的侦听器 { if (listener == nullptr) bool isFound = ; auto removeListenerInVector = [&](std::vector<EventListener*>* listeners){ 这里定义了一个匿名函数,作用是遍历一个listener的数组查找是否含有指定的listener,将其从数组中删除,释放引用等操作。 if (listeners == nullptr) ; for (auto iter = listeners->begin(); iter != listeners->end(); ++iter) { auto l = *iter; if (l == listener) { CC_SAFE_RETAIN(l); l->setRegistered(); if (l->getAssociatedNode() != nullptr) { dissociateNodeAndEventListener(l->getAssociatedNode(),l); l->setAssociatedNode(nullptr); NULL out the node pointer so we don't have any dangling pointers to destroyed nodes. } ) { listeners->erase(iter); CC_SAFE_RELEASE(l); } isFound = ; break; } } }; for (auto iter = _listenerMap.begin(); iter != _listenerMap.end();)// 遍历_listenerMap 按侦听器ID分类,一类一类遍历 { auto listeners = iter->second; auto fixedPriorityListeners = listeners->getFixedPriorityListeners(); auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners(); removeListenerInVector(sceneGraphPriorityListeners); 查找优先级为0的列表 (isFound) { fixed #4160: Dirty flag need to be updated after listeners were removed. setDirty(listener->getListenerID(),DirtyFlag::SCENE_GRAPH_PRIORITY); 如果找到了,那么侦听器对象有变化,设置脏标记。 } { removeListenerInVector(fixedPriorityListeners); 查找优先级不为0的列表 (isFound) { setDirty(listener->#if CC_NODE_DEBUG_VERIFY_EVENT_LISTENERS CCASSERT(_inDispatch != 0 || !sceneGraphPriorityListeners || std::count(sceneGraphPriorityListeners->begin(),sceneGraphPriorityListeners->end(),listener) == ,0)">Listener should be in no lists after this is done if we're not currently in dispatch mode.); CCASSERT(_inDispatch != 0 || !fixedPriorityListeners || std::count(fixedPriorityListeners->begin(),fixedPriorityListeners->end(),0)">); #endif if (iter->second->empty()) 如果列表已经没有其它侦听器那么删除这个分类 { _priorityDirtyFlagMap.erase(listener->getListenerID()); auto list = iter->second; iter = _listenerMap.erase(iter); CC_SAFE_DELETE(list); } { ++iter; } (isFound) ; } (isFound) 找到了那么安全删除这个侦听器 { CC_SAFE_RELEASE(listener); } else 如果上面过程都没找到,那么在将要注册的侦听器列表里面再找一遍,如果存在那么将其删除。 { for(auto iter = _toAddedListeners.begin(); iter != _toAddedListeners.end(); ++iter) { if (*iter == listener) { listener->setRegistered(); listener->release(); _toAddedListeners.erase(iter); ; } } } }

下面我们看一下其它重载版本的注销侦听器的函数。

* 根据侦听器的大类型来删除侦听器 void removeEventListenersForType(EventListener::Type listenerType); * 根据Node结点来删除侦听器. void removeEventListenersForTarget(Node* target,255)">bool recursive = * 根据指定事件名称来删除侦听器 void removeCustomEventListeners( customEventName); * 清除所有侦听器 void removeAllEventListeners();

这些方法我就不一个一个分析了,因为过程都与第一个版本的相似,就是查找,删除,释放引用 这几个操作。大家可以自行看一下代码。

再看一下侦听器侦听的暂停与恢复方法。

* Pauses all listeners which are associated the specified target. void pauseEventListenersForTarget(Node* target,0)">* Resumes all listeners which are associated the specified target. void resumeEventListenersForTarget(Node* target,255)">false);

这两个方法也不用多说,就是设置了暂停属性。第二个参数是用来指定是否递归作用于子结点的。

接下来我们看一下 事件分发的方法,

* Dispatches the event * Also removes all EventListeners marked for deletion from the * event dispatcher list. void dispatchEvent(Event* event);


从注释上初步了解这个函数是分发消息并且会注销那些被标记为要删除的侦听器。

下面我们看实现。

void EventDispatcher::dispatchEvent(Event* ) { if (!_isEnabled) ; updateDirtyFlagForSceneGraph(); DispatchGuard guard(_inDispatch); if (event->getType() == Event::Type::TOUCH) { dispatchTouchEvent(static_cast<EventTouch*>()); ; } auto listenerID = __getListenerID(); sortEventListeners(listenerID); auto iter =if (iter != _listenerMap.end()) { auto listeners = iter->second; auto onEvent = [&event](EventListener* listener) -> { event->setCurrentTarget(listener->getAssociatedNode()); listener->_onEvent(); event->isStopped(); }; dispatchEventToListeners(listeners,onEvent); } updateListeners(); }

这个函数的流程为:

  1. 调用 updateDirtyFlagForSceneGraph 这个函数我们后面再分析实现,在这里从命名上可以知道这块处理了那些脏标记。
  2. 将TOUCH事件单独进行了处理,也就将Touch事件调用了dispatchTouchEvent这个方法。这个方法后面我们也单独分析。
  3. 把所有侦听当前传入的Event事件ID的侦听器进行了排序。sortEventListeners方法。
  4. 针对每个侦听当前事件的侦听器进行分发,使用了dispatchEventToListeners方法。
  5. 调用updateListeners 这个方法也后面分析。

其实这个dispatchEvent只是做了一个分拣操作,并没有直接去执行侦听器的回调方法。

上面过程中提到了几个重要的方法,我们下面一个一个分析。

updateDirtyFlagForSceneGraph

EventDispatcher::updateDirtyFlagForSceneGraph() { _dirtyNodes.empty()) { for (auto& node : _dirtyNodes) { auto iter = _nodeListenersMap.find(node); _nodeListenersMap.end()) { for (auto& l : *iter->second) { setDirty(l->

这个方法就是遍历了_dirtyNodes列表,看看有没有脏结点,一个一个的结点去设置新的事件优先级。最后将脏结点从_dirtyNodes里面删除。

sortEventListeners

void EventDispatcher::sortEventListeners(const EventListener::ListenerID& listenerID) { DirtyFlag dirtyFlag = DirtyFlag::NONE; auto dirtyIter = _priorityDirtyFlagMap.find(listenerID); if (dirtyIter != _priorityDirtyFlagMap.end()) { dirtyFlag = dirtyIter->second; } if (dirtyFlag != DirtyFlag::NONE) { Clear the dirty flag first,if `rootNode` is nullptr,then set its dirty flag of scene graph priority dirtyIter->second = DirtyFlag::NONE; if ((int)dirtyFlag & ()DirtyFlag::FIXED_PRIORITY) { sortEventListenersOfFixedPriority(listenerID); } )DirtyFlag::SCENE_GRAPH_PRIORITY) { auto rootNode = Director::getInstance()->getRunningScene(); (rootNode) { sortEventListenersOfSceneGraphPriority(listenerID,rootNode); } { dirtyIter->second = DirtyFlag::SCENE_GRAPH_PRIORITY; } } } }

这个方法作用是根据指定的事件ID来对结点进行排序。

函数过程为:

  1. 在脏列表里面找这个listenerID
  2. 如果脏列表里有这个事件ID那么才进行排序,这里在脏列表里面找有一个优化,如果脏列表里面没有,那么证明这类开事件没有变化,那么就不用排序,因为上次已经排列过顺序了。这块这么处理是按需来排序。很巧妙。
  3. 下面根据优先级权限来分别调用了sortEventListenersOfFixedPriority与sortEventListenersOfSceneGraphPriority两个方法。
  4. 这里要注意一点,在渲染对象中间传递事件实际上是以当前运行的场景为根结点来进行排序的。

我们再看下这两个方法。

sortEventListenersOfFixedPriority

void EventDispatcher::sortEventListenersOfFixedPriority( listenerID) { auto listeners = getListeners(listenerID); ; auto fixedListeners = listeners->getFixedPriorityListeners(); if (fixedListeners == 根据优先级排顺序
    std::sort(fixedListeners->begin(),fixedListeners->end(),[](const EventListener* l1,255)">const EventListener* l2) {
        return l1->getFixedPriority() < l2->getFixedPriority();
    });
    
     因为根据优先级排列顺序为 <0   >0 下面这块是找到第一个大于0优先级的索引。也就是分界点,优先级小于0的侦听器在场景渲染之前触发,大于0的侦听器在场景渲染之后触发。所以这个值很有用。
    int index = for (auto& listener : *fixedListeners)
    {
        if (listener->getFixedPriority() >= )
            ;
        ++index;
    }
    
    listeners->setGt0Index(index);
    
#if DUMP_LISTENER_ITEM_PRIORITY_INFO
    log(-----------------------------------fixedListeners)
    {
        log(listener priority: node (%p),fixed (%d)",l->_node,l->_fixedPriority);
    }    
#endif
    
}

sortEventListenersOfSceneGraphPriority

void EventDispatcher::sortEventListenersOfSceneGraphPriority(const EventListener::ListenerID& listenerID,0)"> rootNode) { auto listeners = getListeners(listenerID); ; auto sceneGraphListeners = listeners->getSceneGraphPriorityListeners(); if (sceneGraphListeners == Reset priority index _nodePriorityIndex = ; _nodePriorityMap.clear(); visitTarget(rootNode,0)"> After sort: priority < 0,> 0 std::sort(sceneGraphListeners->begin(),sceneGraphListeners->end(),[this](return _nodePriorityMap[l1->getAssociatedNode()] > _nodePriorityMap[l2->getAssociatedNode()]; }); sceneGraphListeners) { log(listener priority: node ([%s]%p),priority (%d)_node]); } }

这个场景渲染对象事件的优先级排列与上面一个函数过程类似,很好理解。不多说了。

dispatchEventToListeners

void EventDispatcher::dispatchEventToListeners(EventListenerVector* listeners,255)">bool(EventListener*)>& onEvent) { bool shouldStopPropagation = ; auto fixedPriorityListeners = listeners->getFixedPriorityListeners(); auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners(); ssize_t i = 先处理 priority < 0 的事件侦听器 (fixedPriorityListeners) { CCASSERT(listeners->getGt0Index() <= static_cast<ssize_t>(fixedPriorityListeners->size()),0)">Out of range exception!); if (!fixedPriorityListeners->empty()) { for (; i < listeners->getGt0Index(); ++i) { auto l = fixedPriorityListeners->at(i); if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l)) 这里判断事件是不是需要传递下去,前面是检测侦听器的状态后面onEvent返回l是否停止的属性。它是dispatcherEvent里一个匿名函数 . { shouldStopPropagation = ; ; } } } } // 处理 priority == 0 的事件侦听器 (sceneGraphPriorityListeners) { shouldStopPropagation) { priority == 0,scene graph priority sceneGraphPriorityListeners) { onEvent(l)) { shouldStopPropagation = ; } } } } 处理 priority < 0 的事件侦听器 (fixedPriorityListeners) { priority > 0 ssize_t size = fixedPriorityListeners->size(); for (; i < size; ++at(i); ; } } } } }

dispatchTouchEvent

这个函数大家可以自己看一下,这里不详细分析了,基本过程与dispatchEventToListeners 差不多 区别在于它区分了onebyone及all by once的处理方式。

触摸事件后继章节我们会单独分析。

updateListeners

void EventDispatcher::updateListeners(Event* ) { CCASSERT(_inDispatch > If program goes here,there should be event in dispatch.); auto onUpdateListeners = [ listenerID) 这里定义了一个匿名函数,作用是清理_listenerMap 里的侦听器,无效的都会进行注销清除操作。 { auto listenersIter = _listenerMap.find(listenerID); if (listenersIter == _listenerMap.end()) ; auto listeners = listenersIter->second; auto fixedPriorityListeners = listeners->getSceneGraphPriorityListeners(); (sceneGraphPriorityListeners) { for (auto iter = sceneGraphPriorityListeners->begin(); iter != sceneGraphPriorityListeners->end();) { auto l = *iter; if (!l->isRegistered()) { iter = sceneGraphPriorityListeners->erase(iter); l->release(); } { ++iter; } } } (fixedPriorityListeners) { for (auto iter = fixedPriorityListeners->begin(); iter != fixedPriorityListeners->isRegistered()) { iter = fixedPriorityListeners->if (sceneGraphPriorityListeners && sceneGraphPriorityListeners->empty()) { listeners->clearSceneGraphListeners(); } if (fixedPriorityListeners && fixedPriorityListeners->clearFixedListeners(); } }; 匿名函数结束。 Event::Type::TOUCH) 调用匿名函数清除TOUCH类的无效侦听器 { onUpdateListeners(EventListenerTouchOneByOne::LISTENER_ID); onUpdateListeners(EventListenerTouchAllAtOnce::LISTENER_ID); } 调用匿名函数清除非TOUCH类型的无效侦听器 { onUpdateListeners(__getListenerID()); } if (_inDispatch > ) ; CCASSERT(_inDispatch == 1,0)">_inDispatch should be 1 here. 清理listenerMap里的空项目。
    {
        empty())
        {
            _priorityDirtyFlagMap.erase(iter->first);
            delete iter-> _listenerMap.erase(iter);
        }
        iter;
        }
    }
    
    _toAddedListeners.empty())//清理_toAddedListeners里的空项目
 listener : _toAddedListeners)
        {
            forceAddEventListener(listener);
        }
        _toAddedListeners.clear();
    }
}

至此消息分发过程我们分析完了。

下面看一下用户息定义的消息是怎么分发的

dispatchCustomEvent

void EventDispatcher::dispatchCustomEvent(void *optionalUserData) { EventCustom ev(eventName); ev.setUserData(optionalUserData); dispatchEvent(&ev); }

参数为一个事件名称和一个用户自定义的数据指针,这里面创建了个文化EventCustom对象,然后调用了dispatchEvent(&ev);之后分发的过程与上面的一样了。

好啊,今天又啰嗦这么多,主要分析了三个小编,Cocos2d-x中的 事件、 侦听器、事件分发器

有经验的同学可以看出,其实这里用到了一个常用的设计模式就是观察者模式,采用了注册事件,触发采用回调函数来执行事件过程。

小鱼在这里总结一下:

  1. Cocos2d-x的事件有几种类型,触摸(TOUCH)、键盘(KEYBOARD)、重力器(ACCELERATION)、鼠标(MOUSE)、用户自定义类型(CUSTOM)
  2. 侦听器也有几种 TOUCH_ONE_BY_ONE,CUSTOM
  3. 分发器将事件对象传递给在分发器里注册的事件侦听对象,根据事件类型做匹配,匹配到合适的侦听器后就算事件触发了,调用 侦听器的回调函数来执行事件过程。
  4. Cocos2d-x引擎中有一个分发器对象,就是在Direct类中的_eventDispatcher这个变量,在创建Direct对象时进行的初始化。

今天Cocos2d-x的事件分发机制源码我们分析到这里,在event_dispatch目录里还有一些关于事件的类我们就不做具体分析了,大同小异,如果理解上面的内容自行阅读那部分源码是没问题的。

下一章 我们来阅读Cocos2d-x3.0有关场景Scene类的源码。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


    本文实践自 RayWenderlich、Ali Hafizji 的文章《How To Create Dynamic Textures with CCRenderTexture in Cocos2D 2.X》,文中使用Cocos2D,我在这里使用Cocos2D-x 2.1.4进行学习和移植。在这篇文章,将会学习到如何创建实时纹理、如何用Gimp创建无缝拼接纹
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@163.com微信公众号:HopToad 欢迎转载,转载标注出处:http://blog.csdn.netotbaron/article/details/424343991.  软件准备 下载地址:http://cn.cocos2d-x.org/download 2.  简介2.1         引用C
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从Cocos2D-x官网上下载,进入网页http://www.cocos2d-x.org/download,点击Cocos2d-x以下的Download  v3.0,保存到自定义的文件夹2:从python官网上下载。进入网页https://www.python.org/downloads/,我当前下载的是3.4.0(当前最新
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发引擎,易学易用,支持多种智能移动平台。官网地址:http://cocos2d-x.org/当前版本:2.0    有很多的学习资料,在这里我只做为自己的笔记记录下来,错误之处还请指出。在VisualStudio2008平台的编译:1.下载当前稳
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《最强大脑》娱乐节目。将2048改造成一款挑战玩家对数字记忆的小游戏。邮箱:appdevzw@163.com微信公众号:HopToadAPK下载地址:http://download.csdn.net/detailotbaron/8446223源码下载地址:http://download.csdn.net/
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试以QtCreatorIDE来进行CMake构建。Cocos2d-x3.X地址:https://github.com/cocos2d/cocos2d-x1.打开QtCreator,菜单栏→"打开文件或项目...",打开cocos2d-x目录下的CMakeLists.txt文件;2.弹出CMake向导,如下图所示:设置
 下载地址:链接:https://pan.baidu.com/s/1IkQsMU6NoERAAQLcCUMcXQ提取码:p1pb下载完成后,解压进入build目录使用vs2013打开工程设置平台工具集,打开设置界面设置: 点击开始编译等待编译结束编译成功在build文件下会出现一个新文件夹Debug.win32,里面就是编译
分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!http://www.captainbed.net前言上次用象棋演示了cocos2dx的基本用法,但是对cocos2dx并没有作深入的讨论,这次以超级马里奥的源代码为线索,我们一起来学习超级马里奥的实
1. 圆形音量button事实上作者的本意应该是叫做“电位计button”。可是我觉得它和我们的圆形音量button非常像,所以就这么叫它吧~先看效果:好了,不多解释,本篇到此为止。(旁白: 噗。就这样结束了?)啊才怪~我们来看看代码:[cpp] viewplaincopyprint?CCContro
原文链接:http://www.cnblogs.com/physwf/archive/2013/04/26/3043912.html为了进一步深入学习贯彻Cocos2d,我们将自己写一个场景类,但我们不会走的太远,凡是都要循序渐进,哪怕只前进一点点,那也至少是前进了,总比贪多嚼不烂一头雾水的好。在上一节中我们建
2019独角兽企业重金招聘Python工程师标准>>>cocos2d2.0之后加入了一种九宫格的实现,主要作用是用来拉伸图片,这样的好处在于保留图片四个角不变形的同时,对图片中间部分进行拉伸,来满足一些控件的自适应(PS: 比如包括按钮,对话框,最直观的形象就是ios里的短信气泡了),这就要求图
原文链接:http://www.cnblogs.com/linji/p/3599478.html1.环境和工具准备Win7VS2010/2012,至于2008v2版本之后似乎就不支持了。 2.安装pythonv.2.0版本之前是用vs模板创建工程的,到vs2.2之后就改用python创建了。到python官网下载版本2.7.5的,然后
环境:ubuntu14.04adt-bundle-linux-x86_64android-ndk-r9d-linux-x86_64cocos2d-x-3.0正式版apache-ant1.9.3python2.7(ubuntu自带)加入环境变量exportANDROID_SDK_ROOT=/home/yangming/adt-bundle-linux/sdkexportPATH=${PATH}:/$ANDROID_SDK_ROOTools/export
1开发背景游戏程序设计涉及了学科中的各个方面,鉴于目的在于学习与进步,本游戏《FlappyBird》采用了两个不同的开发方式来开发本款游戏,一类直接采用win32底层API来实现,另一类采用当前火热的cocos2d-x游戏引擎来开发本游戏。2需求分析2.1数据分析本项目要开发的是一款游
原文链接:http://www.cnblogs.com/linji/p/3599912.html//纯色色块控件(锚点默认左下角)CCLayerColor*ccc=CCLayerColor::create(ccc4(255,0,0,128),200,100);//渐变色块控件CCLayerGradient*ccc=CCLayerGradient::create(ccc4(255,0,0,
原文链接:http://www.cnblogs.com/linji/p/3599488.html//载入一张图片CCSprite*leftDoor=CCSprite::create("loading/door.png");leftDoor->setAnchorPoint(ccp(1,0.5));//设置锚点为右边中心点leftDoor->setPosition(ccp(240,160));/
为了答谢广大学员对智捷课堂以及关老师的支持,现购买51CTO学院关老师的Cocos2d-x课程之一可以送智捷课堂编写图书一本(专题可以送3本)。一、Cocos2d-x课程列表:1、Cocos2d-x入门与提高视频教程__Part22、Cocos2d-x数据持久化与网络通信__Part33、Cocos2d-x架构设计与性能优化内存优
Spawn让多个action同时执行。Spawn有多种不同的create方法,最终都调用了createWithTwoActions(FiniteTimeAction*action1,FiniteTimeAction*action2)方法。createWithTwoActions调用initWithTwoActions方法:对两个action变量初始化:_one=action1;_two=action2;如果两个a
需要环境:php,luajit.昨天在cygwin上安装php和luajit环境,这真特么是一个坑。建议不要用虚拟环境安装打包环境,否则可能会出现各种莫名问题。折腾了一下午,最终将环境转向linux。其中,luajit的安装脚本已经在quick-cocos2d-x-develop/bin/中,直接luajit_install.sh即可。我的lin
v3.0相对v2.2来说,最引人注意的。应该是对触摸层级的优化。和lambda回调函数的引入(嗯嗯,不枉我改了那么多类名。话说,每次cocos2dx大更新。总要改掉一堆类名函数名)。这些特性应该有不少人研究了,所以今天说点跟图片有关的东西。v3.0在载入图片方面也有了非常大改变,仅仅只是