结合源码看《我所理解的cocos2dx-3.0》—— 事件系统

事件系统

这个基本都是采用观察者模式实现的,本文记录下cocos2dx的实现逻辑。

监听的注册

addEventListener分2种情况:如果有listener正在触发,先加入缓存_toAddedListeners中(在onevent的监听触发完成后,调用updateListeners将其加入orceAddEventListener),否则调用forceAddEventListener直接加入。

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

void EventDispatcher::forceAddEventListener(EventListener* listener)

  1. 获取listener的事件名,这里叫ListenerID:typedef std::string ListenerID;
  2. 判断_listenerMap是否有该事件,没有创建一个EventListenerVector并加入_listenerMap
  3. listener加入EventListenerVector
  4. 如果istenerFixedPriority是0,说明是scene graph priority listener,加入_nodeListenersMap
  5. 设置对应事件的监听队列的脏标,触发监听时根据这个排序
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.emplace(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())
        {
            listener->setPaused(true);
        }
    }
    else
    {
        setDirty(listenerID, DirtyFlag::FIXED_PRIORITY);
    }
}

针对UI节点添加的监听,cocos2dx进行了优化:

指定整数的优先级,通常要求开发者记住大量的优先级数字,而有时候一个处于更下层的 UI元素却被错误地指定了一个更高的优先级,给例如触摸的管理带来了不少麻烦。因此,Cocos2d-x3.0新增了一种将分发优先级与一个Node元素关联的方式,EventDispatcher将自动根据当前该Node元素绘制的相反顺序来决定分发优先级,即使该UI元素的层级发生变更,它也能正确处理,这样就大大简化了对触摸优先级的管理。

EventDispatcher分发事件的顺序如下:

  1. priority<0
  2. scene graph (priority=0)
  3. priority >0

即首先分发优先级小于0的订阅者,它们按优先级从小到大的顺序分发;
然后分发所有与Node元素关联的订阅者,它们按照自身关联的Node在UI场景中的层级从前往后分发;
最后分发所有优先级大于0的订阅者,按优先级从小到大的顺序分发。
实际上所有与Node关联的订阅者优先级都被设置为0,而开发者无法注册一个优先级为0的订阅者。

事件触发 dispatchEvent

  1. _inDispatch ++;方法也很巧妙,使用RAII,弄了个DispatchGuard类,在构造函数里加一,析构里减一
  2. dispatchTouchEvent,对触摸事件,使用dispatchTouchEvent接口单独处理
  3. sortEventListeners 排序,从_priorityDirtyFlagMap中判断对应事件的监听列表是否需要重新排序,采用stable_sort进行稳定排序,设置了下优先级大于0的listner的索引位置index。额外的,使用宏来控制了log的输出,方便查找问题:
#if DUMP_LISTENER_ITEM_PRIORITY_INFO
    log("-----------------------------------");
    for (auto& l : *fixedListeners)
    {
        log("listener priority: node (%p), fixed (%d)", l->_node, l->_fixedPriority);
    }    
#endif
  1. 触发,遍历EventListenerVector,执行每个listener_onEvent接口,可以使用stop控制是否继续执行后面的listener
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);
}
  1. updateListeners,处理监听触发过程中需要删除的监听,限制了只能在触发程度为1即第一次触发事件时才进行删除;新加的监听也是同样处理,新加的监听不会被本次事件触发。

事件和Node

到目前为止,在Cocos2d-x中,Node元素除了用来显示场景,还用来执行3种逻辑相关的操作:动画(Action)、更新回调(Schedule)和事件(Event)。
这些操作在Node元素从场景移除时将变得毫无意义。而在另外一些状态下,开发者可能也需要让一个Node元素停止处理这些操作,即使它仍然显示在场景中。
Cocos2d-x提供了两个方法用来暂停与恢复这些操作的执行,分别是 pause()方法和resume()方法,示例如下。

void Node::resume()
{
    _scheduler->resumeTarget(this);
    _actionManager->resumeTarget(this);
    _eventDispatcher->resumeEventListenersForTarget(this);
}

void Node::pause()
{
    _scheduler->pauseTarget(this);
    _actionManager->pauseTarget(this);
    _eventDispatcher->pauseEventListenersForTarget(this);
}

这可以在任何时间阻止或允许任何与该Node相关联的订阅者接收事件通知,但是这会同时影响更新回调和动画的执行。如果只是想控制与事件相关的逻辑,则可以直接调用resumeEventListenersForTarget()和 pauseEventListenerForTarget()方法。
在Node元素内部,当onEnter()和onExit()方法被调用时,会分别自动调用resume()和pause()方法。这也会导致所有子元素的动画、更新回调及事件订阅者都会被恢复与暂停。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值