在游戏中,触摸是最基本的,必不可少的。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.一个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; };
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后才可以发生
个人总结,肯定会有很多不足之处,希望路过的旁友们指出。。。