cocos2dx的事件機(jī)制里存在三類:Event、EventListener、EventDispatcher
先理解一下它們之間的關(guān)系
當(dāng)我們按下按鈕時(shí)(Event),會(huì)觸發(fā)一個(gè)特定的事件(EventListener相當(dāng)于回調(diào)函數(shù)),而這個(gè)特定的事件又存儲(chǔ)在EventDispatcher里,可能按下這個(gè)按鈕會(huì)觸發(fā)多個(gè)事件芽突,而事件的先后就是靠EventDispatcher來決定的。
Event的相關(guān)類
Event(基類), EventCustom(自定義事件), EventTouch(觸摸事件), EventMouse(鼠標(biāo)事件), EventKeyboard(鍵盤事件), EventFocus(控件獲取焦點(diǎn)事件), EventAcceleration(加速計(jì)事件)
1. Event
1.1 事件是什么
當(dāng)出現(xiàn)來自鼠標(biāo)董瞻,鍵盤寞蚌,觸屏,搖桿等輸入源的輸入時(shí)钠糊,這個(gè)事實(shí)稱之為事件
1.2 cocos2dx是如何處理事件
引擎無時(shí)無刻都在感受事件挟秤。
在循環(huán)中每一幀會(huì)調(diào)用pollEvents來檢測外部事件,一旦有事件發(fā)生抄伍,就會(huì)調(diào)用EventDispatcher::dispatchEvent(Event* event)艘刚,來判斷事件是否需要處理,給誰處理的后續(xù)問題截珍。
1.3 源碼分析
Event
enum class Type
{
TOUCH,
KEYBOARD,
ACCELERATION,
MOUSE,
FOCUS,
CUSTOM
};
Type _type; ///< Event type
bool _isStopped; ///< whether the event has been stopped.
Node* _currentTarget;
可以看出Event主要包含三個(gè)變量攀甚,一個(gè)是事件類型_type(也就是定義的枚舉類型:觸摸、鍵盤等)岗喉,isStopped判斷事件是否停止秋度,只要事件停止,其相關(guān)的Listener都要停止callback調(diào)用钱床。
EventTouch
它對應(yīng)于四種觸摸操作荚斯,不同的EventCode可以告訴Listener來調(diào)用不同的callback。
enum class EventCode
{
BEGAN,
MOVED,
ENDED,
CANCELLED
};
EventCustom
它是用戶自定義事件,userData記錄用戶自定義數(shù)據(jù)事期,另一個(gè)eventName是用戶給事件取的別名
void* _userData; ///< User data
std::string _eventName;
2. EventListener
std::function<void(Event*)> _onEvent; /// Event callback function
Type _type; /// Event listener type
ListenerID _listenerID; /// Event listener ID
bool _isRegistered; /// Whether the listener has been added to dispatcher.
int _fixedPriority; // The higher the number, the higher the priority, 0 is for scene graph base priority.
Node* _node; // scene graph based priority
bool _paused; // Whether the listener is paused
bool _isEnabled; // Whether the listener is enabled
上面的源碼都有英文注釋滥壕,我就不多解釋了,我只說一個(gè)最重要的_isRegistered兽泣,它判斷事件有沒有被注冊绎橘,如果沒有被注冊就不會(huì)觸發(fā)。(如何注冊事件撞叨?將事件加入dispatcher)
3. EventDispatch
在講它之前,我們先了解一下它的一個(gè)重要變量浊洞。
std::vector<EventListener*>* _fixedListeners;
std::vector<EventListener*>* _sceneGraphListeners;
sceneGraphListeners:一個(gè)事件(比如說觸摸事件)牵敷,需要按照一定的響應(yīng)序列,依次對這些Node進(jìn)行事件響應(yīng)法希,所以該類型的事件都會(huì)綁定一個(gè)與此相關(guān)聯(lián)的node枷餐,并且響應(yīng)順序是與node在scene下的zorder相關(guān)的。該類型下的事件優(yōu)先級統(tǒng)一為0苫亦。(與渲染樹有關(guān))
fixedListeners:優(yōu)先級根據(jù) fixedPriority 的數(shù)值從小往大排序毛肋、
3.1 事件是如何分發(fā)的(非觸摸事件)
- 先獲取事件的監(jiān)聽ID
- 根據(jù)這個(gè)監(jiān)聽ID對這個(gè)事件的所有監(jiān)聽器進(jìn)行排序
先分發(fā)事件到 fixedPriority < 0 的監(jiān)聽器中,然后再分發(fā)到 = 0 的監(jiān)聽器(SceneGraphPriority)中屋剑,最后在分發(fā)到 > 0 的監(jiān)聽器中润匙,如果中途出現(xiàn) onEvent 返回為 true 的結(jié)果,則終止分發(fā) - 獲取事件的所有監(jiān)聽器唉匾,根據(jù)排序一個(gè)一個(gè)觸發(fā)回調(diào)函數(shù)
- 如果對當(dāng)前監(jiān)聽ID孕讳,新增加或刪除監(jiān)聽器,那么DirtyFlag就會(huì)標(biāo)記為true巍膘,該監(jiān)聽ID就需要重新進(jìn)行排序厂财。如果當(dāng)時(shí)正在分發(fā)事件,會(huì)把當(dāng)前需要添加的監(jiān)聽器添加到待添加向量(_toAddedListeners)中峡懈,在事件分發(fā)完畢之后監(jiān)聽器需要從toAddedListeners中轉(zhuǎn)移到正式向量中璃饱。
void EventDispatcher::dispatchEvent(Event* event)
{
...
// 先通過event獲取到事件的標(biāo)志ListenerID
auto listenerID = __getListenerID(event);
// 排序此事件的所有的監(jiān)聽器
sortEventListeners(listenerID);
// 分發(fā)事件邏輯的函數(shù)指針
auto pfnDispatchEventToListeners = &EventDispatcher::dispatchEventToListeners;
if (event->getType() == Event::Type::MOUSE) {
// 如果是鼠標(biāo)事件重新賦值分發(fā)事件的函數(shù)指針
pfnDispatchEventToListeners = &EventDispatcher::dispatchTouchEventToListeners;
}
// 獲取改事件的所有的監(jiān)聽器
auto iter = _listenerMap.find(listenerID);
if (iter != _listenerMap.end())
{
// 如果有,取出里面監(jiān)聽器的Vector
auto listeners = iter->second;
// 找到對應(yīng)的監(jiān)聽器的時(shí)候會(huì)觸發(fā)的回調(diào)函數(shù)
auto onEvent = [&event](EventListener* listener) -> bool{
event->setCurrentTarget(listener->getAssociatedNode());
// 觸發(fā)onEvent回調(diào)
listener->_onEvent(event);
return event->isStopped();
};
// 調(diào)用函數(shù)指針分發(fā)事件
(this->*pfnDispatchEventToListeners)(listeners, onEvent);
}
...
}
3.2 dispatchTouchEvent(觸摸事件的分發(fā)機(jī)制)
- listener根據(jù)Node的globlal ZOrder優(yōu)先級排序后肪康,依次響應(yīng)荚恶。
- 當(dāng)進(jìn)入TouchEvent Began后,所有監(jiān)聽事件都會(huì)依次影響Touch Began磷支。然后再依次響應(yīng)Touch Move等裆甩,而不是一個(gè)一個(gè)由Began->Move->End
3.3 cocos監(jiān)聽事件的bug
只要出現(xiàn)了刪除,修改齐唆,添加監(jiān)聽器的時(shí)候嗤栓,監(jiān)聽器列表需要重新排序,都需要設(shè)置相應(yīng)的 DirtyFlag 操作。但是 Cocos-2dx v3.10 里面的 updateListeners 函數(shù)有刪除監(jiān)聽器的操作茉帅,然而并沒有設(shè)置相應(yīng)的 DirtyFlag 操作叨叙。
會(huì)拋出下面的異常
CCASSERT(listeners->getGt0Index() <= static_cast<ssize_t>(fixedPriorityListeners->size()), "Out of range exception!");
Gt0Index() 方法其實(shí)就是獲取到當(dāng)前監(jiān)聽器中 fixedPriority == 0 的監(jiān)聽器在監(jiān)聽器向量中的位置,它只有在給 Listener 排序的時(shí)候會(huì)設(shè)置堪澎,但是如果更新了對應(yīng) ListenerID 的向量(EventListenerVector)擂错,但是沒有重新排序,就會(huì)出現(xiàn) _gt0Index 未及時(shí)更新的情況樱蛤,導(dǎo)致拋出這個(gè)異常钮呀。
引用:
Cocos2dx游戲引擎(3.x)----新的事件分發(fā)機(jī)制
cocos2dx之event事件(一)
cocos2dx之event事件(三):事件分發(fā)器EventDispatcher
Cocos2dx-v3.10 事件分發(fā)機(jī)制源碼解析