【cocos2d-x 源码解析】事件系统

  • 事件系统
    • 事件 Event
    • 事件监听器 EventListener
    • 事件分发器 EventDispatcher
      • 监听器管理
      • 注册监听器
      • 移除监听器
      • 暂停和恢复监听器
      • 派发事件
    • 使用方法
    • 总结
  • 实现自己的事件系统

事件系统

cocos2d-x 3.x 的事件比 2.x 要好用很多,也比较简单,主要由三部分组成:事件分发器 EventDispatcher、事件监听器 EventListener 和事件 Event

cocos2d-x 3.x 所有与事件相关的文件都在 cocos/base 模块下。

事件 Event

事件在现实中的意思是发生了一件事,当事情发生之后会有人去做一些处理;在应用程序中事件可以看作是一个带有数据的实体,而事件监听器就是事情发生之后做一些处理工作的人;派发事件就是将事件这个实体派发出去,告诉大家发生了这个事情,然后事件监听器会监听到事件触发,接收事件这个实体,取到事件中的数据,做相应的处理。

在这里事件可以简单地看成一个数据体,它在整个事件系统中的作用是传送数据,事件派发器将事件派发出去,事件监听器接收到这个数据,然后把事件中的数据取出来,进行下一步操作。事件的基类是 Event,它的定义很简单,只定义了几个属性

class CC_DLL Event : public Ref
{
public:
    /** Type Event type.*/
    enum class Type
    {
        TOUCH,
        KEYBOARD,
        ACCELERATION,
        MOUSE,
        FOCUS,
        GAME_CONTROLLER,
        CUSTOM
    };

public:
    //function
protected:
    Type _type;     ///< Event type
    bool _isStopped;       ///< whether the event has been stopped.
    Node* _currentTarget;  ///< Current target

    friend class EventDispatcher;
};

Event 类只声明了事件类型 _type,事件是否停止 _isStopped,事件附加的目标结点 _currentTarget 这三个属性。其中,事件类型共有七种,分别是 触摸事件、键盘事件、加速计事件、鼠标事件、焦点事件、手柄事件和自定义事件。具体事件类在 Event 的基础上多定义了几个属性而已,比如键盘事件 EventKeyboard 就多定义了两个属性,_keyCode 表示按下的键值,_isPressed 表示是按下还是松开

class CC_DLL EventKeyboard : public Event
{
public:
    //function
protected:
    KeyCode _keyCode;
    bool _isPressed;

    friend class EventListenerKeyboard;
};

事件监听器 EventListener

事件监听器作为处理事件的“人“,负责监听事件的发生,并调用相应的回调函数来处理事件。

class CC_DLL EventListener : public Ref
{
public:
    /** Type Event type.*/
    enum class Type
    {
        UNKNOWN,
        TOUCH_ONE_BY_ONE,
        TOUCH_ALL_AT_ONCE,
        KEYBOARD,
        MOUSE,
        ACCELERATION,
        FOCUS,
        GAME_CONTROLLER,
        CUSTOM
    };

    typedef std::string ListenerID;

public:
    bool init(Type t, const ListenerID& listenerID, const std::function<void(Event*)>& callback);
    virtual bool checkAvailable() = 0;
    //...

protected:
    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
    friend class EventDispatcher;
};
  • _fixedPriority 表示事件的优先级,_node 表示事件依赖的场景结点,这两个属性是对立的,分别对应两种添加事件监听器的方式,一种是事件监听器依赖于场景结点,则需要保存这个结点(_node),这时事件不需要额外定义优先级;另一种是事件监听器注册在全局中,则需要定义事件的优先级(_fixedPriority)
  • _paused 表示当前监听器是否被暂停,_isEnabled 表示监听器是否启用,_isRegistered 表示监听器是否已注册,每个监听器只能注册一次,如果要重复注册,必须使用 clone 方法创建一个新的副本
  • _type 表示事件监听器的类型,这个类型和事件的类型要区分开,事件监听器有自己的一个类型,事件也有自己的类型,这两个类型有对应关系,但它们是两个不同的数据;_listenerID 表示监听器的唯一 ID,这是个字符串,在保存监听器到 map 时用作 key 值,这个 ID 在具体的事件监听器子类中定义
  • _onEvent 是事件回调函数,当事件触发时,这个函数将被调用

EventListener 也比较简单,比较重要的两个函数是 init 方法和 checkAvaliable 方法

bool EventListener::init(Type t, const ListenerID& listenerID, const std::function<void(Event*)>& callback)
{
    _onEvent = callback;
    _type = t;
    _listenerID = listenerID;
    _isRegistered = false;
    _paused = true;
    _isEnabled = true;

    return true;
}

bool EventListener::checkAvailable()
{ 
    return (_onEvent != nullptr);
}

init 方法初始化几个属性,checkAvaliable 方法检查回调函数是否为空,如果为空,则不能注册监听器,因为没有回调函数,监听器即使监听到事件发生也无法处理事件。这两个方法在基类没看到调用的地方,init 方法在子类初始化的时候会调用,而 checkAvaliable 方法则在注册事件监听器的时候调用。

下面还是以键盘事件为例,看一下具体的事件监听器类做了哪些事件

class CC_DLL EventListenerKeyboard : public EventListener
{
public:
    static const std::string LISTENER_ID;

    /** Create a keyboard event listener.
     * 
     * @return An autoreleased EventListenerKeyboard object.
     */
    static EventListenerKeyboard* create();

    /// Overrides
    virtual EventListenerKeyboard* clone() override;
    virtual bool checkAvailable() override;

    std::function<void(EventKeyboard::KeyCode, Event*)> onKeyPressed;
    std::function<void(EventKeyboard::KeyCode, Event*)> onKeyReleased;
CC_CONSTRUCTOR_ACCESS:
    EventListenerKeyboard();
    bool init();
};

EventListenerKeyboard 新定义了两个子回调事件 onKeyPressed 和 onKeyReleased,这个函数参数是对外公开的,由外部进行赋值;这两个子回调函数将由父类定义的回调函数 _onEvent 调用(看下面的 init 方法)。

EventListenerKeyKeyboard 的 init 方法首先会定义回调函数,然后调用父类的 init 方法,将事件类型、事件 ID 和回调函数传过去,做父类属性初始化。

bool EventListenerKeyboard::init()
{
    auto listener = [this](Event* event){
        auto keyboardEvent = static_cast(event);
        if (keyboardEvent->_isPressed)
        {
            if (onKeyPressed != nullptr)
                onKeyPressed(keyboardEvent->_keyCode, event);
        }
        else
        {
            if (onKeyReleased != nullptr)
                onKeyReleased(keyboardEvent->_keyCode, event);
        }
    };

    if (EventListener::init(Type::KEYBOARD, LISTENER_ID, listener))
    {
        return true;
    }

    return false;
}

init 方法定义了回调函数 _onEvent,_onEvent 会调用 onKeyPressed 或者 onKeyReleased 方法,所以 checkAvailable 方法不用再检查 _onEvent 是否为空,而是检查这两个子回调函数

bool EventListenerKeyboard::checkAvailable()
{
    if (onKeyPressed == nullptr && onKeyReleased == nullptr)
    {
        CCASSERT(false, "Invalid EventListenerKeyboard!");
        return false;
    }

    return true;
}

事件分发器 EventDispatcher

事件作为一个被传递的数据体,事件监听器则负责事件触发之后的处理逻辑,事件和事件监听器都比较简单,而事件分发器则复杂很多,它是事件系统中很重要的一环。EventDispatcher 的作用主要有两方面,第一个作用是管理事件监听器的订阅,包括注册监听器、移除监听器、暂停监听器、恢复监听器、设置监听器优先级;第二个作用就是派发事件,当一件事件发生时,由事件分发器派发出去,再由事件监听器接收处理。

监听器管理

首先看第一部分,即监听器管理。EventDispatcher 定义了一个私有类 EventListenerVector 来存储注册的事件监听器

class EventListenerVector
{
public:
    EventListenerVector();
    ~EventListenerVector();
    size_t size() const;
    bool empty() const;

    void push_back(EventListener* item);
    void clearSceneGraphListeners();
    void clearFixedListeners();
    void clear();

    inline std::vector* getFixedPriorityListeners() const { return _fixedListeners; };
    inline std::vector* getSceneGraphPriorityListeners() const { return _sceneGraphListeners; };
    inline ssize_t getGt0Index() const { return _gt0Index; };
    inline void setGt0Index(ssize_t index) { _gt0Index = index; };
private:
    std::vector* _fixedListeners;
    std::vector* _sceneGraphListeners;
    ssize_t _gt0Index;
};

EventListenerVector 用两个向量来保存监听器,分别对应两种监听器,_sceneGraphListeners 保存的是事件优先级依赖于结点的监听器,_fixedListeners 保存的是独立设置事件优先级的监听器,数值越小优先级越高,_fixedListeners 存储的监听器优先级从 1 开始,1 表示最高优先级,对应的事件最先触发;_sceneGraphListeners 存储的监听器优先级数值都为 0,但其优先级还与依赖结点的绘制顺序(zOrder)有关。

注册监听器

注册监听器有三个接口,注册依赖结点的监听器,注册独立优先级的监听器,注册自定义事件监听器

void EventDispatcher::addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node)
{
    CCASSERT(listener && node, "Invalid parameters.");
    CCASSERT(!listener->isRegistered(), "The listener has been registered.");

    if (!listener->checkAvailable())
        return;

    listener->setAssociatedNode(node);
    listener->setFixedPriority(0);
    listener->setRegistered(true);

    addEventListener(listener);
}

void EventDispatcher::addEventListenerWithFixedPriority(EventListener* listener, int fixedPriority)
{
    CCASSERT(listener, "Invalid parameters.");
    CCASSERT(!listener->isRegistered(), "The listener has been registered.");
    CCASSERT(fixedPriority != 0, "0 priority is forbidden for fixed priority since it's used for scene graph based priority.");

    if (!listener->checkAvailable())
        return;

    listener->setAssociatedNode(nullptr);
    listener->setFixedPriority(fixedPriority);
    listener->setRegistered(true);
    listener->setPaused(false);

    addEventListener(listener);
}

EventListenerCustom* EventDispatcher::addCustomEventListener(const std::string &eventName, const std::function<void(EventCustom*)>& callback)
{
    EventListenerCustom *listener = EventListenerCustom::create(eventName, callback);
    addEventListenerWithFixedPriority(listener, 1);
    return listener;
}

首先检查监听器(和结点)是否为空,检查监听器是否已注册(不能重复注册),检查监听器是否合法,即对应的回调函数是否有定义;然后设置依赖的结点、优先级,设置为已注册,最后调用私有函数 addEventListener 注册事件监听器,所以 addEventListener 才是真正注册事件的方法。addCustomEventListener 只是针对自定义事件的二次封装,其最终调用的还是 addEventListenerWithFixedPriority 方法。

void EventDispatcher::addEventListener(EventListener* listener)
{
    if (_inDispatch == 0)
    {
        forceAddEventListener(listener);
    }
    else
    {
        _toAddedListeners.push_back(listener);
    }

    listener->retain();
}

addEventListener 先判断事件分发器当前是否在派发事件,如果是的话则先将事件监听器放到一个队列中去,否则则调用 forceAddEventListener 注册事件监听器

void EventDispatcher::forceAddEventListener(EventListener* listener)
{
    EventListenerVector* listeners = nullptr;
    EventListener::ListenerID listenerID = listener->getListenerID();
    auto itr = _listenerMap.find(listenerID);
    if (itr == _listenerMap.end())
    {

        listeners = new (std::nothrow) EventListenerVector();
        _listenerMap.insert(std::make_pair(listenerID, listeners));
    }
    else
    {
        listeners = itr->second;
    }

    listeners->push_back(listener);

    if (listener->getFixedPriority() == 0)
    {
        setDirty(listenerID, DirtyFlag::SCENE_GRAPH_PRIORITY);

        auto node = listener->getAssociatedNode();
        CCASSERT(node != nullptr, "Invalid scene graph priority!");

        associateNodeAndEventListener(node, listener);

        if (node->isRunning())
        {
            resumeEventListenersForTarget(node);
        }
    }
    else
    {
        setDirty(listenerID, DirtyFlag::FIXED_PRIORITY);
    }
}

forceAddEventListener 将监听器保存在一个 EventListenerVector 中,再将这个 EventListenerVector 保存在一个字典 _listenerMap 中,最后设置一下关联结点,再设置一个 dirty flag。_listenerMap 是监听器ID-EventListenerVector 键值对,也就是同一类型(ID)的监听器存储在一个 EventListenerVector 中,而这些监听器可以注册在结点上,也可能独立注册,EventListenerVector 会将它们放在不同的向量列表中,看一下它的 push_back 方法

void EventDispatcher::EventListenerVector::push_back(EventListener* listener)
{
    if (listener->getFixedPriority() == 0)
    {
        if (_sceneGraphListeners == nullptr)
        {
            _sceneGraphListeners = new std::vector();
            _sceneGraphListeners->reserve(100);
        }

        _sceneGraphListeners->push_back(listener);
    }
    else
    {
        if (_fixedListeners == nullptr)
        {
            _fixedListeners = new std::vector();
            _fixedListeners->reserve(100);
        }

        _fixedListeners->push_back(listener);
    }
}

为了保证不发生逻辑混乱,EventDispatcher 在派发事件过程中是不能注册事件监听器,所以事件监听器会被暂时放在一个队列 _toAddedListeners 中,然后在事件派发完成之后调用 updateListeners 注册这些被暂时挂起的监听器

void EventDispatcher::updateListeners(Event* event)
{
    //...
    if (!_toAddedListeners.empty())
    {
        for (auto& listener : _toAddedListeners)
        {
            forceAddEventListener(listener);
        }
        _toAddedListeners.clear();
    }

    if (!_toRemovedListeners.empty())
    {
        cleanToRemovedListeners();
    }
}

void EventDispatcher::dispatchEvent(Event* event)
{
    //...
    updateListeners(event);
}

updateListeners 会根据事件类型更新这一事件监听器 ID 对应的所有监听器,这部分代码先不看;然后遍历 _toAddedListeners 列表,调用 forceAddEventListener 注册这些挂起的监听器;和注册监听器一样,移除监听器时如果事件分发器正在派发事件,则将要移除的监听器存储下来,然后在 updateListeners 的最后遍历 _toRemovedListeners 列表,调用 cleanToRemovedListeners 移除这些被挂起的监听器,这部分在下面的移除监听器处再看。

移除监听器

移除监听器共有下面六个接口

void removeEventListener(EventListener* listener);
void removeEventListenersForListenerID(const EventListener::ListenerID& listenerID);

void removeEventListenersForType(EventListener::Type listenerType);
void removeEventListenersForTarget(Node* target, bool recursive = false);
void removeCustomEventListeners(const std::string& customEventName);
void removeAllEventListeners();

其中最重要的接口有两个 removeEventListenter 和 removeEventListenerForListenerID ,其它四个接口都是调用这两个接口,

  • removeEventListenter 用于移除单个监听器
  • removeEventListenerForListenerID 用于移除一个事件类型的所有监听器
  • removeEventListenersForTarget 遍历目标结点的所有监听器,然后调用 removeEventListenter 一个个移除
  • removeEventListenersForTyperemoveCustomEventListeners 根据事件类型或者事件名得到对应的监听器 ID,然后调用 removeEventListenerForListenerID 移除该类型事件的所有监听器;removeAllEventListeners 则遍历所有的监听器 ID,然后调用 removeEventListenerForListenerID 移除该类型事件的所有监听器,最终移除所有的监听器

接下来看 removeEventListener 的实现

void EventDispatcher::removeEventListener(EventListener* listener)
{
    if (listener == nullptr)
        return;

    bool isFound = false;

    auto removeListenerInVector = [&](std::vector* listeners){
        if (listeners == nullptr)
            return;

        for (auto iter = listeners->begin(); iter != listeners->end(); ++iter)
        {
            auto l = *iter;
            if (l == listener)
            {
                CC_SAFE_RETAIN(l);
                l->setRegistered(false);
                if (l->getAssociatedNode() != nullptr)
                {
                    dissociateNodeAndEventListener(l->getAssociatedNode(), l);
                    l->setAssociatedNode(nullptr);  // nullptr out the node pointer so we don't have any dangling pointers to destroyed nodes.
                }

                if (_inDispatch == 0)
                {
                    iter = listeners->erase(iter);
                    CC_SAFE_RELEASE(l);
                }
                else
                {
                    _toRemovedListeners.push_back(l);
                }

                isFound = true;
                break;
            }
        }
    };

    for (auto iter = _listenerMap.begin(); iter != _listenerMap.end();)
    {
        auto listeners = iter->second;
        auto fixedPriorityListeners = listeners->getFixedPriorityListeners();
        auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners();

        removeListenerInVector(sceneGraphPriorityListeners);
        if (isFound)
        {
            // fixed #4160: Dirty flag need to be updated after listeners were removed.
            setDirty(listener->getListenerID(), DirtyFlag::SCENE_GRAPH_PRIORITY);
        }
        else
        {
            removeListenerInVector(fixedPriorityListeners);
            if (isFound)
            {
                setDirty(listener->getListenerID(), DirtyFlag::FIXED_PRIORITY);
            }
        }

        if (iter->second->empty())
        {
            _priorityDirtyFlagMap.erase(listener->getListenerID());
            auto list = iter->second;
            iter = _listenerMap.erase(iter);
            CC_SAFE_DELETE(list);
        }
        else
        {
            ++iter;
        }

        if (isFound)
            break;
    }

    if (isFound)
    {
        CC_SAFE_RELEASE(listener);
    }
    else
    {
        for(auto iter = _toAddedListeners.begin(); iter != _toAddedListeners.end(); ++iter)
        {
            if (*iter == listener)
            {
                listener->setRegistered(false);
                listener->release();
                _toAddedListeners.erase(iter);
                break;
            }
        }
    }
}

代码非常长,总结起来就是下面几个步骤

  • 遍历 _listenerMap 得到一个监听器 ID 对应的监听器列表 listeners,再分别遍历 listeners 的两个子列表 fixedPriorityListeners 和 sceneGraphPriorityListeners 里的监听器,如果找到要移除的监听器则进行移除,遍历结束
  • 移除操作和注册操作相对应,设置为未注册,取消监听器和结点的关联关系,然后将监听器从列表中移除,要注意的是和注册监听器一样,如果正在派发事件则不能进行移除操作,而是将监听器暂时放在一个列表 _toRemovedListeners 中,然后等事件派发完成调用 updateListeners 进行移除
  • 将监听器从列表移除后,该列表可能为空了,即没有该监听器 ID 对应的监听器了,所以要把这个列表从 _listenerMap 移除
  • 如果遍历完 _listenerMap 都没有找到要移除的监听器,则遍历 _toAddedListeners 列表,看看该监听器是否在注册的时候被挂起,如果找到了则从 _toAddedListeners 中移除,这样这个挂起的监听器就不会在后面被注册了

removeEventListenersForListenerID 的操作和 removeEventListener 差不多,这里就不贴出代码了。前面讲到 updateListeners 会重新注册被挂起的监听器,还会重新移除被挂起的监听器,我们看一下这个移除的函数

void EventDispatcher::cleanToRemovedListeners()
{
    for (auto& l : _toRemovedListeners)
    {
        auto listenersIter = _listenerMap.find(l->getListenerID());
        if (listenersIter == _listenerMap.end())
        {
            CC_SAFE_RELEASE(l);
            continue;
        }

        bool find = false;
        auto listeners = listenersIter->second;
        auto fixedPriorityListeners = listeners->getFixedPriorityListeners();
        auto sceneGraphPriorityListeners = listeners->getSceneGraphPriorityListeners();

        if (sceneGraphPriorityListeners)
        {
            auto machedIter = std::find(sceneGraphPriorityListeners->begin(), sceneGraphPriorityListeners->end(), l);
            if (machedIter != sceneGraphPriorityListeners->end())
            {
                find = true;
                CC_SAFE_RELEASE(l);
                sceneGraphPriorityListeners->erase(machedIter);
            }
        }

        if (fixedPriorityListeners)
        {
            auto machedIter = std::find(fixedPriorityListeners->begin(), fixedPriorityListeners->end(), l);
            if (machedIter != fixedPriorityListeners->end())
            {
                find = true;
                CC_SAFE_RELEASE(l);
                fixedPriorityListeners->erase(machedIter);
            }
        }

        if (find)
        {
            if (sceneGraphPriorityListeners && sceneGraphPriorityListeners->empty())
            {
                listeners->clearSceneGraphListeners();
            }

            if (fixedPriorityListeners && fixedPriorityListeners->empty())
            {
                listeners->clearFixedListeners();
            }
        }
        else
            CC_SAFE_RELEASE(l);
    }

    _toRemovedListeners.clear();
}

这个逻辑也很简单,就是遍历最顶层列表 _listenerMap,然后遍历子列表 fixedPriorityListeners 和 sceneGraphPriorityListeners,找到了就移除;如果移除子列表为空,则调用 clearSceneGraphListeners 或 clearFixedListeners 清除子列表,这里有个不足的地方就是没有像 removeEventListener 一样,当这两个子列表都为空时,应该将 listeners 从 _listenerMap 中移除;最后将被挂起移除监听器列表 _toRemovedListeners 清空、

暂停和恢复监听器

暂停和恢复监听器很简单,只是简单地修改监听器的 _isPaused 属性,引擎目前只提供一个暂停监听器接口和一个恢复监听器接口,就是暂停或恢复一个结点的所有监听器

void EventDispatcher::pauseEventListenersForTarget(Node* target, bool recursive/* = false */)
{
    auto listenerIter = _nodeListenersMap.find(target);
    if (listenerIter != _nodeListenersMap.end())
    {
        auto listeners = listenerIter->second;
        for (auto& l : *listeners)
        {
            l->setPaused(true);
        }
    }

    for (auto& listener : _toAddedListeners)
    {
        if (listener->getAssociatedNode() == target)
        {
            listener->setPaused(true);
        }
    }

    if (recursive)
    {
        const auto& children = target->getChildren();
        for (const auto& child : children)
        {
            pauseEventListenersForTarget(child, true);
        }
    }
}

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

    for (auto& listener : _toAddedListeners)
    {
        if (listener->getAssociatedNode() == target)
        {
            listener->setPaused(false);
        }
    }

    setDirtyForNode(target);

    if (recursive)
    {
        const auto& children = target->getChildren();
        for (const auto& child : children)
        {
            resumeEventListenersForTarget(child, true);
        }
    }
}

需要暂停或恢复的监听器只有依赖于结点的监听器,所以只需要遍历 _nodeListenersMap 而不需要遍历 _priorityDirtyFlagMap;另外,被挂起的监听器也要暂停或恢复,因为这些监听器马上就会被注册了,所以要将它们和正常的监听器同等对待。recursive 表示要不要递归地暂停或恢复子结点的监听器。

派发事件

接下来进入 EventDispatcher 的第二个功能,就是派发事件。前面讲了事件监听器的那么多东西,但其实得到的结论只是事件监听器定义了一个回调函数,然后事件监听器和事件类型以键值对的方式保存下来了;而事件监听器是如何监听到事件触发并执行处理逻辑的我们还是不清楚,也就是事件触发之后如何回调到事件监听器的回调函数的,这就是派发事件做的事。

首先,不要觉得事件触发很复杂,其实事件触发是一个主动调用的过程,也就是说是有一个函数去主动调用事件监听器的回调函数,而这个函数就是派发事件函数。比如我们有“刷新数据”的事件,事先已经为这个事件注册好了监听器,然后开始触发事件,执行命令 dispatchEvent("刷新数据"),则 dispatchEvent 方法会找到对应的监听器,然后调用监听器的 onEvent 方法来做相关的处理。

有了这个认知之后,我们就可以来看 dispatchEvent 的实现了

void EventDispatcher::dispatchEvent(Event* event)
{
    if (!_isEnabled)
        return;

    updateDirtyFlagForSceneGraph();


    DispatchGuard guard(_inDispatch);

    if (event->getType() == Event::Type::TOUCH)
    {
        dispatchTouchEvent(static_cast(event));
        return;
    }

    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())
    {
        auto listeners = iter->second;

        auto onEvent = [&event](EventListener* listener) -> bool{
            event->setCurrentTarget(listener->getAssociatedNode());
            listener->_onEvent(event);
            return event->isStopped();
        };

        (this->*pfnDispatchEventToListeners)(listeners, onEvent);
    }

    updateListeners(event);
}

首先,调用静态方法 __getListenerID 根据事件类型得到事件监听器类型(ID),前面讲过事件类型和事件监听器类型是两个不同的东西,但它们之间有对应关系,这个关系就是在这里使用的。得到事件监听器类型 listenerID 之后,先调用 sortEventListeners 方法对该类型的所有监听器进行排序,排序的依据是优先级;然后再调用 dispatchEventToListeners 方法,这个方法会找到最先响应的监听器,调用其 _onEvent 方法,完成事件回调;触摸事件因为有单点触摸和多点触摸,所以特殊处理,调用的是 dispatchTouchEventToListeners 方法,而鼠标事件在引擎中也被看成是触摸事件,所以也是调用的 dispatchTouchEventToListeners 方法。

引擎有很多地方都有派发事件的逻辑,比如在之前的文章讲过的绘制场景函数中,有刷新场景之前事件 _eventBeforeUpdate,刷新场景之后事件 _eventAfterUpdate,访问结点之后事件 _eventAfterUpdate,绘制完成事件 _eventAfterDraw。

void Director::drawScene()
{
    // calculate "global" dt
    calculateDeltaTime();

    if (_openGLView)
    {
        _openGLView->pollEvents();
    }

    //tick before glClear: issue #533
    if (! _paused)
    {
        _eventDispatcher->dispatchEvent(_eventBeforeUpdate);
        _scheduler->update(_deltaTime);
        _eventDispatcher->dispatchEvent(_eventAfterUpdate);
    }
    //...
    pushMatrix(MATRIX_STACK_TYPE::MATRIX_STACK_MODELVIEW);

    if (_runningScene)
    {
#if (CC_USE_PHYSICS || (CC_USE_3D_PHYSICS && CC_ENABLE_BULLET_INTEGRATION) || CC_USE_NAVMESH)
        _runningScene->stepPhysicsAndNavigation(_deltaTime);
#endif
        //clear draw stats
        _renderer->clearDrawStats();

        //render the scene
        _runningScene->render(_renderer);

        _eventDispatcher->dispatchEvent(_eventAfterUpdate);
    }
    //...
    _eventDispatcher->dispatchEvent(_eventAfterDraw);
    //...
}

这些事件虽然都是引擎自带的,但其实也算是自定义事件,那对于我们常规意义的事件呢,比如鼠标事件、键盘事件,它们的使用方式也是直接调用 EventDispatcher.dispatchEvent 吗?答案是肯定的,只不过这些事件多了一个步骤,即硬件监听,这部分我们不需要关心,自然有相应的接口使用,比如 cocos2d-x 的 win32 版本使用的是 glfw 来创建和管理窗口,则使用 glfw 提供的接口来响应硬件事件。比如键盘事件

void GLViewImpl::onGLFWKeyCallback(GLFWwindow *window, int key, int scancode, int action, int mods)
{
    if (GLFW_REPEAT != action)
    {
        EventKeyboard event(g_keyCodeMap[key], GLFW_PRESS == action);
        auto dispatcher = Director::getInstance()->getEventDispatcher();
        dispatcher->dispatchEvent(&event);
    }

    if (GLFW_RELEASE != action && g_keyCodeMap[key] == EventKeyboard::KeyCode::KEY_BACKSPACE)
    {
        IMEDispatcher::sharedDispatcher()->dispatchDeleteBackward();
    }
}

可以看到,glfw 监听到键盘按下或松开事件之后,创建一个 EventKeyboard 事件,然后调用 EventDispatcher.diaptcheEvent 方法将事件派发出去,所以说这些系统事件和自定义事件是一样的,只不过多了硬件监听这一步而已。事件其实没什么神秘的!

使用方法

使用事件的步骤如下
1. 定义事件和对应的事件监听器,这一步引擎已经做好了,并提供了一个自定义事件用于扩展其它事件,因此即使我们要添加自己的事件也不需要做这一步
2. 创建一个事件监听器并注册
3. 派发事件,这一步可以是主动派发,即在程序在主动调用 dispatchEvent 派发一个整件;也可以是被动派发,即系统响应后自动派发

注册一个键盘事件监听器

auto listener = EventListenerKeyboard::create();
listener->onKeyPressed = [](EventKeyboard::KeyCode code, Event* event)
{
    log("%d", code);
};
auto dispatcher = Director::getInstance()->getEventDispatcher();
dispatcher->addEventListenerWithFixedPriority(listener, 1);

主动派发一次事件,相当于按下键盘的 A 键

auto event = new EventKeyboard(EventKeyboard::KeyCode::KEY_A, true);
dispatcher->dispatchEvent(event);

总结

事件是一个“数据体”,由事件类型和携带的数据组成。事件监听器是“处理事件的人”,其存储了相应事件的回调函数,当事件触发时,这些回调函数就会被调用。事件派发器是一个“事件管理者”,它有两个作用,注册事件监听器和派发事件;注册事件监听器就是给监听器关联一个回调函数,派发事件就是找到事件类型对应的监听器,调用其回调函数。事件触发的过程就是事件派发器将事件传给事件监听器,执行事件监听器的回调函数。

实现自己的事件系统

下面是使用 lua 实现的一个简单事件系统,功能不是很完善,没有优先级的管理,但也覆盖了整个事件系统的框架

-- 定义事件类型
local EVENT_SAYHELLO = 0
local EVENT_SAYBYE = 1

-- 实现事件派发器
EventDispatcher = {}
-- 定义事件监听器
EventDispatcher.eventListeners = {}
-- 注册事件原型
function EventDispatcher:registerEvent(eventType, func)
    if not self.eventListeners[eventType] then
        self.eventListeners[eventType] = {}
    end
    table.insert(self.eventListeners[eventType], func)
end
-- 反注册事件原型
function EventDispatcher:unRegisterEvent(eventType, func)
    if not self.eventListeners[eventType] then
        return
    end
    for i, callback in ipairs(self.eventListeners[eventType]) do
        if callback == func then
            table.remove(self.eventListeners[eventType], i)
            break
        end
    end
end
-- 派发事件原型
function EventDispatcher:dispatchEvent(event)
    for type, listener in pairs(self.eventListeners) do
        if type == event.type then
            for _, callback in ipairs(listener) do
                callback(table.unpack(event.param))
            end
            break
        end
    end
end

-- 注册事件
local function sayHello(name)
    print("hello " .. name)
end
EventDispatcher:registerEvent(EVENT_SAYHELLO, sayHello)
EventDispatcher:registerEvent(EVENT_SAYHELLO, function(name) print("hi " .. name) end)
EventDispatcher:registerEvent(EVENT_SAYBYE, function(name) print("goodbyte " .. name) end)
EventDispatcher:registerEvent(EVENT_SAYBYE, function(name) print("see you later, " .. name) end)

-- 定义事件
local event = {
    type = EVENT_SAYHELLO,
    param = {"ljx"}
}
local event2 = {
    type = EVENT_SAYBYE,
    param = {"ljx"}
}
-- 派发事件
EventDispatcher:dispatchEvent(event)
EventDispatcher:dispatchEvent(event2)

-- 反注册事件
EventDispatcher:unRegisterEvent(EVENT_SAYHELLO, sayHello)
-- 两次派发事件
EventDispatcher:dispatchEvent(event)

执行结果

hello ljx
hi ljx
goodbyte ljx
see you later, ljx
hi ljx

你可能感兴趣的:(cocos2d-x)