今天我們來學習另一個動作–CallFunc,它是一種特殊的動作,它是看不見的,不像之前我們用的動作都非常具體,比如移動、跳躍、旋轉等。CallFunc的作用就是回調一個函數。
用CallFunc我們可以實現動作監聽。比如,我們想讓一個精靈移動到目的之後,通知我們它到了(顯示一個Label)。用CallFunc就很容易實現。
bool HelloWorld::init() {
auto moveBy = MoveBy::create(1.0f, Vec2(500, 0));
auto arrived = [&]() {
auto visibleSize = Director::getInstance()->getVisibleSize();
auto label = Label::createWithTTF("*** I arrived! ***", "fonts/arial.ttf", 24);
label->setPosition(visibleSize.width / 2, visibleSize.height / 2 + 100);
this->addChild(label);
};
auto callFunc = CallFunc::create(arrived);
sprite1->runAction(Sequence::create(moveBy, callFunc, nullptr));
return true;
}
我們看到,當精靈結束移動後,arrived()函數就會被調用,Label就會被創建顯示。效果:
auto arrived = [](){};
這個回調函數其實就是Lambda函數。這是C++的,與cocos2dx無關。從cocos2dx3.0纔開始支持這種類型的函數回調。
和java裏面的Lambda類似,它是一個匿名函數。它的結構是:[] () {}
[] :表示要開始一個Lambda函數
() :裏面可以填寫函數的參數
{} :裏面是函數的內容
我們寫一個最簡單的例子:
auto func = [](){
log("test");
};
auto callFunc = CallFunc::create(func);
this->runAction(callFunc);
這樣可以在輸出裏面看到test。有人會問,還可以this->runAction?之前不都是sprite->runAction嗎?
其實,只要是繼承於Node的類,都是可以使用runAction的。Sprite是直接繼承Node的,而this(也就是我們HelloWorld類)是繼承於Scene,Scene繼承於Node的。所以可以直接用this->runAction來讓它執行動作。
最上面的例子裏,有人會觀察到中括號裏面有個&,這個符號是用來幹啥的呢?你可以把它去掉試一下,是不是直接就編譯錯誤了。這裏涉及到一個概念,就是變量的捕獲模式。
說人話就是,在Lambda函數中,你想利用Lambda函數外面的變量時,應該怎麼辦?你不要以爲這個Lambda函數寫在你當前運行的函數中(比如當前我們運行在init中),就可以隨便訪問這個函數中的局部變量或者訪問類的全局變量,那是不可能的。爲什麼呢?因爲Lambda函數,其本質也是一個函數,計算機創建它的時候是另外開闢一段內存空間給它的,和你在哪寫的它沒有關係。也就是說,HelloWorld裏面的this(全局變量),還有sprite(局部變量)等,對於Lambda函數來說是完全看不到的。
那麼,我們還想用this, sprite它們,怎麼辦?就需要在[]中加上指定的符號,就能指定變量的捕獲模式,常用的捕獲模式如下:
[]
:不截取任何變量
[&]
:截取外部作用域中的所有變量,並且作爲引用在Lambda函數中使用。只要外部作用域中的變量沒有別釋放,在Lambda函數中就可以使用。所以局部變量時不能使用的,因爲局部變量會被釋放。
[=]
:截取外部作用域中的所有變量,並且拷貝一份在Lambda函數中使用。即使外部變量的值改變了,在Lambda函數執行的時候,依舊是複製時候的值。
[=, &myvar]
:和[=]作用一樣,但是對myvar變量使用引用(也就是[&]方式)。
[myvar]
:和[=]作用一樣,但只針對於myvar一個變量,其他變量忽略。
估計大家已經暈了。還是用代碼解釋一下。
在下面的Lambda函數中,我們建立一個label,然後用this把它添加到HelloWorldScene上,那麼這個this就是外部變量,我們要使用它,設定捕獲模式[&],就是用this了。
bool HelloWorld::init() {
auto arrived = [&]() {
auto visibleSize = Director::getInstance()->getVisibleSize();
auto label = Label::createWithTTF("*** I arrived! ***", "fonts/arial.ttf", 24);
label->setPosition(visibleSize.width / 2, visibleSize.height / 2 + 100);
this->addChild(label);
};
this->runAction(arrived);
return true;
}
那我們再改一下這個代碼,把Label的定義放到Lambda函數外面:
bool HelloWorld::init() {
auto visibleSize = Director::getInstance()->getVisibleSize();
auto label = Label::createWithTTF("*** I arrived! ***", "fonts/arial.ttf", 24);
label->setPosition(visibleSize.width / 2, visibleSize.height / 2 + 100);
auto arrived = [&]() {
this->addChild(label);
};
this->runAction(arrived);
return true;
}
這樣,對於Lambda函數arrived來講,this和Label都是外部變量。那還是使用[&]可以嗎?編譯不過了。因爲this是HelloWorld的全局變量,label是init()函數的局部變量。當init()函數執行完,this是不會被釋放的,而label是會被釋放了(不存在了,你還訪問個啥)。這個地方有點微妙,其實Label這個對象本身是沒有被釋放的,只不過是label這個局部變量被釋放了。換句話說label這個指針被釋放了,但是它指向的對象沒有被釋放。(實在不明白,也不用糾結這塊)
這時候,我們得使用[=]。也就是說,給lambda函數拷貝一份label指針,就OK了。既然Lambda函數是匿名函數,我們當然就可以把函數名省略,直接寫在runAction裏面。
bool HelloWorld::init() {
auto visibleSize = Director::getInstance()->getVisibleSize();
auto label = Label::createWithTTF("*** I arrived! ***", "fonts/arial.ttf", 24);
label->setPosition(visibleSize.width / 2, visibleSize.height / 2 + 100);
this->runAction([=]() {
this->addChild(label);
});
return true;
}
只要理解了[&]和[=],其他的模式也就好理解了。
關於Lambda函數就說這麼多,對於一般的需求應該是夠用了。如果想精益求精,可以自己找資料多研究研究C++的知識。