随着cocos2d-x 3.x版本的发展,越来越多的人开始关注并转向3.x的开发。而我就是其中之一,做了2年的2.x开发,刚开始做3.x,难免会遇到一些问题。其中我目前遇到的最搞不懂的就是3.x的触摸事件的处理机制。上网查了资料也没有找到准确的答案。后来自己研究3.x的内核,最终找到了我想要的答案,现在分享给大家,如有错误的地方还望大家在我博客下留言指正。
下面我就针对Cocos2d-x 3.3版本做解析:
首先,我们要知道3.3版本的触摸拆分了两个FIXED_PRIORITY 和SCENE_GRAPH_PRIORITY,FIXED_PRIORITY可以自定义触摸优先级,SCENE_GRAPH_PRIORITY是系统场景触摸事件默认优先级是0,不过在不修改系统层代码的情况下,优先级也只能是0。并且二者的事件是存在不同的vector里,所以二者的事件是“独立的”,就是FIXED_PRIORITY事件的优先级的设置不会影响SCENE_GRAPH_PRIORITY事件的排序。下面我会具体讲。
我们先了解触摸事件的分发流程:
... EventDispatcher::dispatchEvent(Event* event) →EventDispatcher::dispatchTouchEvent(EventTouch* event) →
EventDispatcher::dispatchEventToListeners(...)→ XXX::onTouchBegan()
dispatchEvent函数用来判断是否是触摸事件,是则执行下面的函数。那么触摸事件的判断、分发、屏蔽下层触摸都在下面的两个函数中做完了,我也在这里分析讲下面的这两个函数。
<span style="font-size:14px;">void EventDispatcher::dispatchTouchEvent(EventTouch* event) { sortEventListeners(EventListenerTouchOneByOne::LISTENER_ID); //单点触摸事件排序 sortEventListeners(EventListenerTouchAllAtOnce::LISTENER_ID); //多点触摸事件排序 ...</span><span style="font-size:14px;"> }</span>
<span style="font-size:14px;">sortEventListeners(EventListenerTouchOneByOne::LISTENER_ID);函数的第一行,从函数名知道是对触摸事件的排序,我开始就认为是对触摸事件根据优先级进行排序,然后就忽略了这个函数,后来无意中关注了这个函数,发现没有那么简单。当读者了解了这个函数之后可能就回豁然开朗。</span>
<span style="font-size:14px;"></span><pre name="code" class="cpp">void EventDispatcher::sortEventListeners(const EventListener::ListenerID& listenerID) { ... // 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 & (int)DirtyFlag::FIXED_PRIORITY) { sortEventListenersOfFixedPriority(listenerID); } if ((int)dirtyFlag & (int)DirtyFlag::SCENE_GRAPH_PRIORITY) { auto rootNode = Director::getInstance()->getRunningScene(); if (rootNode) { sortEventListenersOfSceneGraPHPriority(listenerID,rootNode); } else { dirtyIter->second = DirtyFlag::SCENE_GRAPH_PRIORITY; } } <span style="white-space:pre"> </span>... }
里面有两个关键的函数sortEventListenersOfFixedPriority(listenerID)和sortEventListenersOfSceneGraPHPriority(listenerID,rootNode)
void EventDispatcher::sortEventListenersOfFixedPriority(const EventListener::ListenerID& listenerID) { ... // After sort: priority < 0,> 0 std::sort(fixedListeners->begin(),fixedListeners->end(),[](const EventListener* l1,const EventListener* l2) { return l1->getFixedPriority() < l2->getFixedPriority();<span style="white-space:pre"> </span>//lambda函数 }); // FIXME: Should use binary search int index = 0; for (auto& listener : *fixedListeners) { if (listener->getFixedPriority() >= 0) break; ++index; } listeners->setGt0Index(index); ... }从上面的函数可以看出来std::sort的排序规程是按照FixedPriority的优先级。 l1->getFixedPriority()地方取到的就是FixedPriority事件的优先级,我们可以通过 EventDispatcher ::setPriority( EventListener * listener, int fixedPriority) 在代码中设置的优先级。通过lambda函数返回,也就是优先级值越小排序越靠前(不知道lambda函数的自己查资料我就解释了)。下面的那段代码一直到 listeners->setGt0Index(index);是设置一个分界,以有优先级为0的分界。下面会用到,下面再说。
void EventDispatcher::sortEventListenersOfSceneGraPHPriority(const EventListener::ListenerID& listenerID,Node* rootNode) { ... // Reset priority index _nodePriorityIndex = 0; _nodePriorityMap.clear(); visitTarget(rootNode,true); // After sort: priority < 0,> 0 std::sort(sceneGraphListeners->begin(),sceneGraphListeners->end(),[this](const EventListener* l1,const EventListener* l2) { return _nodePriorityMap[l1->getAssociatedNode()] > _nodePriorityMap[l2->getAssociatedNode()]; }); ... }这个函数排序规则的lambda函数的返回 _nodePriorityMap[l1->getAssociatedNode()] > _nodePriorityMap[l2->getAssociatedNode()],那么这到底是什么呢?研究代码发现在排序上面有个 visitTarget(rootNode,true)函数。
void EventDispatcher::visitTarget(Node* node,bool isRootNode) { int i = 0; auto& children = node->getChildren(); auto childrenCount = children.size(); if(childrenCount > 0) { Node* child = nullptr; // visit children zOrder < 0 for( ; i < childrenCount; i++ ) { child = children.at(i); if ( child && child->getLocalZOrder() < 0 ) visitTarget(child,false); else break; } if (_nodeListenersMap.find(node) != _nodeListenersMap.end()) { _globalZOrderNodeMap[node->getGlobalZOrder()].push_back(node); } for( ; i < childrenCount; i++ ) { child = children.at(i); if (child) visitTarget(child,false); } } else { if (_nodeListenersMap.find(node) != _nodeListenersMap.end()) { _globalZOrderNodeMap[node->getGlobalZOrder()].push_back(node); } } if (isRootNode) { std::vector<float> globalZOrders; globalZOrders.reserve(_globalZOrderNodeMap.size()); for (const auto& e : _globalZOrderNodeMap) { globalZOrders.push_back(e.first); } std::sort(globalZOrders.begin(),globalZOrders.end(),[](const float a,const float b){ return a < b; }); for (const auto& globalZ : globalZOrders) { for (const auto& n : _globalZOrderNodeMap[globalZ]) { _nodePriorityMap[n] = ++_nodePriorityIndex; } } _globalZOrderNodeMap.clear(); } }这是一个递归方法,遍历rootnode下的所有node,并且把每个node的globalZOrder作为他们的Key放在map表中,在通过转换把node作为key,给每一个node设置一个标志叠放高度的值放在 _nodePriorityMap中,这样就可以根据node得到他们的叠放层次。 l1->getAssociatedNode()方法返回的这个监听事件所对应的node,这样通过sort函数就可以把 sceneGraphListeners中的所有node按照叠放的优先级排序。
void EventDispatcher::dispatchEventToListeners(EventListenerVector* listeners,const std::function<bool(EventListener*)>& onEvent) { bool shouldStopPropagation = false; auto fixedPriorityListeners = listeners->getFixedPriorityListeners(); auto sceneGraPHPriorityListeners = listeners->getSceneGraPHPriorityListeners(); ssize_t i = 0; // priority < 0 if (fixedPriorityListeners) { CCASSERT(listeners->getGt0Index() <= static_cast<ssize_t>(fixedPriorityListeners->size()),"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)) { shouldStopPropagation = true; break; } } } } if (sceneGraPHPriorityListeners) { if (!shouldStopPropagation) { // priority == 0,scene graph priority for (auto& l : *sceneGraPHPriorityListeners) { if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l)) { shouldStopPropagation = true; break; } } } } if (fixedPriorityListeners) { if (!shouldStopPropagation) { // priority > 0 ssize_t size = fixedPriorityListeners->size(); for (; i < size; ++i) { auto l = fixedPriorityListeners->at(i); if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l)) { shouldStopPropagation = true; break; } } } } }
从这个函数里看到先是处理fixpriority中小于0的事件,就是我们之前提到的 listeners->setGt0Index(index)分界。然后是 sceneGraPHPriority,之后是大于等于0的fixpriority。并且在上面的三块代码里都用for去遍历每个对应的触摸事件 ( fix按照触摸优先级排好的,scene按照叠放层次排好的 ),
if (l->isEnabled() && !l->isPaused() && l->isRegistered() && onEvent(l)) { shouldStopPropagation = true; break; }满足条件会 shouldStopPropagation = true,并且跳出循环,这样就表示有某个节点已经接收了触摸并且是对下层屏蔽的。其中 onEvent(l)是从上层传过来的一个函数指针,具体我就不细说了。
总结,如果你之前做过2.x开发的,可以看出来,这里的FixedProrityListener和2.x的触摸是基本相同的,不同的就是多了一个sceneGraPHPriority。从上面的分析可以看出来sceneGraPHPriority的优势还是蛮明显的。如果我们的事件都是设置为sceneGraPHPriority,这样我们就不需要管理他们的触摸优先级了,也不会出现2.x里面的有时设置有触摸优先级搞了,而叠放层次低,上层无法触摸的乱象。
原文链接:https://www.f2er.com/cocos2dx/344997.html