事件分發機制

簡介

在使用時,首先建立一個事件監聽器,事件監聽器包含如下幾種:node

  • 觸摸事件 (EventListenerTouch)函數

  • 鍵盤響應事件 (EventListenerKeyboard)this

  • 加速記錄事件 (EventListenerAcceleration)spa

  • 鼠標響應事件 (EventListenerMouse)code

  • 自定義事件 (EventListenerCustom)繼承

以上事件監聽器統一由 _eventDispatcher 來進行管理。它的工做須要三部分組成:遊戲

  • 事件分發器 EventDispatcher事件

  • 事件類型 EventTouch, EventKeyboard 等圖片

  • 事件監聽器 EventListenerTouch, EventListenerKeyboard 等ci

監聽器實現了各類觸發後的邏輯,在適當時候由 事件分發器分發事件類型,而後調用相應類型的監聽器。

使用方法

如今將要實如今一個界面中添加三個按鈕,三個按鈕將會互相遮擋,而且可以觸發觸摸事件,如下是具體實現

首先建立三個精靈,做爲三個按鈕的顯示圖片

    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);

touchable_sprite

建立一個單點觸摸事件監聽器,並完成邏輯處理內容

    // 建立一個事件監聽器 OneByOne 爲單點觸摸
    auto listener1 = EventListenerTouchOneByOne::create();
    // 設置是否吞沒事件,在 onTouchBegan 方法返回 true 時吞沒
    listener1->setSwallowTouches(true);

    // 使用 lambda 實現 onTouchBegan 事件回調函數
    listener1->onTouchBegan = [](Touch* touch, Event* event){
        // 獲取事件所綁定的 target 
        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;
    };

    // 觸摸移動時觸發
    listener1->onTouchMoved = [](Touch* touch, Event* event){
        auto target = static_cast<Sprite*>(event->getCurrentTarget());
        // 移動當前按鈕精靈的座標位置
        target->setPosition(target->getPosition() + touch->getDelta());
    };

    // 點擊事件結束處理
    listener1->onTouchEnded = [=](Touch* touch, Event* event){
        auto target = static_cast<Sprite*>(event->getCurrentTarget());
        log("sprite onTouchesEnded.. ");
        target->setOpacity(255);
        // 從新設置 ZOrder,顯示的先後順序將會改變
        if (target == sprite2)
        {
            sprite1->setZOrder(100);
        }
        else if(target == sprite1)
        {
            sprite1->setZOrder(0);
        }
    };

添加事件監聽器到事件分發器

    // 添加監聽器
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, sprite1);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite2);
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite3);

_eventDispatcherNode 的屬性,經過它管理當前節點(如 場景 、層、精靈等 )的全部事件分發狀況。可是它自己是一個單例模式值的引用,在 CCNode 構造函數中,經過 "Director::getInstance()->getEventDispatcher();" 獲取,有了這個屬性,咱們能更爲方便的調用。

注意: 這裏當咱們再次使用 listener1 的時候,須要使用 clone() 方法建立一個新的克隆,由於在使用 addEventListenerWithSceneGraphPriority 或者 addEventListenerWithFixedPriority 方法時,會對當前使用的事件監聽器添加一個已註冊的標記,這使得它不可以被添加屢次。另外,有一點很是重要,FixedPriority listener添加完以後須要手動remove,而SceneGraphPriority listener是跟node綁定的,在node的析構函數中會被移除。具體的示例用法能夠參考引擎自帶的tests。

新的觸摸機制

以上的步驟看似相對 2.x 版本觸摸機制實現時,複雜了點,在老的版本中繼承一個 delegate ,裏面定義了 onTouchBegan 等方法,而後在裏面判斷點擊的元素,進行邏輯處理。而這裏將事件處理邏輯獨立出來,封裝到一個 Listener 中,而以上的邏輯實現瞭如下功能:

  1. 經過添加事件監聽器,將精靈以顯示優先級 (SceneGraphPriority) 添加到事件分發器。這就是說,當咱們點擊精靈按鈕時,根據屏幕顯示的「遮蓋」實際狀況,進行有序的函數回調(即:如圖中黃色按鈕首先進入 onTouchBegan 邏輯處理)。

  2. 在事件邏輯處理時,根據各類條件處理觸摸後的邏輯,如點擊範圍判斷,設置被點擊元素爲不一樣的透明度,達到點擊效果。

  3. 由於設置了 listener1->setSwallowTouches(true); 而且在 onTouchBegan 中作相應的判斷,以決定其返回值是 false 仍是 true,用來處理觸摸事件是否依據顯示的順序關係向後傳遞。

注意:SceneGraphPriority 所不一樣的是 FixedPriority 將會依據手動設定的 Priority 值來決定事件相應的優先級,值越小優先級越高。

其它事件派發處理模塊

除了觸摸事件響應以外,還有如下模塊使用了相同的處理方式。

鍵盤響應事件

除了鍵盤,還能夠是終端設備的各個菜單,他們使用同一個監聽器來進行處理。

    // 初始化並綁定
    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);
    }

加速計事件

在使用加速計事件監聽器以前,須要先啓用此硬件設備:

Device::setAccelerometerEnabled(true);

而後建立對應的監聽器,在建立回調函數時,可使用 lambda 表達式建立匿名函數,也能夠綁定已有的函數邏輯實現,以下:

    auto listener = EventListenerAcceleration::create(CC_CALLBACK_2(AccelerometerTest::onAcceleration, this));
    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

    // 加速計回調函數原型實現
    void AccelerometerTest::onAcceleration(Acceleration* acc, Event* event)
    {
        // 這裏處理邏輯
    }

鼠標響應事件

在 3.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 ,而且設置了其 UserData  數據,手動的經過 _eventDispatcher->dispatchEvent(&event); 將此事件分發出去,從而觸發以前所實現的邏輯。

移除事件監聽器

咱們能夠經過如下方法移除一個已經被添加了的監聽器。

    _eventDispatcher->removeEventListener(listener);

也可使用以下方法,移除當前事件分發器中全部監聽器。

    _eventDispatcher->removeAllEventListeners();

當使用 removeAll 的時候,此節點的全部的監聽將被移除,推薦使用 指定刪除的方式。

注意:removeAll 以後 菜單 也不能響應。由於它也須要接受觸摸事件。

相關文章
相關標籤/搜索