zoukankan      html  css  js  c++  java
  • 【Cocos2d-x 3.x】 事件处理机制源码分析

    在游戏中,触摸是最基本的,必不可少的。Cocos2d-x 3.x中定义了一系列事件,同时也定义了负责监听这些事件的监听器,另外,cocos定义了事件分发类,用来将事件派发出去以便可以实现相应的事件。


    触摸事件

    Event

    Cocos2d-x 3.x定义了事件基类Event,基于Event,引擎派生出几种事件:

        enum class Type
        {
            TOUCH, // 触摸事件
            KEYBOARD, // 键盘事件
            ACCELERATION, // 加速度事件
            MOUSE,// 鼠标事件
            FOCUS,// 焦点事件
            CUSTOM // 自定义事件
        };

    EventTouch

    EventTouch是触摸事件中非常重要的一类事件,它定义了四种touch操作:

    enum class EventCode
    {
        BEGAN,
        MOVED,
        ENDED,
        CANCELLED
    };

    它还定义了一个 static int值,表示最大的触摸点数为15点:

    static const int MAX_TOUCHES = 15;

    事件监听器

    EventListener

    EventListener是事件监听器的基类,派生出的监听器对应各种触摸事件:

    enum class Type
    {
        UNKNOWN,
        TOUCH_ONE_BY_ONE,
        TOUCH_ALL_AT_ONCE,
        KEYBOARD,
        MOUSE,
        ACCELERATION,
        FOCUS,
        CUSTOM
    };

     重要的成员变量:
    1.onEvent,是绑定于该Listener的callback function,该func的声明使用了c++11
    2.type与Event类似,增加一个Unknown的属性。
    3.isRegistered变量非常重要,如果他没有被注册,则他的事件不会触发。
    4.优先级代表了响应一个事件时的顺序,该值越低,越先响应。
    5.node 代表了与该listener相关的node,用于 scene graph类的事件响应,具体的在Dispatcher里面有进行介绍。
    6.ListenerID,这是该类型事件的标识符。除了EventCustomListener的ListerID是与name相关的,其余的ListenerID都是固定的,用于标识该类EventListener。


    另外:

    1.一个Listener想接收事件必须是enabled true 并且 paused false。

    2.值得注意的是,pause的变量专门是为了scenGraph类的事件存在的(后续有说明),而且一个Node的onEnter和onExit 事件会影响到与Node相关的该类事件的pause状态。


    EventListenerTouchOneByOne

    单点触摸方式,实现它时需要重写父类的四种触摸方式的函数:

        /// Overrides
        virtual EventListenerTouchOneByOne* clone() override;
        virtual bool checkAvailable() override;
        //
    
    public:
        std::function<bool(Touch*, Event*)> onTouchBegan;
        std::function<void(Touch*, Event*)> onTouchMoved;
        std::function<void(Touch*, Event*)> onTouchEnded;
        std::function<void(Touch*, Event*)> onTouchCancelled;

    另外, 单点触摸当onTouchBegan函数不是nullptr时,它就是可用的:

    bool EventListenerTouchOneByOne::checkAvailable()
    {
        // EventDispatcher will use the return value of 'onTouchBegan' to determine whether to pass following 'move', 'end'
        // message to 'EventListenerTouchOneByOne' or not. So 'onTouchBegan' needs to be set.
        if (onTouchBegan == nullptr)
        {
            CCASSERT(false, "Invalid EventListenerTouchOneByOne!");
            return false;
        }
        
        return true;
    }

    还有,EventListenerTouchOneByOne可以设置吞噬。


    EventListenerAllAtOnce

    多点触摸,当四种触摸方式函数都不为nullptr时,EventListenerAllAtOnce时可用的:


    bool EventListenerTouchAllAtOnce::checkAvailable()
    {
        if (onTouchesBegan == nullptr && onTouchesMoved == nullptr
            && onTouchesEnded == nullptr && onTouchesCancelled == nullptr)
        {
            CCASSERT(false, "Invalid EventListenerTouchAllAtOnce!");
            return false;
        }
        
        return true;
    }


    EventListenerCustom


    EventListenerID是根据独特的Name进行命名的,值得注意的是请确保你的name是具有唯一性的,否在在DispatchCustomEvent时会有问题。


    事件分发器EventDispatcher

    首先,先看一个内嵌类EventListenerVector:

    class EventListenerVector
        {
        public:
            EventListenerVector();
            ~EventListenerVector();
            size_t size() const;
            bool empty() const;
            
            void push_back(EventListener* item);
            void clearSceneGraphListeners();
            void clearFixedListeners();
            void clear();
            
            inline std::vector<EventListener*>* getFixedPriorityListeners() const { return _fixedListeners; };
            inline std::vector<EventListener*>* getSceneGraphPriorityListeners() const { return _sceneGraphListeners; };
            inline ssize_t getGt0Index() const { return _gt0Index; };
            inline void setGt0Index(ssize_t index) { _gt0Index = index; };
        private:
            std::vector<EventListener*>* _fixedListeners;
            std::vector<EventListener*>* _sceneGraphListeners;
            ssize_t _gt0Index;
        };

    fixedListeners和sceneGraphListeners是两个非常非常重要的变量,这是两个截然不同的Listener列表。
            1. sceneGraph类型的事件,是与当前正在运行的scene下node相关的事件,也就是说一个事件(比如说触摸事件),需要按照一定的响应序列,依次对这些Node进行事件响应,所以该类型的事件都会绑定一个与此相关联的node,并且响应顺序是与node在scene下的zorder相关的。该类型下的事件优先级统一为0.
            2. fixed类型的事件相对就比较简单了,但是有一个限制就是其优先级不能为0.
    在EventDispatcher的成员变量中有一个map :std::unordered_map<EventListener::ListenerID, EventListenerVector*> _listenerMap;  一种ListenerID对应了一个Vector。


    EventDispatcher有三种添加事件的方式:addEventListenerWithSceneGraphPriority、addEventListenerWithFixedPriority和addCustomEventListener

    void EventDispatcher::addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node)
    {
        CCASSERT(listener && node, "Invalid parameters.");
        CCASSERT(!listener->isRegistered(), "The listener has been registered.");
        
    	//检查Listener可用性
        if (!listener->checkAvailable())
            return;
        //设置listener相关属性
        listener->setAssociatedNode(node);
        listener->setFixedPriority(0);
        listener->setRegistered(true);
        
        addEventListener(listener);
    }

    addEventListenerWithSceneGraphPriority将监听器和node节点关联起来,addEventListenerWithSceneGraphPriority不需要手动移除监听器,因为在node的析构函数中会自动移除的,还有,addEventListenerWithSceneGraphPriority设置监听器的优先权为0。优先权值越小,越先派发


    void EventDispatcher::addEventListenerWithFixedPriority(EventListener* listener, int fixedPriority)
    {
        CCASSERT(listener, "Invalid parameters.");
    	//一个事件只能被注册一次
        CCASSERT(!listener->isRegistered(), "The listener has been registered.");
    	//Fixed类型的事件优先级不能是0
        CCASSERT(fixedPriority != 0, "0 priority is forbidden for fixed priority since it's used for scene graph based priority.");
        
    	//检查可用性
        if (!listener->checkAvailable())
            return;
        
    	//设置关联属性
        listener->setAssociatedNode(nullptr);
        listener->setFixedPriority(fixedPriority);
        listener->setRegistered(true);
        listener->setPaused(false);
    
        addEventListener(listener);
    }

    addEventListenerWithFixedPriority不能将监听器的优先权设置为0。

    EventListenerCustom* EventDispatcher::addCustomEventListener(const std::string &eventName, const std::function<void(EventCustom*)>& callback)
    {
    	//custom类的事件添加是通过eventName 和 eventcallBack来进行添加的
        EventListenerCustom *listener = EventListenerCustom::create(eventName, callback);
    	
    	//custom的事件优先级被默认为1
        addEventListenerWithFixedPriority(listener, 1);
    	
        return listener;
    }

    addCustomEventListener通过eventName和eventcallBack来添加的,自定以监听器优先权默认为1.。


    这三种方法都使用了addEventListener函数来进行实际操作:

    void EventDispatcher::addEventListener(EventListener* listener)
    {
    	//如果当前Dispatcher正在进行事件Dispatch,则放到toAddList中。
        if (_inDispatch == 0)
        {
            forceAddEventListener(listener);
        }
        else
        {
    		// std::vector
            _toAddedListeners.push_back(listener);
        }
    
        listener->retain();
    }

    如果当前派发器没有进行事件的派发,则强制进行,并添加该事件监听器,该操作由forceAddEventListener来完成:

    void EventDispatcher::forceAddEventListener(EventListener* listener)
    {
        EventListenerVector* listeners = nullptr;
        EventListener::ListenerID listenerID = listener->getListenerID();
    	
    	//找到该类eventlistener的vector,此处的vector是EventVector
        auto itr = _listenerMap.find(listenerID);
    	//如果没有找到,则需要向map中添加一个pair
        if (itr == _listenerMap.end())
        {
            
            listeners = new EventListenerVector();
            _listenerMap.insert(std::make_pair(listenerID, listeners));
        }
        else
        {
            listeners = itr->second;
        }
        //将该类别listenerpush_back进去(这个函数调用的是EventVector的pushback哦)
        listeners->push_back(listener);
        
    	//如果优先级是0,则设置为graph。
        if (listener->getFixedPriority() == 0)
        {
    		//设置该listenerID的DirtyFlag
    		//(setDirty函数可以这样理解,每个ListenerID都有特定的dirtyFlag,每次进行add操作后,都要更新该ID的flag)
            setDirty(listenerID, DirtyFlag::SCENE_GRAPH_PRIORITY);
            
    		//如果是sceneGraph类的事件,则需要处理两个方面:
    		//1.将node 与event 关联
    		//2.如果该node是运行中的,则需要恢复其事件(因为默认的sceneGraph listener的状态时pause)
    		//增加该listener与node的关联
            auto node = listener->getAssociatedNode();
            CCASSERT(node != nullptr, "Invalid scene graph priority!");
            
            associateNodeAndEventListener(node, listener);
            
    		//恢复node的运行状态
            if (node->isRunning())
            {
                resumeEventListenersForTarget(node);
            }
        }
        else
        {
            setDirty(listenerID, DirtyFlag::FIXED_PRIORITY);
        }
    }

    如果优先级是0,则还有将监听器与node关联起来的操作:

    void EventDispatcher::associateNodeAndEventListener(Node* node, EventListener* listener)
    {
    	//将listener与node关联,先从map中找到与该node相关的listener vector
        std::vector<EventListener*>* listeners = nullptr;
        auto found = _nodeListenersMap.find(node);
        if (found != _nodeListenersMap.end())
        {
            listeners = found->second;
        }
        else
        {
            listeners = new std::vector<EventListener*>();
            _nodeListenersMap.insert(std::make_pair(node, listeners));
        }
        //vector内添加该listener,这里的vector 是std::vector
        listeners->push_back(listener);
    }

    DispatchEvent(核心)

    touch事件和其他事件的分发是不同的:

    void EventDispatcher::dispatchEvent(Event* event)
    {
        if (!_isEnabled)
            return;
        
    	//为dirtyNodesVector中的dirtyNode更新Scene Flag。
        updateDirtyFlagForSceneGraph();
        
        
        DispatchGuard guard(_inDispatch);
        
    	//特殊touch事件,转到特殊的touch事件处理
        if (event->getType() == Event::Type::TOUCH)
        {
            dispatchTouchEvent(static_cast<EventTouch*>(event));
            return;
        }
        
    	//根据事件的类型,获取事件的ID
        auto listenerID = __getListenerID(event);
        
    	//根据事件ID,将该类事件进行排序(先响应谁)
        sortEventListeners(listenerID);
        
        auto iter = _listenerMap.find(listenerID);
        if (iter != _listenerMap.end())
        {
            auto listeners = iter->second;
            //该类事件的lambda函数
            auto onEvent = [&event](EventListener* listener) -> bool{
    			//设置event的target
                event->setCurrentTarget(listener->getAssociatedNode());
    			//调用响应函数
                listener->_onEvent(event);
    			//返回是否已经停止
                return event->isStopped();
            };
            
    		//将该类事件的listeners 和 该类事件的 lambda函数传给该函数
            dispatchEventToListeners(listeners, onEvent);
        }
        
    	//更新该事件相关的listener
        updateListeners(event);
    }

    先看看touch事件的分发机制:


    void EventDispatcher::dispatchTouchEvent(EventTouch* event)
    {
    	//先将EventListeners排序
        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;
        
    	//mutableTouches是用来处理allAtOnce的
        bool isNeedsMutableSet = (oneByOneListeners && allAtOnceListeners);
        
    	//这些touch都来自该事件
        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();
            //遍历touches,每一个touch都来自于同一个事件
            for (; touchesIter != originalTouches.end(); ++touchesIter)
            {
                bool isSwallowed = false;
    
    			//事件处理的lambda函数
                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);
                    //claimed代表该listener是否接收了该touch(Began返回true or false)
                    bool isClaimed = false;
                    std::vector<Touch*>::iterator removedIter;
                    
    				//根据eventNode的不同,会调用不同的callBack函数
                    EventTouch::EventCode eventCode = event->getEventCode();
                    
                    if (eventCode == EventTouch::EventCode::BEGAN)
                    {
    					//调用began
                        if (listener->onTouchBegan)
                        {
                            isClaimed = listener->onTouchBegan(*touchesIter, event);
                            if (isClaimed && listener->_isRegistered)
                            {
    							//返回true后 将该touch放入该listener的claimedTouches
                                listener->_claimedTouches.push_back(*touchesIter);
                            }
                        }
                    }
    				//如果是后三个move end cancel
                    else if (listener->_claimedTouches.size() > 0
                             && ((removedIter = std::find(listener->_claimedTouches.begin(), listener->_claimedTouches.end(), *touchesIter)) != listener->_claimedTouches.end()))
                    {
                        isClaimed = true;
                        //调用相应的callBack
                        switch (eventCode)
                        {
                            case EventTouch::EventCode::MOVED:
                                if (listener->onTouchMoved)
                                {
                                    listener->onTouchMoved(*touchesIter, event);
                                }
                                break;
                            case EventTouch::EventCode::ENDED:
                                if (listener->onTouchEnded)
                                {
                                    listener->onTouchEnded(*touchesIter, event);
                                }
                                if (listener->_isRegistered)
                                {
                                    listener->_claimedTouches.erase(removedIter);
                                }
                                break;
                            case EventTouch::EventCode::CANCELLED:
                                if (listener->onTouchCancelled)
                                {
                                    listener->onTouchCancelled(*touchesIter, event);
                                }
                                if (listener->_isRegistered)
                                {
                                    listener->_claimedTouches.erase(removedIter);
                                }
                                break;
                            default:
                                CCASSERT(false, "The eventcode is invalid.");
                                break;
                        }
                    }
                    
                    // If the event was stopped, return directly.
                    if (event->isStopped())
                    {
                        updateListeners(event);
                        return true;
                    }
                    
                    CCASSERT((*touchesIter)->getID() == (*mutableTouchesIter)->getID(), "");
                    
    				//如果接收该touch并且需要吞噬该touch,会有两个影响
    				//1.Touches(standard 触摸机制)的触摸操作都接收不到该touch了
    				//2.因为返回值是true,在调用dispatchEventToListeners时,在该node之后的node将会不再接收该touch
                    if (isClaimed && listener->_isRegistered && listener->_needSwallow)
                    {
                        if (isNeedsMutableSet)
                        {
                            mutableTouchesIter = mutableTouches.erase(mutableTouchesIter);
                            isSwallowed = true;
                        }
                        return true;
                    }
                    
                    return false;
                };
                
                //结合上面的dispatchEventToListeners的源码分析,可以看出新版本的OneByOne touch机制是这样的:
    			//1.listener根据Node的优先级排序后,依次响应。值得注意的是,新版本的优先级是根据Node的global Zorder来的,而不是2.x的触摸优先级。
    			//2.当TouchEvent Began来了之后,所有的listener会依次影响Touch Began。然后再依次响应Touch Move...而不是一个listener响应完
    			//began move end之后 轮到下一个listener响应的顺序。
    			//3.吞噬操作只有发生在began return true后才可以发生
                dispatchEventToListeners(oneByOneListeners, onTouchEvent);
                if (event->isStopped())
                {
                    return;
                }
                
                if (!isSwallowed)
                    ++mutableTouchesIter;
            }
        }
        
        //
        // process standard handlers 2nd
        //
    	//相比于OneByOne,AllAtOnce要简单许多。值得注意的是被吞噬的touch也不会被AllAtOnce响应到
        if (allAtOnceListeners && mutableTouches.size() > 0)
        {
            
            auto onTouchesEvent = [&](EventListener* l) -> bool{
                EventListenerTouchAllAtOnce* listener = static_cast<EventListenerTouchAllAtOnce*>(l);
                // Skip if the listener was removed.
                if (!listener->_isRegistered)
                    return false;
                
                event->setCurrentTarget(listener->_node);
                
                switch (event->getEventCode())
                {
                    case EventTouch::EventCode::BEGAN:
                        if (listener->onTouchesBegan)
                        {
                            listener->onTouchesBegan(mutableTouches, event);
                        }
                        break;
                    case EventTouch::EventCode::MOVED:
                        if (listener->onTouchesMoved)
                        {
                            listener->onTouchesMoved(mutableTouches, event);
                        }
                        break;
                    case EventTouch::EventCode::ENDED:
                        if (listener->onTouchesEnded)
                        {
                            listener->onTouchesEnded(mutableTouches, event);
                        }
                        break;
                    case EventTouch::EventCode::CANCELLED:
                        if (listener->onTouchesCancelled)
                        {
                            listener->onTouchesCancelled(mutableTouches, event);
                        }
                        break;
                    default:
                        CCASSERT(false, "The eventcode is invalid.");
                        break;
                }
                
                // If the event was stopped, return directly.
                if (event->isStopped())
                {
                    updateListeners(event);
                    return true;
                }
                
                return false;
            };
            
            dispatchEventToListeners(allAtOnceListeners, onTouchesEvent);
            if (event->isStopped())
            {
                return;
            }
        }
        
        updateListeners(event);
    }

    在第34行代码开始可以看到,如果单点触摸的onTouchBegan函数返回值不是true,那么后面的onTouchMoved、onTouchEnded和onTouchCancelled也就不会触发了。


    3.x中的OneByOne机制:
              1.listener根据Node的优先级排序后,依次响应。值得注意的是,新版本的优先级是根据Node的global Zorder来的,而不是2.x的触摸优先级。
    2.当TouchEvent Began来了之后,所有的listener会依次影响Touch Began。然后再依次响应Touch Move...而不是一个listener响应完  began move end之后 轮到下一个listener响应的顺序。
    3.吞噬操作只有发生在began return true后才可以发生



    个人总结,肯定会有很多不足之处,希望路过的旁友们指出。。。



  • 相关阅读:
    中国软件杯——基于计算机视觉的交通场景智能应用
    《架构实践--软件架构设计的过程》读书笔记
    《架构实践--软件架构设计的过程》读书笔记
    软件架构师Refined Architecture部分读后感
    pycharm安装TensorFlow失败如何解决
    python聚类树图
    极限测试三
    极限测试进度2
    极限测试进度1
    赛题分析
  • 原文地址:https://www.cnblogs.com/averson/p/5149996.html
Copyright © 2011-2022 走看看