经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » 游戏设计 » 查看文章
Cocos2d-x 学习笔记(15.2) EventDispatcher 事件分发机制 dispatchEvent(event)
来源:cnblogs  作者:深潮  时间:2019/8/5 9:39:41  对本文有异议

1. 事件分发方法 EventDispatcher::dispatchEvent(Event* event)

首先通过_isEnabled标志判断事件分发是否启用。

执行 updateDirtyFlagForSceneGraph()。把一些node对应的ID置脏标记。

对_inDispatch++,当前正在分发的事件数+1。

  1. DispatchGuard guard(_inDispatch);

接下来是一个判断,如果是触摸事件,会调用触摸专用的分发方法,而不是本方法。

  1. if (event->getType() == Event::Type::TOUCH)
  2. {
  3. dispatchTouchEvent(static_cast<EventTouch*>(event));
  4. return;
  5. }

获取参数事件的ID作为监听器ID。

  1. auto listenerID = __getListenerID(event);

接下来对事件同ID的所有监听器进行排序。

  1. sortEventListeners(listenerID);

又是一个类型判断,如果是鼠标事件,定义鼠标专用的事件分发函数指针,否则,定义通用的事件分发函数指针。

  1. auto pfnDispatchEventToListeners = &EventDispatcher::dispatchEventToListeners;
  2. if (event->getType() == Event::Type::MOUSE) {
  3. pfnDispatchEventToListeners = &EventDispatcher::dispatchTouchEventToListeners;
  4. }

然后通过参数事件监听器ID从_listenerMap中找到对应的Vector,该类包含两个存储监听器的容器。

  1. auto iter = _listenerMap.find(listenerID);
  2. if (iter != _listenerMap.end())
  3. {
  4. auto listeners = iter->second;
  5. //...
  6. }

定义匿名函数。

  1. auto onEvent = [&event](EventListener* listener) -> bool{
  2. event->setCurrentTarget(listener->getAssociatedNode());
  3. listener->_onEvent(event);
  4. return event->isStopped();
  5. };

进行事件分发。

  1. (this->*pfnDispatchEventToListeners)(listeners, onEvent);

最后对所有待添加和待删除的监听器进行处理。

  1. updateListeners(event);

简而言之,事件分发的逻辑是,通过参数事件,找到事件对应的监听器ID,分发前还要判断ID对应的监听器容器是否需要重新排序,把该事件分发给所有同ID监听器的回调函数进行处理。

接下来对一些重点方法进行学习。

2. updateDirtyFlagForSceneGraph()

当调用resumeEventListenersForTarget方法,把node的所有关联监听器从暂停状态恢复时,需要把node加入_dirtyNodes。

该函数是就是把_dirtyNodes中的node相关的曾经暂停的监听器的ID在_priorityDirtyFlagMap置脏标记SCENE_GRAPH_PRIORITY,对这些ID的监听器容器之后重新排序。

3. DispatchGuard guard(_inDispatch)

 创建了DispatchGuard类的对象,_inDispatch作为构造函数。

  1. DispatchGuard(int& count):
  2. _count(count)
  3. {
  4. ++_count;
  5. }
  6. ~DispatchGuard()
  7. {
  8. --_count;
  9. }

可以看出,对一件事件进行分发时,_inDispatch++。在分发方法结束时,会对这个局部对象析构,_inDispatch--。十分巧妙的实现了对_inDispatch的自动管理。

4. sortEventListeners(listenerID)

简要的说,在_priorityDirtyFlagMap中判断每种ID的脏标记,根据脏标记的不同,决定ID的哪些容器要重新排序。

该方法首先获取待排序的监听器ID的脏标记。

  1. DirtyFlag dirtyFlag = DirtyFlag::NONE;
  2. auto dirtyIter = _priorityDirtyFlagMap.find(listenerID);
  3. if (dirtyIter != _priorityDirtyFlagMap.end())
  4. {
  5. dirtyFlag = dirtyIter->second;
  6. }

脏标记不为NONE,说明容器需要重新排序,于是先把脏标记置NONE,接下来开始排序。脏标记为NONE时,因为已排好序,排序函数执行完成。

  1. if (dirtyFlag != DirtyFlag::NONE)
  2. {
  3. dirtyIter->second = DirtyFlag::NONE;
  4. //...

这里用按位与操作判断是否对ID的两个容器排序。根据按位与的结果,可能两容器都要重新排序,也可能只有一个容器需要排序。

  1. if ((int)dirtyFlag & (int)DirtyFlag::FIXED_PRIORITY)
  2. {
  3. sortEventListenersOfFixedPriority(listenerID);
  4. }
  5. if ((int)dirtyFlag & (int)DirtyFlag::SCENE_GRAPH_PRIORITY)
  6. {
  7. auto rootNode = Director::getInstance()->getRunningScene();
  8. if (rootNode)
  9. {
  10. sortEventListenersOfSceneGraphPriority(listenerID, rootNode);
  11. }
  12. else
  13. {
  14. dirtyIter->second = DirtyFlag::SCENE_GRAPH_PRIORITY;
  15. }
  16. }

对两个容器排序分别用到了两个方法:

  1. sortEventListenersOfFixedPriority(listenerID);
  2. sortEventListenersOfSceneGraphPriority(listenerID, rootNode);

4.1 sortEventListenersOfFixedPriority(listenerID)

该方法首先获取ID对应的fixedListeners容器。

  1. auto listeners = getListeners(listenerID);
  2. if (listeners == nullptr)
  3. return;
  4. auto fixedListeners = listeners->getFixedPriorityListeners();
  5. if (fixedListeners == nullptr)
  6. return;

对容器进行排序,按优先级从小到大的顺序。

  1. std::stable_sort(fixedListeners->begin(), fixedListeners->end(), [](const EventListener* l1, const EventListener* l2) {
  2. return l1->getFixedPriority() < l2->getFixedPriority();
  3. });

对排好序的容器从小到大查找,找到第一个优先级不小于0的监听器,把其下标记录,作为Vector的成员_gt0Index。

  1. int index = 0;
  2. for (auto& listener : *fixedListeners)
  3. {
  4. if (listener->getFixedPriority() >= 0)
  5. break;
  6. ++index;
  7. }
  8. listeners->setGt0Index(index);

4.2 sortEventListenersOfSceneGraphPriority(listenerID, rootNode)

参数rootNode是当前运行的场景。

同上面的排序一样,显先获取容器。不同之处在于sceneGraphListeners容器里的监听器优先级都为0,排序需要按照node的顺序。

需要_nodePriorityIndex容器记录node的优先级。

  1. _nodePriorityIndex = 0;
  2. _nodePriorityMap.clear();
  3. visitTarget(rootNode, true);

visitTarget方法将计算好的node和node优先级存储在_nodePriorityMap。接下来对sceneGraphListeners进行排序,排序依照每个监听器关联的node在_nodePriorityMap的优先级大小,node优先级大,监听器排序在前。

  1. std::stable_sort(sceneGraphListeners->begin(), sceneGraphListeners->end(), [this](const EventListener* l1, const EventListener* l2) {
  2. return _nodePriorityMap[l1->getAssociatedNode()] > _nodePriorityMap[l2->getAssociatedNode()];
  3. });

4.3 visitTarget(rootNode, true)

简要的说,将计算好的node和node优先级存储在_nodePriorityMap

该方法首先对node的子节点排序。排序后子节点按LocalZOrder从小到大排列,相同时按添加到node的顺序(即顺序不变)。

  1. node->sortAllChildren();

获取子节点容器,数量。

  1. auto& children = node->getChildren();
  2. auto childrenCount = children.size();

对children进行中序遍历,遍历到的node的globalZOrder和node存入_globalZOrderNodeMap中。此时,map中的每个node容器中node都是按LocalZOrder从小到大排列。

  1. if(childrenCount > 0)
  2. {
  3. Node* child = nullptr;
  4. // visit children zOrder < 0
  5. for( ; i < childrenCount; i++ )
  6. {
  7. child = children.at(i);
  8. if ( child && child->getLocalZOrder() < 0 )
  9. visitTarget(child, false);
  10. else
  11. break;
  12. }
  13. if (_nodeListenersMap.find(node) != _nodeListenersMap.end())
  14. {
  15. _globalZOrderNodeMap[node->getGlobalZOrder()].push_back(node);
  16. }
  17. for( ; i < childrenCount; i++ )
  18. {
  19. child = children.at(i);
  20. if (child)
  21. visitTarget(child, false);
  22. }
  23. }
  24. else
  25. {
  26. if (_nodeListenersMap.find(node) != _nodeListenersMap.end())
  27. {
  28. _globalZOrderNodeMap[node->getGlobalZOrder()].push_back(node);
  29. }
  30. }

场景节点中,先获取场景中所有节点globalZOrder,并对globalZOrder从小到大排序。

遍历_globalZOrderNodeMap,获取每个node。遍历按globalZOrder从小到大的顺序,相同globalZOrder则按先后顺序(LocalZOrder从小到大)遍历。按遍历的顺序,将node依次添加到_nodePriorityMap。优先级按node的顺序依次+1。即,越晚绘制的node优先级越高。

  1.    if (isRootNode)
  2. {
  3. std::vector<float> globalZOrders; //存储scene中所有node的globalZOrder
  4. globalZOrders.reserve(_globalZOrderNodeMap.size());
  5. for (const auto& e : _globalZOrderNodeMap)
  6. {
  7. globalZOrders.push_back(e.first);
  8. }
  9. std::stable_sort(globalZOrders.begin(), globalZOrders.end(), [](const float a, const float b){
  10. return a < b;
  11. }); //globalZOrder从小到大排序
  12. for (const auto& globalZ : globalZOrders)
  13. {
  14. for (const auto& n : _globalZOrderNodeMap[globalZ])
  15. {
  16. _nodePriorityMap[n] = ++_nodePriorityIndex;
  17. }
  18. }
  19. _globalZOrderNodeMap.clear();
  20. }

5. 进行事件分发 dispatchEventToListeners(listeners, onEvent)

函数指针pfnDispatchEventToListeners根据事件ID是否是鼠标类型指向不同的函数。

以非鼠标dispatchEventToListeners为例。

首先获取ID的两个监听器容器:fixedPriorityListeners sceneGraphPriorityListeners。

按照优先级<0 =0 >0的顺序,对每个监听器执行以下代码。fixedPriorityListeners通过getGt0Index()获取优先级大于0的监听器序号为分界点,进行分类。

  1. if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l))
  2. {
  3. shouldStopPropagation = true;
  4. break;
  5. }

这里调用了之前定义的匿名函数onEvent(listener)。

6. 匿名函数 onEvent(listener)

设置事件的_currentTarget为监听器关联的node,监听器执行回调函数_onEvent(event)对事件进行处理。

  1. auto onEvent = [&event](EventListener* listener) -> bool{
  2. event->setCurrentTarget(listener->getAssociatedNode());
  3. listener->_onEvent(event);
  4. return event->isStopped();
  5. };

7. 收尾处理 updateListeners(event)

删除所有待删除容器里的监听器。添加所有待添加容器里的监听器。删除Vector里isRegistered为false的监听器。删除_listenerMap中Vector为空的元素。

原文链接:http://www.cnblogs.com/deepcho/p/cocos2dx-dispatch-event.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号