Cocos2d-x 3.0 事件系統【轉】

事件系統,是一個軟件的核心組成部分。從小處講它是應用程序內部各模塊交互的設計模式,從大處講,它是軟件架構的組成模塊。在現代軟件開發中,操做系統一般經過一些預約義的事件,告知應用程序發生的一些事情如用戶輸入,內存不足等。
然而,一般咱們並不直接使用系統事件,例如一個界面可能不一樣區域的元素對觸摸事件的理解都不同,在某些狀況下須要優先處理某些邏輯,這就須要對系統事件再包裝,以應對界面複雜的元素和邏輯。另外一方面,咱們須要一個事件系統用來在應用程序內部分發消息,例如當敵人進入攻擊範圍時通知英雄射擊,當敵人血量低於0時播放死亡動畫等等。這些都須要遊戲引擎提供一個靈活的事件系統,它既能管理分發系統事件,還能借助其簡單管理自定義事件。

 

Cocos2d-x 3.0將全部的事件統一集中到EventDispatcher中處理,它不光改進了觸摸等系統事件的管理和使用方式,還使得咱們能夠藉助其處理程序自定義的事件。本章將學習相關的內容。
5.1 事件類型
要處理一個事件,首先得定義一個事件類型。事件系統老是按類型而不是實例來處理事件的訂閱和分發,這樣使得同一個事件能夠有多個訂閱者。Event是全部事件的基類,它用一個字符串來表示該事件的類型。咱們不該該直接使用Event,而應該從它繼承實現自定義事件。事件類型一般不是一個變量,以保證相同類型的事件實例擁有相同的類型,但EventCustom除外,它能夠在初始化的時候指定不一樣的類型,這是爲了簡化編寫事件類型。如下是Event類的定義:

class Event
{
protected:
Event(const std::string& type);node

public:
virtual ~Event();
inline const std::string& getType() const { return _type; };
inline void stopPropagation() { _isStopped = true; };
inline bool isStopped() const { return _isStopped; };
inline Node* getCurrentTarget() { return _currentTarget; };
protected:
inline void setCurrentTarget(Node* target) { _currentTarget = target; };std::string _type;
bool _isStopped;
Node* _currentTarget;
friend class EventDispatcher;
};
實際上Event的成員應該僅包含一個表示類型的字符串,然而在Cocos2d-x中有些事件的分發如觸摸可能和Node的層級相關,因此它還包含一個獲取關聯元素的方法:getCurrentTarget();另外它仍是EventDispatcher的友元,這是爲了方便處理觸摸等事件分發,這些都會在本章後面分析。
一個Event實例其實是事件傳遞過程當中的數據,它由事件觸發者構造,並傳遞給事件分發器,事件分發器根據其類型分別通知全部訂閱該類型事件的訂閱者,並將其做爲參數傳遞給訂閱者。由於事件是一種異步通訊機制,它一般沒有回調,甚至一個類型的事件可能不包含任何訂閱者,這就須要事件的觸發者向接受者傳遞相關的上下文數據,接受者才能正確處理,例如EventTouch對象中會包含觸摸點的信息,以便於訂閱者處理邏輯。
Cocos2d-x引擎自帶的事件類型包括:EventTouch,EventKeyboard,EventAcceleration,以及便於開發者自定義事件的EventCustom。
5.2 事件的訂閱者
訂閱者負責處理事件,它的成員包含一個訂閱事件的類型(這個類型應該和對應的Event的類型一致),以及一個回調方法用來處理事件。這兩個成員都應該只被事件分發器(EventDispatcher)使用,因此它們被定義爲受保護的成員,同時EventListener被定義爲EventDispatcher的友元:

class EventListener : public Object
{
protected:
EventListener();
bool init(const std::string& t, std::function<void(Event*)>callback);ios

public:
virtual ~EventListener();
virtual bool checkAvaiable() = 0;
virtual EventListener* clone() = 0;
protected:
std::function<void(Event*)> _onEvent;
    std::string _type;
bool _isRegistered;friend class EventDispatcher;
friend class Node;
};
在Cocos2d-x之前的版本中,訂閱者以繼承的方式定義,訂閱者和處理邏輯的對象是同一個實體,例如CCLayer實現了CCTouchDelegate。而在3.0中EventListener被定義爲一個變量,其好處是能夠將處理方法定義爲lambda表達式,這是3.0支持C++11的一個重要方面,它改變了使用事件的編程習慣,可是帶來了lambda表達式的好處,編程更加靈活,你甚至能夠在一個EventListener的處理程序中再定義一個EventListener變量。
與事件類型相對應,Cocos2d-x中自帶的訂閱者包括:EventListenerTouch,EventListenerKeyboard,EventListenerAcceleration以及EventListenerCustom。
5.3 事件的工做流程
在定義了事件和訂閱者以後,應用程序只須要向事件分發器註冊一個訂閱者實例,便可在事件發生的時候獲得通知。在Cocosd-x中負責事件的訂閱,註銷,分發的是EventDispatcher,它是一個單例,應用程序可經過EventDispatcher::getInstance()方法獲取其實例。
下面經過一個示例來演示事件的工做方式,在這個示例中當CollisionSystem檢測到兩個Node之間發生碰撞時,將觸發碰撞事件,而HitSystem是碰撞事件的其中一個訂閱者,它會響應碰撞事件並修改敵人的生命值:

class CollisionEvent:public Event
{
public:
static const char* COLLISION_EVENT_TYPE;編程

CollisionEvent(Entity* l,Entity* r);設計模式

Entity* getLeft(){return _left;}
Entity* getRight(){return _right;}
private:
Entity* _left;
Entity* _right;安全

};
上述代碼首先添加一個碰撞事件類CollisionEvent,它繼承自Event,並用一個常量COLLISION_EVENT_TYPE定義其類型。
CollisionEvent做爲事件傳遞的數據,應該向訂閱者傳遞相關的上下文,這裏須要傳遞的是發生碰撞的兩個Entity實例,關於Entity Component System會在本書後面的章節講述。

class CollisionListener : public EventListener
{
public:
static CollisionListener* create(std::function<void(CollisionEvent*)> callback);
virtual bool checkAvaiable() override;
virtual CollisionListener* clone() override;架構

protected:
CollisionListener();
bool init(std::function<void(CollisionEvent*)> callback);異步

std::function<void(CollisionEvent*)> _onCollisionEvent;ide

};
接下來,咱們須要定義訂閱者,在CollisionListener的init()方法中,聲明瞭它訂閱事件的類型,經過查看CollisionListener的實現部分代碼,能夠看到它引用的是上面CollisionEvent定義的COLLISION_EVENT_TYPE。

void HitSystem::configure()
{
auto listener=CollisionListener::create(
[this](CollisionEvent* event){
this->hit(event);
});
EventDispatcher::getInstance()->addEventListenerWithFixedPriority(listener, 1);
}函數

而後,咱們須要向EventDispatcher註冊訂閱者。HitSystem會響應碰撞事件,因此咱們在HitSystem初始化的時候向EventDispatcher註冊,並傳遞一個lambda表達式做爲處理程序。

void CollisionSystem::update(float dt)
{學習

    if (collide()) {
CollisionEvent* event=new CollisionEvent(entity,collisionEntity);
EventDispatcher::getInstance()->dispatchEvent(event);
}
}
最後,是觸發事件的程序。因爲CollisionSystem負責碰撞檢測,因此它會在檢測到兩個Node之間發生碰撞時,通知EventDispatcher分發此碰撞事件,並將發生碰撞的兩個Entity做爲數據保存在Event參數中。EventDispatcher在接受到事件通知的時候,首先根據Event參數的類型,查找與此類型相符的訂閱者,在本示例程序中CollisionListener的類型與CollisionEvent的類型一致,因此將會執行CollisionListener中的回調方法。
因此,經過EventDispatcher咱們就能自定義各類事件,在應用程序的各個模塊之間靈活通訊,大大簡化了事件的處理,同時下降了模塊間的耦合。
固然通常狀況下並不須要像這樣定義每個事件,能夠直接使用EventCustom,它的構造函數接受一個類型參數,使得一樣的EventCustom實例能夠分發不一樣類型的事件。同理,EventListenerCustom也接受一個類型參數,使得其能夠處理不一樣的事件類型。
5.4 深刻分析EventDispatcher
經過前面的學習,咱們應該初步學會了在Cocos2d-x中怎樣使用通常的事件。然而更靈活熟練地使用事件,還須要深刻學習更多的知識,在進一步分析EventDispatcher的機制以前,咱們來總結一下通常在遊戲中使用事件還有哪些特殊的需求:
  1. 設置訂閱者的優先級,一個類型的事件可能擁有多個訂閱者,所以有必要設置處理順序,例如當碰撞事件完成以後,其中一個訂閱者負責處理傷害計算,而另外一個訂閱者可能作一些UI的操做,例如播放聲音或者粒子效果。前者的優先級確定須要更高,由於後者的處理可能須要依賴於生命值的計算。
  2. 修改訂閱者的優先級。
  3. 中止事件的繼續分發,使後續的訂閱者不用再處理該事件。
  4. 根據屏幕上元素的層級,而不是手動設定的優先級來處理事件分發,這在觸摸事件的分發中尤爲重要。
帶着這些目標,咱們來分析EventDispatcher是怎樣實現它們,以及咱們在應用程序中應該怎樣使用它們。
首先,EventDispatcher提供了兩種註冊訂閱者的方法:

void addEventListenerWithSceneGraphPriority(EventListener* listener, Node* node);

    void addEventListenerWithFixedPriority(EventListener* listener, int fixedPriority);
第一種提供一個相關聯的Node,這樣事件的處理將會依據該Node的繪製順序來決定分發的優先級。第二種則是手動設定一個優先級,這樣EventDispatcher將根據該優先級直接決定分發順序。同時,經過第二種方法註冊的訂閱者還能夠經過調用setPriority()方法修改優先級。
其次,EventDispatcher是怎樣作到根據元素的繪製順序來計算訂閱者的優先級的呢?在Cocos2d-x引擎內部,每一個EventListener都被封裝爲一個EventListenerItem的結構體:

struct EventListenerItem
{
int            fixedPriority;
Node*          node;
EventListener* listener;
~EventListenerItem();

    };
若是訂閱者與某個Node相關聯,則node成員將被賦值,同時fixedPriority被設置爲0。而且該listener變量會被添加到該Node的關聯訂閱者列表中。這樣的設置會影響訂閱者的排序,找到sortAllEventListenerItemsForType()方法,能夠總結排序規則以下:
  1. 分發fixedPriority小於0的訂閱者,fixedPriority越小則優先分發。
  2. 分發全部fixedPriority值爲0的訂閱者,而且沒有與Node相關聯的。
  3. 分發全部與Node相關聯的訂閱者,其關聯Node的eventPriority越高(越處於屏幕最上層)則優先級越高。
  4. 分發全部fixedPriority大於0的訂閱者,一樣fixedPriority越小則優先分發。
這裏有兩個地方須要注意:首先,兩種訂閱方式的優先級進行比較並無太大意義,一般咱們應該避免同一個事件類型的訂閱者混用兩種註冊方式;其次,Node的eventPriority變量不須要咱們操心,只要設置爲根據Node計算優先級,則引擎會保證其與繪製順序一致。
若是讀者對此感興趣,能夠看到Node類有一個靜態變量_globalEventPriorityIndex,在每一幀開始的時候,Director會將其重置爲0,而後在Node的visit()方法中,每此調用draw()方法以後會調用updateEventPriorityIndex()方法,該方法以下:

inline void updateEventPriorityIndex() {
_oldEventPriority = _eventPriority;
_eventPriority = ++_globalEventPriorityIndex;
if (_oldEventPriority != _eventPriority)
{
setDirtyForAllEventListeners();
}

    };
由此計算,第一個繪製的Node其_eventPriority變量值爲1,第二個Node其_eventPriority值爲2…,一旦有新的Node被添加或者舊的Node被移除,這些值會從新計算,但始終能保證其和繪製順序一致。而一旦某個Node的繪製順序號發生了改變,則從新計算該Node關聯的全部訂閱者在EventDispatcher中的排列順序,從而保證其處理順序始終和繪製順序一致。
每一個Event在被處理的時候,若是訂閱者是經過與Node關聯註冊的,EventDispatcher還會將當前訂閱者關聯的Node臨時保存在Event中,這樣咱們還能夠在事件處理程序中獲取該Node,這就是前面看到的getCurrentTarget()方法。這在某些時候比較有用,由於EventListener能夠和任何Node實例關聯,事件處理代碼所在的對象可能並無保留其引用。
最後是關於事件的禁止分發,因爲一個事件會被多個訂閱者處理,所以同一個Event實例會被傳遞給多個處理程序,這樣每一個處理程序就能夠修改這個共同的Event實例。若是其中一個處理程序調用Event的stopPropagation()方法,將其_isStopped設置爲true,EventDispatcher就會中止對該事件的分發,後續的訂閱者將接受不到事件通知。
此外,EventDispatcher還包含一些例如保證安全的代碼,以及在須要的時候對訂閱者重新排序等方法,dispatchEvent()方法還包含一個forceSortListeners的默認爲false的參數它能夠在分發事件以前對訂閱者從新排序,但彷佛用處不大,由於通常影響優先級的因素如添加,移除節點,直接修改fixedPriority等都會致使訂閱者從新排序,也許可能還存在一些特殊狀況。
5.5 Cocos2d-x中的系統事件
對於系統事件,有的Cocos2d-x採起了特殊的處理,而有的咱們能夠從中學習到使用事件的一些高級技巧,固然在實際使用過程當中最重要的仍是知道怎麼熟練使用它們,以及明白它們在何時被觸發。
5.5.1 EventTouch
觸摸是Coco2d-x中最複雜的事件,爲了簡化最經常使用的單點操做,它要區分多點和單點觸摸,對於單點的狀況它還要在整個觸摸操做過程當中記錄一些狀態。這些複雜的狀況使得上面學習的分發機制不能直接處理,因此Cocos2d-x對於觸摸事件作了特殊處理,咱們能夠在EventDispatcher中找到一個私有的dispatchTouchEvent()方法,它專門用來處理觸摸事件。
EventTouch事件分爲兩種,一種是PC或者Mac上的鼠標點擊,另外一種是移動設備上面的觸摸,其中前者只有一個觸摸點。可是Cocos2d-x並不處理右鍵點擊,而是直接交給父窗口或者系統處理。這些事件都由比較底層的EGLViewProtocol的實現者從系統捕獲,而後封裝成Cocos2d-x中的信息格式傳遞給EventDispatcher。值得注意的是,在這個封裝的過程當中EGLViewProtocol會根據程序設置的分辨率方案對觸摸點的位置進行調整。同時在ios平臺默認狀況下Cocos2d-x並不開啓多點支持,讀者須要在AppController.cpp中設置:
     [__glView setMultipleTouchEnabled:YES];
首先找到EventTouch的定義,它僅包含兩個成員,_eventCode用來表面當前觸摸事件的狀態,它在EventTouch:EventCode中定義;_touches則保存者當前觸摸狀態下的觸摸點信息:

class EventTouch : public Event
{
public:
enum class EventCode{
BEGAN,
MOVED,
ENDED,
CANCELLED
};

EventCode getEventCode() { return _eventCode; };
std::vector<Touch*> getTouches() { return _touches; };

#if TOUCH_PERF_DEBUG
void setEventCode(EventCode eventCode) { _eventCode = eventCode; };
void setTouches(const std::vector<Touch*>& touches) { _touches = touches; };
#endif
};

從定義能夠看出,咱們還能夠直接設置觸摸信息模擬系統事件用於測試。Touch封裝了一個觸摸點的信息,應用程序能夠從這裏獲取不少有用的信息:

class CC_DLL Touch : public Object
{
public:
/** 觸摸點在OpenGL座標系中的位置 */
Point getLocation() const;
/** 觸摸點在OpenGL座標系中的上一個位置 */
Point getPreviousLocation() const;
/** 觸摸點在OpenGL座標系的起點位置 */
Point getStartLocation() const;
/** 在OpenGL座標系中當前位置與上一個位置的差 */
Point getDelta() const;
/** 觸摸點在屏幕座標系中的位置 */
Point getLocationInView() const;
/** 觸摸點在屏幕座標系中的上一個位置 */
Point getPreviousLocationInView() const;
/** 觸摸點在屏幕座標系的起點位置 */
Point getStartLocationInView() const;

int getID() const{ return _id; }

};
有了這些信息,咱們就能夠在程序中進行精準的觸摸判斷,例如斷定是否點中某個區域,以及是否在觸摸事件結束的時候離開了某個區域,還能夠在cancelled事件發生時根據觸摸點發生的位移還原一些元素的位置等等,後面將分析一些實際例子。
在dispatchTouchEvent()方法中咱們不再用爲訂閱者的優先級操心了,由於在這方面,觸摸事件和其餘事件的處理是一致的。這裏須要特殊處理的是觸摸事件要根據不一樣的觸摸狀態調用訂閱者的不一樣響應方法,和其餘訂閱者只有一個處理方法不一樣,觸摸事件的訂閱者須要提供每一個觸摸狀態下的方法:

class EventListenerTouch : public EventListener
{
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;

std::function<void(const std::vector<Touch*>&, Event*)> onTouchesBegan;
std::function<void(const std::vector<Touch*>&, Event*)> onTouchesMoved;
std::function<void(const std::vector<Touch*>&, Event*)> onTouchesEnded;
std::function<void(const std::vector<Touch*>&, Event*)> onTouchesCancelled;

void setSwallowTouches(bool needSwallow);

private:

bool _needSwallow;

    Touch::DispatchMode _dispatchMode;

};
首先經過Touch::DispatchMode將訂閱者分爲單點和多點觸摸的訂閱者,而對於單點的狀況,還能夠經過設置setSwallowTouches來決定是否須要禁止後續的訂閱者繼續處理某個觸摸點。EventListener與Node的關聯只是致使了事件的分發與繪製的順序相反,而對於觸摸事件來講,通常狀況下它可能只須要被處理一次,這個時候EventDispatcher就要根據_needSwallow屬性來決定是否須要繼續分發。
根據這些觸摸事件處理的一些需求,dispatchTouchEvent()方法的邏輯就比較清晰了:
首先,找到全部單點觸摸的訂閱者,而後分別用每個觸摸點分別詢問onTouchBegan是否須要處理,若是須要則將該觸摸點保存到該訂閱者中以供後續的move,end,cencelled等方法處理。同時,若是該訂閱者的_needSwallow設置爲true,則該觸摸點將再也不被任何訂閱者處理。
其次,對上述執行後剩下的全部觸摸點,找到全部多點觸摸的訂閱者,分別調用各個多點觸摸的方法。
值得注意的是,若是同時有大於1個的觸摸點,則單點觸摸的訂閱者將會執行屢次,因此若是玩家同時將兩個手指點擊在一個按鈕上,則按鈕將被觸發兩次點擊事件,EventDispatcher並不保證單點事件的訂閱者只被點擊一次,程序邏輯須要實現狀態記錄,咱們能夠參看後面的Menu分析對此的處理,它用Menu::State來記錄按鈕當前狀態。
最後,咱們來分析兩個引擎中的使用觸摸的例子,讓讀者瞭解觸摸的經常使用處理方法。
5.5.1.1 Layer對系統事件的支持
Layer常常被用來根據UI的層級組織元素,正如它的名字同樣。實際上它的主要目的是方便咱們使用系統事件,觸摸,按鍵,重力加速等事件,它經過提供構造這些事件的訂閱者,並向EventDispatcher註冊和註銷這些訂閱者來簡化咱們使用系統事件。此外,在3.0中它還新加入了對物理引擎集成的支持,在後面的章節咱們會學習。
Layer對全部事件的均採起與自身相關聯的方式向EventDispatcher註冊訂閱者,便是說被處理的優先級取決於自身的UI層級。要使用某個系統事件基本上只須要調用setXXXEnabled()方法,而後重寫相關事件的處理方法便可。固然默認全部的系統事件均沒有開啓,而且對於觸摸事件默認設置爲多點觸摸。
一下咱們以使用觸摸事件爲例,在Layer中使用觸摸事件:

bool HelloWorld::init()
{
if ( !CCLayer::init()){
return false;
}

setTouchMode(Touch::DispatchMode::ONE_BY_ONE);
setTouchEnabled(true);

return true;

}

class HelloWorld : public cocos2d::Layer
{
public:
virtual bool onTouchBegan(Touch *touch, Event *event);
virtual void onTouchMoved(Touch *touch, Event *event);
virtual void onTouchEnded(Touch *touch, Event *event);

    virtual void onTouchCancelled(Touch *touch, Event *event);
}
上述示例中,咱們首先在Layer的init初始化後調用setTouchEnabled()方法聲明須要處理單點觸摸事件,而後重寫了單點觸摸須要實現的4個處理方法。
實際上,在開啓每一個系統事件處理以後,Layer向EventDispatcher註冊訂閱者,並將處理方法聲明爲虛方法以供子類重寫。並在關閉事件,或者元素被移除的時候向EventDispatcher註銷訂閱者。這樣大大簡化了咱們使用系統事件。
5.5.1.2 Menu中的觸摸處理
觸摸更常見的是被用在一些控件中,好比按鈕點擊,表格拖拽等。其開發中最重要的部分是點擊斷定,此外對於按鈕還須要注意前面提到的狀態斷定,以免屢次點擊。Cocos2d-x爲咱們提供了一個經常使用的GUI控件-Menu,用於顯式一組按鈕。經過分析Menu咱們就能夠徹底掌握觸摸相關的處理了。
Menu繼承自LayerRGB,全部能夠很方便的使用觸摸事件。經過查看Menu的源代碼能夠知道Menu註冊了單點觸摸事件。固然Menu還實現了多個MenuItem的管理,咱們這裏關心的是怎麼使用觸摸點的位置信息。

bool Menu::onTouchBegan(Touch* touch, Event* event)
{
if (_state != Menu::State::WAITING || ! _visible || !_enabled){
return false;
}

for (Node *c = this->_parent; c != NULL; c = c->getParent()){
if (c->isVisible() == false){
return false;
}
}

_selectedItem = this->itemForTouch(touch);
if (_selectedItem){
_state = Menu::State::TRACKING_TOUCH;
_selectedItem->selected();

return true;
}

return false;

}
從這個示例咱們能夠看到三點有趣的信息:
首先,Menu經過一個Menu::State變量,來防止同時屢次點擊,若是Menu開始處理一個觸摸點,則會將_state設置爲Menu::State::TRACKING_TOUCH。
其次,它在UI樹上向上查找直到根節點,檢查節點是否正在被繪製。這裏是由於雖然能夠經過設置visible來設置節點的可見性,但其子結點並不能直接知道本身是否被隱藏或者顯式了,須要向上遍歷至根節點才能作出判斷。同時,EventDispatcher雖然能夠根據節點UI層級來決定分發順序,但它並不負責檢查節點的可見性,由於這裏元素僅用來計算分發順序,並且並非全部的事件都依據元素的層級來計算優先級。因此,這裏在開發中常常會遇到的一個問題就是,某個節點經過父級被隱藏了,可是其仍然可以收到觸摸事件。
最後,Menu經過itemForTouch()方法來作點擊斷定:

MenuItem* Menu::itemForTouch(Touch *touch)
{
Point touchLocation = touch->getLocation();

if (_children && _children->count() > 0)
{
Object* pObject = NULL;
CCARRAY_FOREACH_REVERSE(_children, pObject)
{
MenuItem* child = dynamic_cast<MenuItem*>(pObject);
if (child && child->isVisible() && child->isEnabled())
{
Point local = child->convertToNodeSpace(touchLocation);
Rect r = child->rect();
r.origin = Point::ZERO;

if (r.containsPoint(local)){
return child;
}
}
}
}

return NULL;

}
這裏則告訴咱們作點擊斷定的通常方法,首先咱們經過getLocation()方法取出觸摸點在OpenGL座標系中的世界座標,而後將其轉化到節點的本地座標系,最後根據節點的尺寸檢測其是否落於該區域內。至此,咱們就瞭解了關於觸摸的全部知識。
5.5.2 EventKeyboard
鍵盤輸入事件比較簡單,它捕捉一個按鍵動做,它的參數包括按下的鍵_keyCode,以及表示按鍵的兩個狀態_isPressed:

class EventKeyboard : public Event
{
EventKeyboard(KeyCode keyCode, bool isPressed)
: Event(EVENT_TYPE)
, _keyCode(keyCode)
, _isPressed(isPressed)
{};

private:
KeyCode _keyCode;
bool _isPressed;

friend class EventListenerKeyboard;

};

class EventListenerKeyboard : public EventListener
{
public:
std::function<void(EventKeyboard::KeyCode, Event* event)> onKeyPressed;
std::function<void(EventKeyboard::KeyCode, Event* event)> onKeyReleased;

};
有趣的是這裏對兩個處理方法的調用方式,通常事件只有一個處理方法,咱們還記得EventListener定義的_onEvent變量,它被EventDispatcher直接調用,而這裏EventListenerKeyboard作了特殊處理:

bool EventListenerKeyboard::init()
{
auto listener = [this](Event* event){
auto keyboardEvent = static_cast<EventKeyboard*>(event);
if (keyboardEvent->_isPressed){
if (onKeyPressed != nullptr)
onKeyPressed(keyboardEvent->_keyCode, event);
}
else {
if (onKeyReleased != nullptr)
onKeyReleased(keyboardEvent->_keyCode, event);
}
};

if (EventListener::init(EventKeyboard::EVENT_TYPE, listener)){
return true;
}

return false;

}
咱們看到,EventListenerKeyboard從新包裝了listener,因而可知,咱們程序中定義的訂閱者實例並不必定是最終EventDispatcher中引用的實例,而這裏更有趣的是訂閱者中包含了訂閱者。這就是事件分發使用方法指針,而不是繼承實現某個Delegate的好處。
相關文章
相關標籤/搜索