從本章開始,我們開始講解cocos2d-x庫的動作(Action)。游戲的世界是一個動態的世界:無論是主角精靈還是NPC精靈都處於不斷的運動當中,甚至是背景中漂流的樹葉,隨風而動的小草。這些明顯的或者不明顯的運動構成了我們栩栩如生的游戲世界。
仔細研究游戲中精靈的運動,我們發現:所有這樣的運動都可以細分為若干個基本動作和基本動作的組合。通過進一步擴展,我們可以將同一精靈的更多動作和不同精靈之間的不同動作連貫起來,形成關於整個運動世界的連續模擬。
我們給出示例ZYG003,展示cocos2d-x支持的主要動作:
基本動作
從技術上來說,基本動作的本質就是改變某個圖形對象的屬性:位置、角度、大小等。cocos2d-x提供超過20種基本動作供我們使用。根據改變完成所需的時間,可以分為瞬時動作和延時動作。其中,延時動作的執行速度又可以按照不同的方式來改變(位置、大小、顏色、閃爍……)。
再進一步介紹基本動作之前,我們先來簡單明確一下動作是如何與CCNode關聯起來的。CCNode有一個成員函數叫runAction,定義為:
1 /** Executes an action, and returns the action that is executed.
2 The node becomes the action's target.
3 @warning Starting from v0.8 actions don't retain their target anymore.
4 @since v0.7.1
5 @return An Action pointer
6 */
7 CCAction* runAction(CCAction* action);
此接口確保所有的精靈都可以執行各種動作。也正是為了服從這個接口的定義,導致后續各種組合動作也都從CCAction派生。
下面的代碼是通常調用某個動作的方法:
1 CCSize s = CCDirector::sharedDirector()->getWinSize();
2 // 創建動作
3 CCActionInterval *actionTo = CCMoveTo::actionWithDuration(2.0f, ccp(s.width - 40.0f, s.height - 40.0f));
4 // 使用動作(tamara是一個CCSprite指針)
5 tamara->runAction(actionTo);
瞬時動作
顧名思義,瞬時動作就是不需要時間,馬上就完成的動作。瞬時動作的共同基類是CCActionInstant。
cocos2d-x提供以下瞬時動作:
瞬時動作大都有與之對應的屬性設置方法,之所以作為一個動作來實現,是為了可以與其他動作形成一個連續動作。下面來看一下瞬時動作的使用。
放置 - CCPlace
效果類似於setPosition(ccp(x, y))。示例代碼如下:
1 void InstantActionLayer::onPlace(CCObject* pSender)
2 {
3 CCSize s = CCDirector::sharedDirector()->getWinSize();
4 // 理論上使用偽隨機數前應該srand一下,但是知易的例子中沒有,不知道是不是故意這么設計的。
5 // 如果使用time(NULL)初始化偽隨機數種子,要注意time_t有可能是64位的!
6 CCPoint p = ccp(CCRANDOM_0_1() * s.width, CCRANDOM_0_1() * s.height);
7 flight->runAction(CCPlace::actionWithPosition(p));
8 }
隱藏 - CCHide
效果類似於setIsVisible(false)。示例代碼如下:
1 void InstantActionLayer::onHide(CCObject* pSender)
2 {
3 flight->runAction(CCHide::action());
4 }
顯示 - CCShow
效果類似於setIsVisible(true)。示例代碼如下:
1 void InstantActionLayer::onShow(CCObject* pSender)
2 {
3 flight->runAction(CCShow::action());
4 }
可見切換 - CCToggleVisibility
效果類似於setIsVisible(!getIsVisible())。示例代碼如下:
1 void InstantActionLayer::onToggle(CCObject* pSender)
2 {
3 flight->runAction(CCToggleVisibility::action());
4 }
水平翻轉 - CCFlipX
效果類似於setFlipX(true/false)。示例代碼如下:
1 void InstantActionLayer::onFlipX(CCObject* pSender)
2 {
3 flight->runAction(CCFlipX::actionWithFlipX(true));
4 }
垂直翻轉 - onFlipY
效果類似於setFlipY(true/false)。示例代碼如下:
1 void InstantActionLayer::onFlipY(CCObject* pSender)
2 {
3 flight->runAction(CCFlipY::actionWithFlipY(true));
4 }
還有兩個較為特殊的動作(網格重用 - CCReuseGrid|停止網格 - CCStopGrid),我們以后介紹。
延時動作
延時動作就是指動作的完成需要一段時間。因此,幾乎所有的延時動作都使用執行時間作為第一個參數,它們有着共同的基類CCActionInterval。
cocos2d-x中常用的延時動作:
這里有一個簡單的類命名規則:
CCXxxxTo - 絕對動作,執行的結果與當前的狀態關系不密切;
CCXxxxBy - 相對動作,在當前的狀態上執行某種動作,執行的結果與當前狀態是緊密相關的。
移動到 - CCMoveTo
移動 - CCMoveBy
跳躍到 - CCJumpTo
參數為終點位置、跳躍高度和跳躍次數。
跳躍 - CCJumpBy
貝賽爾曲線 - CCBezierBy
支持三次貝賽爾曲線:P0-起點,P1-起點切線方向,P2-終點切線方向,P3-終點。
首先設置貝塞爾參數,然后執行。
放大到 - CCScaleTo
放大 - CCScaleBy
如果參數為小數,那就是縮小了。
旋轉到 - CCRotateTo
旋轉 - CCRotateBy
閃爍 - CCBlink
色調變化到 - CCTintTo
色調變換 - CCTintBy
變暗到 - CCFadeTo
由無變亮 - CCFadeIn
由亮變無 - CCFadeOut
組合動作
按照一定的次序將上述基本動作組合起來,形成連貫的一套組合動作。組合動作包括以下幾類:
序列 - CCSequence
序列的使用非常簡單,該類從CCActionInterval派生,本身就可以被CCNode對象執行。該類的作用就是線性排列若干個動作,然后按先后次序逐個執行。
1 void CompositionActionLayer::onSequence(CCObject* pSender)
2 {
3 CCSize s = CCDirector::sharedDirector()->getWinSize();
4 // 創建5個動作
5 CCFiniteTimeAction *action0 = CCPlace::actionWithPosition(ccp(s.width / 2, 50));
6 CCFiniteTimeAction *action1 = CCMoveTo::actionWithDuration(1.2f, ccp(s.width - 50.0f, s.height - 50.0f));
7 CCFiniteTimeAction *action2 = CCJumpTo::actionWithDuration(1.2f, ccp(150, 50), 30.0f, 5);
8 CCFiniteTimeAction *action3 = CCBlink::actionWithDuration(1.2f, 3);
9 CCFiniteTimeAction *action4 = CCTintBy::actionWithDuration(0.5f, 0, 255, 255);
10 // 將5個動作組合為一個序列,注意不要忘了用NULL結尾。
11 flight->runAction(CCSequence::actions(action0, action1, action2, action3, action4, action0, NULL));
12 }
同步 - CCSpawn
同步的使用非常簡單,該類也是從CCActionInterval派生,可以被CCNode對象執行。該類的作用就是同時並列執行若干個動作,但要求動作本身是可以同時執行的,比如:移動式翻轉、變色、縮放等。
需要特別注意的是,同步執行最后完成的時間由基本動作中用時最大者決定。
1 void CompositionActionLayer::onSpawn(CCObject* pSender)
2 {
3 CCSize s = CCDirector::sharedDirector()->getWinSize();
4 flight->setRotation(0.0f);
5 flight->setPosition(ccp(s.width / 2, 50));
6 // 創建4個需要並行的動作,確保動作用時可組合。(action1/action2/sequence的執行時間都是2秒)
7 CCFiniteTimeAction *action1 = CCMoveTo::actionWithDuration(2.0f, ccp(s.width - 50.0f, s.height - 50.0f));
8 CCFiniteTimeAction *action2 = CCRotateTo::actionWithDuration(2.0f, 180.0f);
9 CCFiniteTimeAction *action3 = CCScaleTo::actionWithDuration(1.0f, 4.0f);
10 CCFiniteTimeAction *action4 = CCScaleBy::actionWithDuration(1.0f, 0.5f);
11 CCFiniteTimeAction *sequence = CCSequence::actions(action3, action4, NULL);
12 // 創建並執行同步動作。
13 flight->runAction(CCSpawn::actions(action1, action2, sequence, NULL));
14 }
重復有限次數 - CCRepeat
CCRepeat用來將某一動作重復有限次數,示例代碼如下:
1 void CompositionActionLayer::onRepeat(CCObject* pSender)
2 {
3 CCSize s = CCDirector::sharedDirector()->getWinSize();
4 flight->setRotation(0.0f);
5 flight->setPosition(ccp(s.width / 2, 50));
6 // 創建動作序列
7 CCFiniteTimeAction *action1 = CCMoveTo::actionWithDuration(2.0f, ccp(s.width - 50.0f, s.height - 50.0f));
8 CCFiniteTimeAction *action2 = CCJumpBy::actionWithDuration(2.0f, ccp(-400, -200), 30.0f, 5);
9 CCFiniteTimeAction *action3 = CCJumpBy::actionWithDuration(2.0f, ccp(s.width / 2, 0), 20.0f, 3);
10 CCFiniteTimeAction *sequence = CCSequence::actions(action1, action2, action3, NULL);
11 // 重復運行上述動作序列3次
12 flight->runAction(CCRepeat::actionWithAction(sequence, 3));
13 }
反動作 - Reverse
反動作就是反向(逆向)執行某個動作,支持針對動作序列的反動作序列。反動作不是一個專門的類,而是CCFiniteTimeAction引入的一個接口。不是所有的類都支持反動作,CCXxxxTo類通常不支持反動作,而CCXxxxBy類通常支持,示例如下:
1 void CompositionActionLayer::onReverse(CCObject* pSender)
2 {
3 CCSize s = CCDirector::sharedDirector()->getWinSize();
4 flight->setRotation(0.0f);
5 flight->setPosition(ccp(s.width / 2, 50));
6
7 CCFiniteTimeAction *action1 = CCMoveBy::actionWithDuration(2.0f, ccp(190, 220));
8 // 創建某個動作的反動作
9 CCFiniteTimeAction *action2 = action1->reverse();
10
11 flight->runAction(CCRepeat::actionWithAction(CCSequence::actions(action1, action2, NULL), 2));
12 }
動畫 - CCAnimate
動畫就是讓精靈自身連續執行一段影像,形成模擬運動的效果:行走時的狀態,打斗時的狀態等。
1 void CompositionActionLayer::onAnimation(CCObject* pSender)
2 {
3 CCSpriteBatchNode *mgr = static_cast<CCSpriteBatchNode *>(this->getChildByTag(4));
4
5 CCAnimation *animation = CCAnimation::animation();
6 animation->setName("flight");
7 animation->setDelay(0.2f);
8 for (int i = 0; i < 3; ++i)
9 {
10 // 定義每一幀的內容
11 float x = static_cast<float>(i % 3);
12 animation->addFrameWithTexture(mgr->getTexture(), CCRectMake(x * 32, 0, 31, 30));
13 }
14 // 創建並執行動畫效果,而且要重復10次。
15 CCAnimate *action = CCAnimate::actionWithAnimation(animation);
16 flight->runAction(CCRepeat::actionWithAction(action, 10));
17 }
無限重復 - CCRepeatForever
在CCRepeatForever Class Reference中,有這樣一條警告“Warning: This action can't be Sequenceable because it is not an IntervalAction”,而實際上它的確是派生自CCActionInterval,這真有點兒把我也搞懵了。
僅從其本意來說,該類的作用就是無限期執行某個動作或動作序列,直到被停止。因此無法參與序列和同步,自身也無法反向執行(但是你可以將某一動作反向,然后無限重復執行)。
1 void CompositionActionLayer::onRepeatForever(CCObject* pSender)
2 {
3 CCSpriteBatchNode *mgr = static_cast<CCSpriteBatchNode *>(this->getChildByTag(4));
4 // 飛行噴火模擬動畫
5 CCAnimation *animation = CCAnimation::animation();
6 animation->setName("flight");
7 animation->setDelay(0.1f);
8 for (int i = 0; i < 3; ++i)
9 {
10 float x = static_cast<float>(i % 3);
11 animation->addFrameWithTexture(mgr->getTexture(), CCRectMake(x * 32, 0, 31, 30));
12 }
13 CCAnimate *action = CCAnimate::actionWithAnimation(animation);
14 // 將該動畫作為精靈的本征動畫,一直運行。
15 flight->runAction(CCRepeatForever::actionWithAction(action));
16
17 CCSize s = CCDirector::sharedDirector()->getWinSize();
18 flight->setRotation(0.0f);
19 flight->setPosition(ccp(100, 50));
20
21 // 創建第二個連續無限期動作序列,疊加兩者形成完整效果。
22 ccBezierConfig bezier;
23 bezier.controlPoint_1 = ccp(0, s.height / 2);
24 bezier.controlPoint_2 = ccp(300, -s.height / 2);
25 bezier.endPosition = ccp(300, 100);
26 CCFiniteTimeAction *action1 = CCBezierBy::actionWithDuration(3.0f, bezier);
27 CCFiniteTimeAction *action2 = CCTintBy::actionWithDuration(0.5f, 0, 255, 255);
28 CCFiniteTimeAction *action3 = CCSpawn::actions(action1, CCRepeat::actionWithAction(action2, 4), NULL);
29 CCFiniteTimeAction *action4 = CCSpawn::actions(action1->reverse(), CCRepeat::actionWithAction(action2, 4), NULL);
30 // CCSequence的actions成員函數返回的是CCFiniteTimeAction指針類型,
31 // 而CCRepeatForever的actionWithAction接受的是CCActionInterval指針類型,
32 // 所以這里需要強轉一下,轉成CCSequence指針類型,
33 // 只要保證序列中有2個或2個以上的動作,這么做是絕對沒有問題的。
34 flight->runAction(CCRepeatForever::actionWithAction(static_cast<CCSequence *>(CCSequence::actions(action3, action4, NULL))));
35 }
速度變化
基本動作和組合動作實現了針對精靈的各種運動和動畫效果,但它們的速度通常是恆定不變的。通過CCActionEase類系和CCSpeed類,我們可以很方便地改變精靈執行動作的速度,是由快至慢還是由慢至快。
CCEaseIn - 由慢至快(速度線性變化)
CCEaseOut - 由快至慢
CCEaseInOut - 由慢至快再由快至慢
CCEaseSineIn - 由慢至快(速度正弦變化)
CCEaseSineOut - 由快至慢
CCEaseSineInOut - 由慢至快再由快至慢
CCEaseExponentialIn - 由慢至極快(速度指數級變化)
CCEaseExponentialOut - 由極快至慢
CCEaseExponentialInOut - 由慢至極快再由極快至慢
CCSpeed - 人工設定速度,還可通過setSpeed不斷調整。
擴展動作
我們已經掌握了各種各樣的動作,也可以按照不同的速度要求修改動作執行的時間,cocos2d-x還提供了針對現有動作的擴展,以實現各種靈活的效果。
延時 - CCDelayTime
通過CCDelayTime,我們可以在動作序列中增加一個時間間歇。
1 void ExtendActionLayer::onDelay(CCObject* pSender)
2 {
3 CCFiniteTimeAction *action1 = CCMoveBy::actionWithDuration(1.2f, ccp(200, 200));
4 CCFiniteTimeAction *action2 = action1->reverse();
5 // 實現一個等待間歇
6 flight->runAction(CCSequence::actions(action1, CCDelayTime::actionWithDuration(1.0f), action2, NULL));
7 }
函數調用
在動作序列中間或者末尾調用某個函數,執行任何任務:動作、狀態修改等。在cocos2d-x中,調用函數的動作一共有4種。
1.CCCallFunc
僅函數調用,無任何參數。
1 void ExtendActionLayer::onCallBack1()
2 {
3 flight->runAction(CCTintBy::actionWithDuration(0.5f, 255, 0, 255));
4 }
5
6 void ExtendActionLayer::onCallFunc(CCObject* pSender)
7 {
8 CCFiniteTimeAction *action1 = CCMoveBy::actionWithDuration(2.0f, ccp(200, 200));
9 CCFiniteTimeAction *action2 = action1->reverse();
10 CCFiniteTimeAction *actionF = CCCallFunc::actionWithTarget(this, callfunc_selector(ExtendActionLayer::onCallBack1));
11 flight->runAction(CCSequence::actions(action1, actionF, action2, NULL));
12 }
2.CCCallFuncN
調用函數,並將當前對象的指針(CCNode指針)作為第一個參數傳遞進去。
1 void ExtendActionLayer::onCallBack2(CCNode* pSender)
2 {
3 // 在這個例子里,pSender就是flight,因為是他執行了那個actionF
4 pSender->runAction(CCTintBy::actionWithDuration(1.0f, 255, 0, 255));
5 }
6
7 void ExtendActionLayer::onCallFuncN(CCObject* pSender)
8 {
9 CCFiniteTimeAction *action1 = CCMoveBy::actionWithDuration(2.0f, ccp(200, 200));
10 CCFiniteTimeAction *action2 = action1->reverse();
11 CCFiniteTimeAction *actionF = CCCallFuncN::actionWithTarget(this, callfuncN_selector(ExtendActionLayer::onCallBack2));
12 flight->runAction(CCSequence::actions(action1, actionF, action2, NULL));
13 }
3.CCCallFuncND
在前一種方式的基礎上增加一個數據參數,這是void指針類型。
1 void ExtendActionLayer::onCallBack3(CCNode* pSender, void* pData)
2 {
3 pSender->runAction(CCTintBy::actionWithDuration(static_cast<float>((int)pData), 255, 0, 255));
4 }
5
6 void ExtendActionLayer::onCallFuncND(CCObject* pSender)
7 {
8 CCFiniteTimeAction *action1 = CCMoveBy::actionWithDuration(2.0f, ccp(200, 200));
9 CCFiniteTimeAction *action2 = action1->reverse();
10 // 這里直接將整數常量強轉成(void *)類型似乎有欠妥當,但對這些Action的生命周期不太清楚,稍后深入一下。
11 CCFiniteTimeAction *actionF = CCCallFuncND::actionWithTarget(this, callfuncND_selector(ExtendActionLayer::onCallBack3), (void *)2);
12 flight->runAction(CCSequence::actions(action1, actionF, action2, NULL));
13 }
4.CCCallFuncO
調用函數,並傳遞一個CCObject指針作為參數。這個似乎不太常用,資料比較少,以后再深入。
小結
至此,我們對cocos2d-x支持的動作有了整體了解。動作是我們的好幫手,它讓游戲世界充滿生機。在后面的章節中,我們會對部分動作以及動作系統繼續深入。