随着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里面的有时设置有触摸优先级搞了,而叠放层次低,上层无法触摸的乱象。