Cocos2d-x 3.X 事件分發機制


介紹

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 中移到了監聽器中,上面的邏輯實現了如下功能。

  1. 通過使用事件監聽器,精靈可以使用 SceneGraphPriority 添加到事件分發器,也就是說,當點擊精靈的時候,回調函數可以以顯示的順序來調用。( 也就是說,顯示在前面的精靈優先得到事件 )
  2. 當處理事件邏輯的時候,基於不同的狀況來處理觸控的邏輯 ( 比如),顯示點擊的效果。
  3. 由於 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 之后,菜單會停止響應,因為它也需要接收觸控事件。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM