cocos2d的事件分成7种:TOUCH,KEYBOARD,acceleration,MOUSE,FOCUS,GAME_CONTROLLER,CUSTOM,分别为 触摸,键盘,加速,鼠标,聚焦,游戏控制器和自定义事件。有了事件,那么就需要有事件监听器,每种事件至少对应一种监听器,由于触摸分单点触摸和多点触摸,触摸事件对应两种监听器,每种监听器中都定义了一个listenerid。


下面分为两个步骤来分析源码:

1----注册监听器

cocos2d提供了两种注册监听器的方式:addEventListenerWithFixedPriority和addEventListenerWithSceneGraPHPriority。

void Eventdispatcher::addEventListenerWithFixedPriority(EventListener* listener,int fixedPriority)
void Eventdispatcher::addEventListenerWithSceneGraPHPriority(EventListener* listener,Node* node)

前者参数中有一个优先级,后者将监听器绑定到一个节点,所以listener的回调触发会收到node的globalZorder和localZorder的影响,addEventListenerWithSceneGraPHPriority优先级默认为0。优先级越小,就越早被触发,当事件被吞噬掉了,那么后面的监听器就不会被触发了,吞噬的方式有两种:
<pre name="code" class="html">event->stopPropagation();
listener->setSwallowtouches(true);//限于单点触摸
 

void Eventdispatcher::addEventListener(EventListener* listener)
{
    if (_indispatch == 0)//当前没有在派发事件,强制加入监听器列表中
    {
        forceAddEventListener(listener);
    }
    else
    {
        _toAddedListeners.push_back(listener);//加入等待加入列表中
    }

    listener->retain();
}
当派发事件结束的时候,会将等待列表加入到监听器列表中。
void Eventdispatcher::updateListeners(Event* event)
{
    CCASSERT(_indispatch > 0,"If program goes here,there should be event in dispatch.");

   //....省略部分代码.........
    
    if (!_toAddedListeners.empty())
    {
        for (auto& listener : _toAddedListeners)
        {
            forceAddEventListener(listener);
        }
        _toAddedListeners.clear();
    }
}

_listenerMap维护了一份listenerId到EventListenerVector的映射,EventListenerVector内部_fixedListeners和_sceneGraphListeners,当加入push一个监听器,根据它的优先级来决定到底是放入到哪一个列表中。当有新的监听器加入时,会写入一份脏数据,该脏数据标识着哪个listenerId类型的监听器中的_fixedListeners和_sceneGraphListeners有变换,在进行事件派发之前需要判断当前事件是否会收到这个脏数据的影响。如果有影响,就需要在派发之前对列表进行重新排序。
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.insert(std::make_pair(listenerID,listeners));
    }
    else
    {
        listeners = itr->second;
    } 
	//EventListenerVector 包括_fixedListeners和_sceneGraphListeners
    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())
        {
            resumeEventListenersForTarget(node);
        }
    }
    else
    {
        setDirty(listenerID,DirtyFlag::FIXED_PRIORITY);
    }
}
//标识哪个listenerID类型的列表需要变化,是_fixedListeners还是_sceneGraphListeners取决于flag
void Eventdispatcher::setDirty(const EventListener::ListenerID& listenerID,DirtyFlag flag)
{    
    auto iter = _priorityDirtyFlagMap.find(listenerID);
    if (iter == _priorityDirtyFlagMap.end())
    {
        _priorityDirtyFlagMap.insert(std::make_pair(listenerID,flag));
    }
    else
    {
        int ret = (int)flag | (int)iter->second;
        iter->second = (DirtyFlag) ret;
    }
}

2-----事件派发

//派发事件,先处理脏数据,监听器排序,派发

void Eventdispatcher::dispatchEvent(Event* event)
{
    if (!_isEnabled)
        return;
    //脏数据不仅仅是在加入监听器的时候会存在,在已存在的监听器绑定到的node resume的时候也会产生脏数据,只会影响到_sceneGraphListeners中的监听器
    updateDirtyFlagForSceneGraph();
	//标识正在派发事件
    dispatchGuard guard(_indispatch);
    if (event->getType() == Event::Type::TOUCH)
    {
		//触摸事件单独处理
        dispatchTouchEvent(static_cast<EventTouch*>(event));
        return;
    }
    auto listenerID = __getListenerID(event);
    
	//对listenerID类型的监听器进行排序(如果需要的话)
	//DirtyFlag::FIXED_PRIORITY-------->fixedPriorityListeners排序
	//DirtyFlag::SCENE_GRAPH_PRIORITY-------->sceneGraPHPriorityListeners排序
    sortEventListeners(listenerID);
    
    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();//是否吞噬此事件
        };
        
		//对事件按照顺序进行派发
        dispatchEventToListeners(listeners,onEvent);
    }
    
    updateListeners(event);
}

派发触摸事件,单点触摸是将event的每个触摸点都进行事件触发,多点触摸是按组进行处理


void Eventdispatcher::dispatchTouchEvent(EventTouch* event)
{
	//监听器排序
    sortEventListeners(EventListenerTouchOneByOne::LISTENER_ID);
    sortEventListeners(EventListenerTouchAllAtOnce::LISTENER_ID);
    
    auto oneByOneListeners = getListeners(EventListenerTouchOneByOne::LISTENER_ID);
    auto allAtOnceListeners = getListeners(EventListenerTouchAllAtOnce::LISTENER_ID);
    
    // If there aren't any touch listeners,return directly.
    if (nullptr == oneByOneListeners && nullptr == allAtOnceListeners)
        return;
    
    bool isNeedsMutableSet = (oneByOneListeners && allAtOnceListeners);
    
    const std::vector<Touch*>& originaltouches = event->gettouches();
    std::vector<Touch*> mutabletouches(originaltouches.size());//保存一组触摸点
    std::copy(originaltouches.begin(),originaltouches.end(),mutabletouches.begin());

    //
    // process the target handlers 1st
    //
    if (oneByOneListeners)
    {
        auto mutabletouchesIter = mutabletouches.begin();
        auto touchesIter = originaltouches.begin();
        
        for (; touchesIter != originaltouches.end(); ++touchesIter)
        {
            bool isSwallowed = false;

			//对每个触摸点进行处理
            auto onTouchEvent = [&](EventListener* l) -> bool { // Return true to break
                EventListenerTouchOneByOne* listener = static_cast<EventListenerTouchOneByOne*>(l);
                
                // Skip if the listener was removed.
                if (!listener->_isRegistered)
                    return false;
             
                event->setCurrentTarget(listener->_node);
                
                bool isClaimed = false;//是否被认领了
                std::vector<Touch*>::iterator removedIter;
                
                EventTouch::EventCode eventCode = event->getEventCode();
                
                if (eventCode == EventTouch::EventCode::BEGAN)
                {
                    if (listener->onTouchBegan)
                    {
                        isClaimed = listener->onTouchBegan(*touchesIter,event);
                        if (isClaimed && listener->_isRegistered)
                        {
//将该触摸点加入到该监听器认领列表中,后面要处理MOVED,ENDED,CANCELLED事件时需要对应的触摸点在认领列表中,否则不触发对应方法
                            listener->_claimedtouches.push_back(*touchesIter);
                        }
                    }
                }
		//是否能找到BEGAN加入的触摸点,找到了才能触发
                else if (listener->_claimedtouches.size() > 0
                         && ((removedIter = std::find(listener->_claimedtouches.begin(),listener->_claimedtouches.end(),*touchesIter)) != listener->_claimedtouches.end()))
                {
                    isClaimed = true;
                    
                    switch (eventCode)
                    {
                       //........省略部分代码.........
                            if (listener->_isRegistered)
                            {
<pre name="code" class="html">//当触摸结束了,必须从认领列表中移出该触摸点,否则列表是很庞大的数据,CANCELLED同理
listener->_claimedtouches.erase(removedIter);
 
                            }
                            //........省略部分代码.........
                    }
                }
                
                // If the event was stopped,return directly.
		//吞噬事件:方式一
                if (event->isstopped())
                {
                    updateListeners(event);
                    return true;//阻止后面的监听器处理 
                }
                
                CCASSERT((*touchesIter)->getID() == (*mutabletouchesIter)->getID(),"");
                
		//吞噬事件:方式二
                if (isClaimed && listener->_isRegistered && listener->_needSwallow)
                {
                    if (isNeedsMutableSet)
                    {
			//由于该点的触摸事件被吞噬了,后面多点触摸的时候就不会触发了这个触摸点了
                        mutabletouchesIter = mutabletouches.erase(mutabletouchesIter);
                        isSwallowed = true;
                    }
                    return true;//阻止后面的监听器处理
                }
                
                return false;
            };
            
            //
            dispatchEventToListeners(oneByOneListeners,onTouchEvent);
            if (event->isstopped())//此种吞噬事件更强大,stop掉以后,不仅仅影响单点触摸接下的监听器,还回阻止多点触摸的处理
            {
                return;
            }
            
            if (!isSwallowed)
                ++mutabletouchesIter;
        }
    }
    //接下来就是 多点触摸
    // process standard handlers 2nd
    //
    if (allAtOnceListeners && mutabletouches.size() > 0)//如果还有触摸点的事件没有被吞噬
    {
        
       //.........省略部分代码,就是触发回调而已..............
    }
    
    updateListeners(event);
}

对排好序的监听器列表的每个监听器进行事件触发,先触发优先级小于0的,然后是sceneGraPHPriorityListeners,最后是优先级大于0的
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())
        {
           //..........省略部分代码................
        }
    }
    if (sceneGraPHPriorityListeners)
    {
        if (!shouldStopPropagation)
        {
            // priority == 0,scene graph priority
            for (auto& l : *sceneGraPHPriorityListeners)
            {
                 //..........省略部分代码................
            }
        }
    }
    if (fixedPriorityListeners)
    {
        if (!shouldStopPropagation)
        {
            // priority > 0
            ssize_t size = fixedPriorityListeners->size();
            for (; i < size; ++i)
            {
                //..........省略部分代码................
            }
        }
    }
}

前面经常说监听器排序,接下来就看看怎么排的序,fixedListeners的排序还是挺简单的,整个列表和一个排序函数就搞定了,但是sceneGraphListeners就比较麻烦了,首先它要去计算所有子节点的深度,深度取决于globalZorder和localZorder,并且访问一个节点的时候,要把按照localZorder从低到高的顺序进行访问,并且在localZorder=0的时候把本节点加进去,然后再去访问localZorder>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()];
    });
 //..........省略部分代码................
}

具体计算深度:
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())
        {
		//访问完了localZorder<0的子节点,将节点加入到对应的globalZorder列表中去,注意globalZorder列表加入的顺序也是很有序的。
            _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())
        {
		//叶节点,将节点加入到对应的globalZorder列表中去
            _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();
    }
}

cocos2d3.2 事件机制源码分析的更多相关文章

  1. iOS推送通知优先级

    我已设置推送通知并正常工作,但是,有时我会遇到终端设备上的延迟交付.有没有办法我可以将推送的“优先级”键设置为10,以便立即发送推送?

  2. ios – 何时使用Semaphore而不是Dispatch Group?

    我会假设我知道如何使用DispatchGroup,为了解问题,我尝试过:结果–预期–是:为了使用信号量,我实现了:并在viewDidLoad方法中调用它.结果是:从概念上讲,dispachGroup和Semaphore都有同样的目的.老实说,我不熟悉:什么时候使用信号量,尤其是在与dispachGroup合作时–可能–处理问题.我错过了什么部分?

  3. 如何使用Xcode的自动布局调整视图大小

    解决方法在写这个问题时,我意识到了诀窍是什么:在NSPopUpButton的大小检查器中,我不得不降低内容拥抱优先级.显然,这可以控制视图“拥抱”其内容的紧密程度.因此,当拥抱优先级高于调整大小优先级时,视图将不希望增加其大小,因为这意味着其边界与其内容之间具有更多的空白空间.然后在我的特殊情况下,我也可以将两个NSPopUpButtons固定为具有相同的宽度和vo:popUpButtons将完美地调整大小,同时保持间距不变.

  4. ios – 默认的自动布局内容拥抱和内容压缩阻抗优先级值是什么?

    我正在尝试调试自动布局问题,并且知道内容拥抱和内容压缩阻力优先级的默认值将有所帮助.这些是什么?它们是否特定于特定组件?我可以使用常量来引用它们吗?

  5. ios – 链接点击监听器上的WKWebView?

    在WKWebView类中是否存在类似onLinkClickListener的东西?我试着谷歌搜索但没有发现任何东西,我也发现了一些关于simillar类型的stackoverflow的未解答的问题.我需要一个linkClickListener的原因是,当我点击链接并且页面尚未加载时,它不会加载网站.当页面加载了监听器时,我也可以创建一个花哨的加载屏幕.解决方法你可以这样做将WKNavigation

  6. 多个监听器用于委托iOS

    我有一个带有代理didSelectString的类搜索栏.我有一个实现委托的A类和一个实现委托的B类.但是只有来自A类的代理才被执行.代表可以有多个监听器吗?并且如何实现这一点解决方法该委托是单一消息传递协议.如果要发送更改的多个对象,则需要使用NSNotifications.您可以使用通知中心传递对象,如下所示:想要收听通知时并设置选择器

  7. ios – 为自定义创建的串行异步队列设置优先级

    如何使用GCD为自定义创建的串行异步队列设置高优先级?如果是这样,什么是替代解决方案?解决方法您的队列仍然是串行的.它只会在高优先级全局并发后台队列的一个插槽中一次执行一项任务.一旦创建,串行队列就不能以任何方式“并发”.同样,如果您创建并发队列并将其设置为以串行队列为目标,则它实际上变为串行.这一切都在manpage中有所涉及.

  8. iOS 9中UILabel中的多行文本

    我正在开发iOS项目但是当我更新到iOS9时,我在UILabels中遇到了多线问题.我正在使用Autolayout.有谁知道如何在iOS9中做到这一点?如果是这样,那么问题可能是标签内容压缩阻力优先级太低,尝试将其设置为required或1000.内容压缩阻力告诉视图引擎您的标签可以缩小的优先级.将其设置为必需会强制它不缩小.在InterfaceBuilder中,只需选择标签,点击SizeInspector(小标尺),然后将其更改为1000.或者,在代码中,等价物将是:

  9. ios – AutoLayout修改约束

    我发现Autolayout有点困难,所以任何帮助都会非常感激解决方法嗨,你可以做2套约束:>1以优先级高管理您的四视图>1以优先级低管理全屏在点击按钮时调用的方法中,将优先级设置为全屏约束,将优先级设置为四视图约束.

  10. Swift 运算符重载

    但是现在还有另外一个Swift的特性,你应该知道并且会爱上它,它就是运算符重载。例如:我们在SwiftSpriteKitutilitylibrary代码中使用运算符重载去讲多个CGPoints对象相加,例如下面代码:1234letpt1=CGPointletpt2=CGPointletpt3=pt1+pt2letpt4=pt3*100方便吧?当一个人查看你的代码,他们希望操作符的默认行为,这时候运算符重载会使他们迷惑。幸运的是Swift让你能够定义属于你自己的自定义的运算符。

随机推荐

  1. 【cocos2d-x 3.x 学习笔记】对象内存管理

    Cocos2d-x的内存管理cocos2d-x中使用的是上面的引用计数来管理内存,但是又增加了一些自己的特色。cocos2d-x中通过Ref类来实现引用计数,所有需要实现内存自动回收的类都应该继承自Ref类。下面是Ref类的定义:在cocos2d-x中创建对象通常有两种方式:这两中方式的差异可以参见我另一篇博文“对象创建方式讨论”。在cocos2d-x中提倡使用第二种方式,为了避免误用第一种方式,一般将构造函数设为protected或private。参考资料:[1]cocos2d-x高级开发教程2.3节[

  2. 利用cocos2dx 3.2开发消灭星星六如何在cocos2dx中显示中文

    由于编码的不同,在cocos2dx中的Label控件中如果放入中文字,往往会出现乱码。为了方便使用,我把这个从文档中获取中文字的方法放在一个头文件里面Chinese.h这里的tex_vec是cocos2dx提供的一个保存文档内容的一个容器。这里给出ChineseWords,xml的格式再看看ChineseWord的实现Chinese.cpp就这样,以后在需要用到中文字的地方,就先include这个头文件然后调用ChineseWord函数,获取一串中文字符串。

  3. 利用cocos2dx 3.2开发消灭星星七关于星星的算法

    在前面,我们已经在GameLayer中利用随机数初始化了一个StarMatrix,如果还不知道怎么创建星星矩阵请回去看看而且我们也讲了整个游戏的触摸事件的派发了。

  4. cocos2dx3.x 新手打包APK注意事项!

    这个在编译的时候就可以发现了比较好弄这只是我遇到的,其他的以后遇到再补充吧。。。以前被这两个问题坑了好久

  5. 利用cocos2dx 3.2开发消灭星星八游戏的结束判断与数据控制

    如果你看完之前的,那么你基本已经拥有一个消灭星星游戏的雏形。开始把剩下的两两互不相连的星星消去。那么如何判断是GameOver还是进入下一关呢。。其实游戏数据贯穿整个游戏,包括星星消除的时候要加到获得分数上,消去剩下两两不相连的星星的时候的加分政策等,因此如果前面没有做这一块的,最好回去搞一搞。

  6. 利用cocos2dx 3.2开发消灭星星九为游戏添加一些特效

    needClear是一个flag,当游戏判断不能再继续后,这个flag变为true,开始消除剩下的星星clearSumTime是一个累加器ONE_CLEAR_TIME就是每颗星星消除的时间2.连击加分信息一般消除一次星星都会有连击信息和加多少分的信息。其实这些combo标签就是一张图片,也是通过控制其属性或者runAction来实现。源码ComboEffect.hComboEffect.cpp4.消除星星粒子效果消除星星时,为了实现星星爆裂散落的效果,使用了cocos2d提供的粒子特效引擎对于粒子特效不了

  7. 02 Cocos2D-x引擎win7环境搭建及创建项目

    官网有搭建的文章,直接转载记录。环境搭建:本文介绍如何搭建Cocos2d-x3.2版本的开发环境。项目创建:一、通过命令创建项目前面搭建好环境后,怎样创建自己的Cocos2d-x项目呢?先来看看Cocos2d-x3.2的目录吧这就是Cocos2d-x3.2的目录。输入cocosnew项目名–p包名–lcpp–d路径回车就创建成功了例如:成功后,找到这个项目打开proj.win32目录下的Hello.slnF5成功了。

  8. 利用cocos2dx 3.2开发消灭星星十为游戏添加音效项目源码分享

    一个游戏,声音也是非常的重要,其实cocos2dx里面的简单音效引擎的使用是非常简单的。我这里只不过是用一个类对所有的音效进行管理罢了。Audio.hAudio.cpp好了,本系列教程到此结束,第一次写教程如有不对请见谅或指教,谢谢大家。最后附上整个项目的源代码点击打开链接

  9. 03 Helloworld

    程序都有一个入口点,在C++就是main函数了,打开main.cpp,代码如下:123456789101112131415161718#include"main.h"#include"AppDelegate.h"#include"cocos2d.h"USING_NS_CC;intAPIENTRY_tWinMain{UNREFERENCED_ParaMETER;UNREFERENCED_ParaMETER;//createtheapplicationinstanceAppDelegateapp;return

  10. MenuItemImage*图标菜单创建注意事项

    学习cocos2dx,看的是cocos2d-x3.x手游开发实例详解,这本书错误一大把,本着探索求知勇于发现错误改正错误的精神,我跟着书上的例子一起调试,当学习到场景切换这个小节的时候,出了个错误,卡了我好几个小时。

返回
顶部