对Cocosd2x3.x触摸事件的深度解析

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

相关文章

    本文实践自 RayWenderlich、Ali Hafizji 的文章《...
Cocos-code-ide使用入门学习地点:杭州滨江邮箱:appdevzw@1...
第一次開始用手游引擎挺激动!!!进入正题。下载资源1:从C...
    Cocos2d-x是一款强大的基于OpenGLES的跨平台游戏开发...
1.  来源 QuickV3sample项目中的2048样例游戏,以及最近《...
   Cocos2d-x3.x已经支持使用CMake来进行构建了,这里尝试...