介紹
Cocos2d-X 3.X 引入了一種新的響應用戶事件的機制。
涉及三個基本的方面:
- Event listeners 封裝你的事件處理代碼
- Event dispatcher 向 listener 分發用戶事件
- Event 對象 包含關於事件的信息
為了響應事件,首先你要創建一個 EventListener,有五種不同的 EventListener.
- EventListenerTouch 響應觸控事件
- EventListenerKeyboard 響應鍵盤事件
- EventListenerAcceleration 響應加速器事件
- EventListenMouse 響應鼠標事件
- EventListenerCustom 響應定制的事件
然后,將你的時間處理代碼連接到適當的事件監聽回調方法中。( 例如 EventListenerTouch 的 onTouchBegan ,或者 EventListenerKeyboard 的 onKeyPressed )
接着,使用 EventDispatcher 注冊你的 EventListener。
當事件觸發之后 ( 例如,用戶觸摸了屏幕,或者敲擊樂鍵盤 ),EventDispatcher 通過調用適當的 EventListener 的回調來分發 Event 對象 ( 例如 EventTouch, 或者 EventKeyboard ),每個事件對象包含對應的事件信息 ( 例如包含觸控的坐標 )。
示例
在下面的代碼中,我們在場景中添加三個按鈕,每一個都可以響應觸控事件。
auto sprite1 = Sprite::create("Images/CyanSquare.png"); sprite1->setPosition(origin+Point(size.width/2, size.height/2) + Point(-80, 80)); addChild(sprite1, 10); auto sprite2 = Sprite::create("Images/MagentaSquare.png"); sprite2->setPosition(origin+Point(size.width/2, size.height/2)); addChild(sprite2, 20); auto sprite3 = Sprite::create("Images/YellowSquare.png"); sprite3->setPosition(Point(0, 0)); sprite2->addChild(sprite3, 1);
如圖所示
創建一個觸控的事件監聽器和回調代碼
(注意,在下面的代碼中,我們使用 C++11 的 Lambda 表達式來實現回調,后面的鍵盤事件使用另外一種方式,使用 CC_CALLBACK_N 宏來實現)
// 創建一個排隊的觸控事件監聽器 ( 同時僅僅處理一個觸控事件 ) auto listener = EventListenerTouchOneByOne::create(); // 當 "swallow touches" 設置為 true, 然后,在 onTouchBegan 方法發返回 'true' 將會吃掉觸控事件, 防止其他監聽器使用這個事件. listener->setSwallowTouches(true); // 使用 lambda 表達式實現 onTouchBegan 事件的回調函數 listener->onTouchBegan = [](Touch* touch, Event* event){ // event->getCurrentTarget() 返回 *listener's* sceneGraphPriority 節點. auto target = static_cast<Sprite*>(event->getCurrentTarget()); // 獲取當前觸控點相對與按鈕的位置 Point locationInNode = target->convertToNodeSpace(touch->getLocation()); Size s = target->getContentSize(); Rect rect = Rect(0, 0, s.width, s.height); // 檢測點擊區域 if (rect.containsPoint(locationInNode)) { log("sprite began... x = %f, y = %f", locationInNode.x, locationInNode.y); target->setOpacity(180); return true; } return false; }; // 當移動觸控的時候 listener->onTouchMoved = [](Touch* touch, Event* event){ auto target = static_cast<Sprite*>(event->getCurrentTarget()); // 移動當前的精靈 target->setPosition(target->getPosition() + touch->getDelta()); }; // 結束 listener->onTouchEnded = [=](Touch* touch, Event* event){ auto target = static_cast<Sprite*>(event->getCurrentTarget()); log("sprite onTouchesEnded.. "); target->setOpacity(255); //重新設置 zOrder,改變現實順序 if (target == sprite) { sprite->setZOrder(100); } else if (target == sprite) { sprite->setZOrder(0); } };
添加事件監聽器到事件分發器
// 注冊監聽器 _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, sprite1); _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite2); _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite3);
_eventDispatcher 是 Node 的屬性,我們使用它來管理當前節點的所有事件分發 ( 還有像 Scene, Layer, Sprite 等等 )。
注意,在上面的例子中,我們在調用第二和第三個 addEventListenerWithSceneGraphPriority 中使用 clone() 方法,這是因為每個事件監聽器只能被添加一次。addEventListenerWithSceneGraphPriority 方法和 addEventListenerWithFixedPriority
在事件監聽器中設置一個注冊標志,如果已經設置了標志,就不能再次添加了。
還有需要記住的就是,如果你添加了一個 _fixed priority_ listener 到節點,當節點被刪除的時候,你需要手動刪除這個監聽器,而綁定到節點的 _scene graph priority_ listener,當節點被析構的時候,監聽器將會被自動析構。
新的觸控機制
上面的處理過程與 2.X版本比較,看起來比較難,在舊版中,你需要從 delegate 類派生,其中定義了 onTouchBegan 等等方法,你的事件處理代碼會放到這些委托方法中。
新的事件處理機制將事件處理邏輯從 delegate 中移到了監聽器中,上面的邏輯實現了如下功能。
- 通過使用事件監聽器,精靈可以使用 SceneGraphPriority 添加到事件分發器,也就是說,當點擊精靈的時候,回調函數可以以顯示的順序來調用。( 也就是說,顯示在前面的精靈優先得到事件 )
- 當處理事件邏輯的時候,基於不同的狀況來處理觸控的邏輯 ( 比如),顯示點擊的效果。
- 由於
listener1->setSwallowTouches(true)
設置了,還有在 onTouchBegan 中的處理邏輯,不管何種顯示順序都可以被處理。
FixedPriority 對 SceneGraphPriority
EventDispatcher 使用優先級來決定監聽器對事件的分發。
FixedPriority ,一個整數,低的 EventListeners 優先高的 EventListenters.
SceneGraphPriority ,一個節點的指針,高的 Z Order 的節點優先於低的 Z Order 節點,這樣確保前面的元素獲取觸控事件。
其它事件處理模式
下面代碼使用另外的機制。
你也可以使用 CC_CALLBACK_N 宏來實現類似機制,下面的代碼演示鍵盤處理。
// 創建鍵盤監聽器 auto listener = EventListenerKeyboard::create(); listener->onKeyPressed = CC_CALLBACK_2(KeyboardTest::onKeyPressed, this); listener->onKeyReleased = CC_CALLBACK_2(KeyboardTest::onKeyReleased, this); _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this); // 實現鍵盤回調的宏 void KeyboardTest::onKeyPressed(EventKeyboard::KeyCode keyCode, Event* event) { log("Key with keycode %d pressed", keyCode); } void KeyboardTest::onKeyReleased(EventKeyboard::KeyCode keyCode, Event* event) { log("Key with keycode %d released", keyCode); }
Accelerometer 事件
在使用加速器事件之前,需要在設備上啟用加速器。
Device::setAccelerometerEnabled(true);
使用監聽器
auto listener = EventListenerAcceleration::create(CC_CALLBACK_2(AccelerometerTest::onAcceleration, this)); _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this); // 實現回調函數 void AccelerometerTest::onAcceleration(Acceleration* acc, Event* event) { // Processing logic here }
鼠標事件
在 V3.0 中添加了鼠標點擊事件分發,支持多平台,豐富了用戶的游戲體驗。
與所有的事件類型一樣,首先需要創建事件監聽器
_mouseListener = EventListenerMouse::create(); _mouseListener->onMouseMove = CC_CALLBACK_1(MouseTest::onMouseMove, this); _mouseListener->onMouseUp = CC_CALLBACK_1(MouseTest::onMouseUp, this); _mouseListener->onMouseDown = CC_CALLBACK_1(MouseTest::onMouseDown, this); _mouseListener->onMouseScroll = CC_CALLBACK_1(MouseTest::onMouseScroll, this); _eventDispatcher->addEventListenerWithSceneGraphPriority(_mouseListener, this);
然后,一個一個實現監聽器的回調函數
void MouseTest::onMouseDown(Event *event) { EventMouse* e = (EventMouse*)event; string str = "Mouse Down detected, Key: "; str += tostr(e->getMouseButton()); // ... } void MouseTest::onMouseUp(Event *event) { EventMouse* e = (EventMouse*)event; string str = "Mouse Up detected, Key: "; str += tostr(e->getMouseButton()); // ... } void MouseTest::onMouseMove(Event *event) { EventMouse* e = (EventMouse*)event; string str = "MousePosition X:"; str = str + tostr(e->getCursorX()) + " Y:" + tostr(e->getCursorY()); // ... } void MouseTest::onMouseScroll(Event *event) { EventMouse* e = (EventMouse*)event; string str = "Mouse Scroll detected, X: "; str = str + tostr(e->getScrollX()) + " Y: " + tostr(e->getScrollY()); // ... }
定制事件
上面的事件是系統定義的事件,事件由系統自動觸發,額外的,你也可以定制不是由系統自動觸發的事件,通過你自己的代碼來實現。
_listener = EventListenerCustom::create("game_custom_event1", [=](EventCustom* event){ std::string str("Custom event 1 received, "); char* buf = static_cast<char*>(event->getUserData()); str += buf; str += " times"; statusLabel->setString(str.c_str()); }); _eventDispatcher->addEventListenerWithFixedPriority(_listener, 1);
自定義的事件監聽器如上所示,有響應代碼,添加到事件分發器,如何觸發呢?看下面。
static int count = 0; ++count; char* buf = new char[10]; sprintf(buf, "%d", count); EventCustom event("game_custom_event1"); event.setUserData(buf); _eventDispatcher->dispatchEvent(&event); CC_SAFE_DELETE_ARRAY(buf);
上面的代碼創建了一個 EventCustom 對象,設置了用戶數據,通過手工調用 _eventDispatcher 的 dispatchEvent 方法觸發,這就會觸發前面定義的處理器。
刪除事件監聽器
已經添加的事件監聽器可以如下刪除。
_eventDispatcher->removeEventListener(listener);
使用下面的代碼,刪除所有的事件監聽器。
_eventDispatcher->removeAllEventListeners();
當掉用 removeAllEventListeners 的時候,這個節點所有的監聽器都被刪除了,建議刪除特定的監聽器。
注意,當調用 removeAll 之后,菜單會停止響應,因為它也需要接收觸控事件。