1. 程式人生 > >[cocos2dx]事件分發機制(一)

[cocos2dx]事件分發機制(一)

事件分發機制

什麼是事件? Event及子類EventXXX,核心成員_type

誰監聽事件? EventListener及子類EventListenerXXX,核心成員_onEvent

誰派發事件? EventDispatcher分發器,核心方法dispatchEvent(導演類全域性例項化了一個EventDispatcher) 

基本概念:

  • 事件監聽器:封裝了事件處理的程式碼;
  • 事件排程器(或分發器)通知使用者事件的監聽器;
  • 事件物件:包含了關於事件的資訊。

事件分發本質是採用的觀察者模式: 註冊觀察者,事件產生並回調,取消註冊

事件監聽器的5種類型

  • 觸控響應事件 (EventListenerTouch)
  • 鍵盤響應事件 (EventListenerKeyboard)
  • 滑鼠響應事件 (EventListenerMouse)
  • 自定義響應事件 (EventListenerCustom)
  • 加速響應事件 (EventListenerAcceleration)

事件

觸控事件

在手遊中,最重要的事件是觸控事件。它們很容易被建立來提供通用的功能。首先我們要明確什麼是觸控事件。當你觸控移動裝置的螢幕時,螢幕會接收到這個觸控行為,並檢查你觸摸了哪裡以及決定你觸控到了什麼。然後你的觸控行為就會被響應。你所觸控的或許不是響應物件,但很有可能是它下面的東西。通常會給觸控事件分配優先順序,優先順序最高的就是被先響應的。以下程式碼建立了一個基本的觸控事件監聽器:

//  Create a "one by one" touch event listener
// (processes one touch at a time)
auto listener1 = EventListenerTouchOneByOne::create();
 
// trigger when you push down
listener1->onTouchBegan = [](Touch* touch, Event* event){
// your code
return true; // if you are consuming it
};
 
// trigger when moving touch
listener1->onTouchMoved = [](Touch* touch, Event* event){
// your code
};
 
// trigger when you let up
listener1->onTouchEnded = [=](Touch* touch, Event* event){
// your code
};
 
// Add listener
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1, this);

鍵盤事件

    //Initializing and binding 
    auto listener = EventListenerKeyboard::create();
    listener->onKeyPressed = CC_CALLBACK_2(KeyboardTest::onKeyPressed, this);
    listener->onKeyReleased = CC_CALLBACK_2(KeyboardTest::onKeyReleased, this);

    _eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);

    // Implementation of the keyboard event callback function prototype
    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);
    }  

滑鼠事件

_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());
    // ...
}

加速計事件

很些移動裝置都配備了加速度計。加速計是一個感測器,可以測量重力和方向上的變化。例如,來回移動你的電話來模擬平衡。Cocos2d-x也支援這些事件並且建立起來很簡單。在使用加速計事件之前,你需要在裝置上啟用這個事件:

Device::setAccelerometerEnabled(true);
// creating an accelerometer event
auto listener = EventListenerAcceleration::create(CC_CALLBACK_2(
AccelerometerTest::onAcceleration, this));
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener, this);
 
// Implementation of the accelerometer callback function prototype
void AccelerometerTest::onAcceleration(Acceleration* acc, Event* event)
{
    //  Processing logic here
}

自定義事件

上面的事件型別都是系統定義的,所以事件由系統自動觸發。作為額外的,你可以自定義不由系統觸發的事件,程式碼類似下面:

    _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);手工地分發事件。這樣就能觸發前面定義的回撥函式。

事件分配器

使用分配器註冊事件

使用事件分配器可以很容易的註冊事件。以上文的觸控事件監視器為例:

// Add listener
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1,sprite1);

值得注意的是,每個物件都只能註冊一個觸控事件。如果多個物件需要使用相同的監聽器,你需要使用clone()方法.

// Add listener
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1,sprite1); 
// Add the same listener to multiple objects.
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite2); 
_eventDispatcher->addEventListenerWithSceneGraphPriority(listener1->clone(), sprite3);

從分配器中移除事件

使用如下方法可以移除一個已有的監聽器:

_eventDispatcher->removeEventListener(listener);

儘管這看起來很特殊,但是內建的Node物件使用事件分發機制與我們所講的方式是相同的。以Menu為例,當點選帶有MenuItems屬性的 Menu時,你就已經分配到了一個事件。同樣也可對內建的Node物件使用removeEventListener方法。 

cocos2dx+lua註冊事件函式

為了降低模組間的耦合, 很多系統使用事件派發機制, 接收方無需知道派發者是誰.在Qt中,這個系統被稱作Slot&Signal.
  • registerScriptTouchHandler           註冊觸屏事件
  • registerScriptTapHandler             註冊點選事件
  • registerScriptHandler                註冊基本事件 包括 觸屏 層的進入 退出 事件
  • registerScriptKeypadHandler          註冊鍵盤事件
  • registerScriptAccelerateHandler      註冊加速事件

registerScriptTouchHandler 詳解(可以設定單點或多點)

function gameWindow:addLayerTouchEventMethod1()
    local function onTouchEvent(eventType, x, y)
        --log("eventType = "..tostring(eventType))
        if eventType == "began" then 
            --需要返回true
            return onTouchBegan(touch, event)
        elseif eventType == "moved" then 
            onTouchMoved(touch, event)
        elseif eventType == "ended" then 
            onTouchEnded(touch, event)
        end
    end
    config.bottomLayer:setTouchEnabled(true)
    config.bottomLayer:registerScriptTouchHandler(onTouchEvent)
end

registerScriptTapHandler    註冊點選事件

function gameWindow:addBtn()
    local btn = cc.MenuItemImage:create("white.png", "black.png", "black.png")
    btn:setPosition(320, 160)
    local function btnClick()
        log("btnClick")
    end
    btn:registerScriptTapHandler(btnClick)

    local menu = cc.Menu:create()
    config.bottomLayer:addChild(menu)
    menu:setPosition(cc.p(0,0))

    menu:addChild(btn)
end

registerScriptHandler        註冊基本事件

function gameWindow:addLayerTouchEventMethod2()
    --建立一個單點觸屏事件
    local listener = cc.EventListenerTouchOneByOne:create()
    --註冊觸屏開始事件
    listener:registerScriptHandler(onTouchBegan, cc.Handler.EVENT_TOUCH_BEGAN)
    --註冊觸屏移動事件
    listener:registerScriptHandler(onTouchMoved, cc.Handler.EVENT_TOUCH_MOVED)
    --註冊觸屏結束事件
    listener:registerScriptHandler(onTouchEnded, cc.Handler.EVENT_TOUCH_ENDED)
    --獲取層的事件派發器
    local eventDispatcher = config.bottomLayer:getEventDispatcher()
    --事件派發器 註冊一個node事件
    eventDispatcher:addEventListenerWithSceneGraphPriority(listener, config.bottomLayer)
end

註冊layer的進入/退出事件用法

function gameWindow:addLayerEnterAndExitEvent()
    local function onNodeEvent(eventType)
        if eventType == "enter" then
            log("enter")
        elseif eventType == "exit" then
            log("exit")
        end
    end
    config.bottomLayer:registerScriptHandler(onNodeEvent)
end
事件分發機制