天天看點

學習Cocos2d-x Lua:Lua回調函數小結

這個系列我們主要學習Cocos2d-x Lua,總結Lua開發過程中所涉及的知識點,以及在開發過程中如何使用Cocos Code IDE。這一篇對回調函數做一個小結。

       起因是最近做一個小項目,是用Lua寫的,中間用到了很多的回調,基本Cocos中的那幾種常用回調都用到了,本文就針對所用到的回調函數做一個總結。

1、菜單按鈕的回調

這二者的回調是這麼實作的,建立一個菜單或者是按鈕,為了點選菜單或者按鈕以後實作程式的邏輯,我們需要為菜單和按鈕來綁定一個回調函數,于是乎,我們有了以下的代碼:

--定義菜單項的回調函數
    local function item1_callback()
        --切換場景
        local gameScene = require("GameScene")
        cc.Director:getInstance():replaceScene(gameScene:createScene())
    end
    local function item2_callback()
        --切換場景
        local aboutScene = require("AboutScene")
        cc.Director:getInstance():pushScene(aboutScene:createScene())
    end
    local item1 = cc.MenuItemLabel:create(cc.Label:createWithTTF("開始遊戲","fonts/menu.ttf",42))
    item1:registerScriptTapHandler(item1_callback)
    local item2 = cc.MenuItemLabel:create(cc.Label:createWithTTF("關于遊戲","fonts/menu.ttf",42))
    item2:registerScriptTapHandler(item2_callback)

--建立菜單
    local menu = cc.Menu:create(item1,item2)
    menu:alignItemsVerticallyWithPadding(size.height*0.15)

--添加菜單到遊戲中
    layer:addChild(menu)      

在這裡我們使用了一個重要的回調注冊函數registerScriptTapHandler,Tap就是按下的意思,是以調用這個函數的時候是對菜單和按鈕進行綁定的,這個函數的調用者是菜單項,需要傳入一個參數,這個參數就是綁定的函數。接下來我們看下綁定的函數,我綁定的函數并沒有接受任何傳遞過來的參數,這個就是Lua,你可以選擇接受或者不接受,但是參數就在那裡。為了看看這貨是什麼鳥,我們寫上倆個參數,列印一下,發現一個值是-1,一個值是UserData,有圖為證。

--定義菜單項的回調函數
    local function item1_callback(para1,para2)
        print(para1,para2)
        --切換場景
        local gameScene = require("GameScene")
        cc.Director:getInstance():replaceScene(gameScene:createScene())
    end      

列印的結果是-1和userdata,userdata我們可以了解,你綁定的誰那麼就傳遞過來誰嘛,但是第一個傳遞過來的參數是-1腫麼回事?原來第一個參數代表的是tag,如果你沒有給你的菜單項設定tag,那麼它就是-1,如果設定了tag,那麼這個值就是tag的值,OK,這樣的話就清楚了。以下的代碼是添加button的代碼,一共添加了三個button,大家可以參考一下。

--添加按鈕
function GameScene:addButton(layer)

    --建立button
    local function scale9_normal()
        return cc.Scale9Sprite:create("buttonBackground.png")
    end
    local function scale9_press()
        return cc.Scale9Sprite:create("buttonHighlighted.png")
    end
    
    --文本資訊
    self.button1_label = cc.Label:createWithTTF("Ready","fonts/label.TTF",32)
    self.button2_label = cc.Label:createWithTTF("Ready","fonts/label.TTF",32)
    self.button3_label = cc.Label:createWithTTF("Ready","fonts/label.TTF",32)
    
    --button1
    local button1 = cc.ControlButton:create(self.button1_label,scale9_normal())
    button1:setBackgroundSpriteForState(scale9_press(),cc.CONTROL_EVENTTYPE_TOUCH_DOWN)
    button1:setTag(1)
    button1:setPosition(self.size.width*0.2,self.size.height*0.2)
    
    --button2
    local button2 = cc.ControlButton:create(self.button2_label,scale9_normal())
    button2:setBackgroundSpriteForState(scale9_press(),cc.CONTROL_EVENTTYPE_TOUCH_DOWN)
    button2:setTag(2)
    button2:setPosition(self.size.width*0.5,self.size.height*0.2)
    
    --button3
    local button3 = cc.ControlButton:create(self.button3_label,scale9_normal())
    button3:setBackgroundSpriteForState(scale9_press(),cc.CONTROL_EVENTTYPE_TOUCH_DOWN)
    button3:setTag(3)
    button3:setPosition(self.size.width*0.8,self.size.height*0.2)

    --設定button的回調函數
    local button_callback = function (ref,button)
        
        --如果正确跟新分數
        if self.correct == ref:getTag() then
            self:correct_callback()
        else
            self:wrong_callback()
        end
    end

    --設定回調函數
    button1:registerControlEventHandler(button_callback,cc.CONTROL_EVENTTYPE_TOUCH_UP_INSIDE)
    button2:registerControlEventHandler(button_callback,cc.CONTROL_EVENTTYPE_TOUCH_UP_INSIDE)
    button3:registerControlEventHandler(button_callback,cc.CONTROL_EVENTTYPE_TOUCH_UP_INSIDE)

    --添加到層中
    layer:addChild(button1)
    layer:addChild(button2)
    layer:addChild(button3)
end      

2、添加Android傳回鍵的響應代碼

Android傳回鍵同樣需要回調函數來做這件事情,在Cocos中使用的是事件監聽和分發機制,是以我們需要先注冊一下事件類型,然後綁定回調,代碼如下:

--監聽手機傳回鍵
    local key_listener = cc.EventListenerKeyboard:create()
    
    --傳回鍵回調
    local function key_return()
        
        --結束遊戲
        cc.Director:getInstance():endToLua()
    end
    
    --lua中得回調,厘清誰綁定,監聽誰,事件類型是什麼
    key_listener:registerScriptHandler(key_return,cc.Handler.EVENT_KEYBOARD_RELEASED)
    local eventDispatch = layer:getEventDispatcher()
    eventDispatch:addEventListenerWithSceneGraphPriority(key_listener,layer)      

這次我們使用的注冊回調函數是registerScriptHandler,沒有Tap,顯然就不是為菜單和按鈕回調準備的,是以這個函數是用來注冊一般的回調函數所使用的。其中注釋有句話說的比較好,就是一定要明白誰綁定,監聽誰,事件類型是什麼,在這個例子看來,是事件監聽器去綁定了一個回調函數key_return,然後事件的類型是放到了一個Handler表中的,我們的事件類型是keyBoard,可以到Handler中看看都有哪些事件類型,然後所有你熟悉的東西都會看到了,比如以下的代碼所代表的事件類型。

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 = layerFarm:getEventDispatcher()
eventDispatcher:addEventListenerWithSceneGraphPriority(listener, layerFarm)      

3、動作的回調

3.x以後在C++層的動作回調函數隻剩下Callfunc和CallfuncN了,其他的都可以用c++11的std::bind解決,而在Lua中就剩下一個了,這個是由Lua的特性決定的,用法如下。

--ready和go的回調函數 函數可以有倆個參數,第一個代表接受動作的對象,就是誰執行了動作,第二個是傳過來的參數,全部放到了表中
local  ready_action = function(actionSelf,tab)
end

local go_action = function()
end

cc.CallFunc:create(ready_action,{x=1})      

CallFunc中有倆個參數,一個是綁定的回調函數,另一個是table表,這個表中可以存放一些命名參數,把你想要傳遞的東西統統通過這個表傳遞過去,然後在回調函數中,我們有倆個參數來接受,第一個當然是動作的執行者了,第二個就是傳遞過來的這張表,裡邊有所有我們要得參數,是不是很高大上啊!

4、小結

看完了以上的這些東西,我們基本明白了怎麼使用,所謂知其然更要知其是以然,這些回調函數是怎麼分發到Lua層的代碼中的,我們就需要看一個檔案了。打開你引擎目錄的cocos/scripting/lua-bindings/manual/CCLuaEngine.cpp檔案,我們需要找到這樣一處代碼。

int LuaEngine::sendEvent(ScriptEvent* evt)
{
    if (NULL == evt)
        return 0;

    switch (evt->type)
    {
        case kNodeEvent:
            {
               return handleNodeEvent(evt->data);
            }
            break;
        case kMenuClickedEvent:
            {
                return handleMenuClickedEvent(evt->data);
            }
            break;
        case kCallFuncEvent:
            {
                return handleCallFuncActionEvent(evt->data);
            }
            break;
        case kScheduleEvent:
            {
                return handleScheduler(evt->data);
            }
            break;
        case kTouchEvent:
            {
                return handleTouchEvent(evt->data);
            }
            break;
        case kTouchesEvent:
            {
                return handleTouchesEvent(evt->data);
            }
            break;
        case kKeypadEvent:
            {
                return handleKeypadEvent(evt->data);
            }
            break;
        case kAccelerometerEvent:
            {
                return handleAccelerometerEvent(evt->data);
            }
            break;
        case kCommonEvent:
            {
                return handleCommonEvent(evt->data);
            }
            break;
        case kControlEvent:
            {
                return handlerControlEvent(evt->data);
            }
            break;
        default:
            break;
    }

    return 0;
}      

sendEvent顧名思義,這段代碼就是向Lua層來分發回調事件的代碼,在這段代碼中,我們看到了很多熟悉的身影,比如說node的分發,menu的分發,schedule的分發,touch的分發等等都在這裡,但是卻沒有事件監聽器的分發,沒錯,事件監聽器的回調Lua并沒有寫在這個檔案中,而是在其他的檔案中處理的,大家可以自行研究。我們以node回調Lua層的代碼為例,看看是怎麼回調Lua層的函數的,關鍵的代碼在這裡:

int action = *((int*)(basicScriptData->value));
    switch (action)
    {
        case kNodeOnEnter:
            _stack->pushString("enter");
            break;

        case kNodeOnExit:
            _stack->pushString("exit");
            break;

        case kNodeOnEnterTransitionDidFinish:
            _stack->pushString("enterTransitionFinish");
            break;

        case kNodeOnExitTransitionDidStart:
            _stack->pushString("exitTransitionStart");
            break;

        case kNodeOnCleanup:
            _stack->pushString("cleanup");
            break;

        default:
            return 0;
    }
    int ret = _stack->executeFunctionByHandler(handler, 1);
    _stack->clean();      

根據action的類型,我們将不同的字元串壓入了棧中,然後執行了executeFunctionByHandler函數,第一個參數handler就是回調的函數,第二個參數代表傳遞到回調函數中的參數個數。其他的回調Lua層的函數大同小異,大家自行研究吧。那麼在Lua層,這個回調函數是怎麼被注冊的呢?我們建立工程由系統建立了一個GameScene.lua檔案,這個檔案中的這段代碼,不知道大家是否留意過。

local function onNodeEvent(event)
      if "exit" == event then
          cc.Director:getInstance():getScheduler():unscheduleScriptEntry(self.schedulerID)
      end
end
layerFarm:registerScriptHandler(onNodeEvent)      

是以以後Lua中實作onEnter,onExit函數知道怎麼做了吧,本文就到這裡了。

繼續閱讀