Cocos2Dx之動作Action
不錯的文章轉載了,原文地址:http://my.oschina.net/sulliy/blog/290914
Cocos2Dx提供了調度器。結合調度器,我們可以不斷地修改節點的屬性,從而實現豐富的動態效果。但是這樣做過於麻煩。舉個例子,我們需要把一個精靈從一個位置移動到另外一個位置。從我們前面提到的內容來思考一個可行的方案:在父節點的update中,將它的位置移動鎮間隔時間移動的距離。假定目標位置到初始距離爲d,移動時間指定爲t秒,幀間隔時間1/60秒,那麼每個update我們需要往目標位置移動d/60t。這還是勻速地移動,如果要求加速移動的效果,或者減速移動的效果,情況就變得更加複雜一些了。
爲了簡化節點的動作執行,一般是CCSprite,Cocos2Dx提供了CAction來支持動作。動作可以簡單地分爲兩類,一是瞬時動作,即立即完成的動作;二是持續性動作,即動作的執行需要持續一段時間。另外,我們還需要有能夠組合各種動作的能力,稱之爲複合動作。下面我們,先看看動作的分類。
瞬時動作。瞬時動作就是下一幀就要完成的動作,比如定位、縮放。其實這樣的動作不需要定時機制,修改節點的屬性即可,但還是封裝爲一個動作,目的是方便進行做動作的組合。瞬時動作包括:
-
CCPlace:將節點放置到某個指定位置,其作用與修改節點的Position屬性相同。
-
CCFlipX和CCFlipY:用於將精靈沿X和Y軸反向顯示,其作用與設置精靈的FlipX和FlipY屬性相同。
-
CCShow和CCHide:用於顯示和隱藏節點,其作用與設置節點的Visible屬性的作用一樣。
-
CCCallFunc:CCCallFunc系列動作包括CCCallFunc、CCCallFuncN、CCCallFuncND,以及CCCall- FuncO四個動作,用來在動作中進行類的實例方法的調用。
持續性動作。持續性動作,以爲着動作的執行需要持續一段時間。所以,這些都做都需要指定動作執行的時間:duration。持續性動作還可以根據產生的效果不同進一步細分。
-
位置變化動作:針對位置(position)屬性。
-
CCMoveTo和CCMoveBy:用於使節點做直線運動。
-
CCJumpTo和CCJumpBy:使節點以一定的軌跡跳躍到指定位置。
-
CCBezierTo和CCBezierBy:使節點進行曲線運動,運動的軌跡由貝塞爾曲線描述。
-
by是相對於節點的位置;to是絕對位置。
-
屬性變化動作:通過屬性值的逐漸變化來實現動畫效果。
-
CCScaleTo和CCScaleBy:產生縮放效果,使節點的縮放係數隨時間線性變化。
-
CCRotateTo和CCRotateBy:產生旋轉效果。
-
CCFadeIn和CCFadeOut:產生淡入淡出效果,其中前者實現了淡入效果,後者實現了淡出效果。
-
CCFadeTo:用於設置一段時間內透明度的變化效果。
-
CCTintTo和CCTintBy:設置色調變化。可以理解爲RGB顏色中的某一種顏色值變化了。
-
-
視覺特效動作:一些特殊的視覺效果。
-
CCBlink:使目標節點閃爍。
-
CCAnimation:播放幀動畫,用幀動畫的形式實現動畫效果。
-
-
控制動作:對一些列動作進行精細控制。
-
CCDelayTime:將動作延時一定的時間才執行。
-
CCRepeat:把現有的動作重複一定次數。
-
CCRepeatForever:現有動作一直執行下去。
-
複合動作。複合動作是由一些基本動作一起組合成的新動作。可以滿足遊戲需要的複雜動作需求。
-
CCSpawn:允許一批動作同時執行。
-
CCSequence:順序執行一系列動作。
-
CCSpeed:改變動作執行的速度。
-
CCActionEase:是一類動作,不同於CCSpeed的勻速執行,CCActionEase允許多樣化地控制速度。
-
CCEaseRateAction
-
CCEaseIn
-
CCEaseOut
-
CCEaseInOut
-
-
CCEaseExponentialIn
-
CCEaseExponentialIn
-
CCEaseExponentialInOut
-
CCEaseSineIn
-
CCEaseSineOut
-
CCEaseSineInOut
-
CCEaseElastic
-
CCEaseElasticIn
-
CCEaseElasticOut
-
CCEaseElasticInOut
-
-
CCEaseBounce
-
CCEaseBounceIn
-
CCEaseBounceOut
-
CCEaseBounceInOut
-
-
CCEaseBackIn
-
CCEaseBackOut
-
CCEaseBackInOut
-
JQuery有個比較詳盡的Easing效果示例。可以訪問http://jqueryui.com/easing/查看。並不是所有的Easing效果Cocos2Dx都支持。
知道大概有哪些Action後,我們看看Action是怎麼得到調度執行的。
我們執行一個Action的方法,一般是是調用CCSripte::runAction,因爲動作一般都是作用於某個精靈的。CCSripte繼承自CCNodeRGBA,後者繼承自CCNode。CCNode提供了runAction來執行動作。CCLayer也繼承自CCNode,因此它也是可以執行動作的。後面都假定執行動作的對象是一個CCSprite。
CCNode有一個成員m_pActionManager,它默認初始化爲CCDirector內部的CCActionManager。CCDirector在它的init初始化函數中會創建一個CCActionManager對象。這類似於調度器CCScheduler。CCNode擁有指向CCActionManager對象的指針,因此在CCNode::runAction裏面,通過CCActionManager的addAction將自己添加到CCActionManager當中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
void CCActionManager::addAction(CCAction *pAction, CCNode *pTarget, bool paused) { tHashElement *pElement = NULL; CCObject *tmp = pTarget; HASH_FIND_INT(m_pTargets, &tmp, pElement); if (! pElement) { pElement = (tHashElement*) calloc ( sizeof (*pElement), 1); pElement->paused = paused; pTarget->retain(); pElement->target = pTarget; HASH_ADD_INT(m_pTargets, target, pElement); } actionAllocWithHashElement(pElement); ccArrayAppendObject(pElement->actions, pAction); pAction->startWithTarget(pTarget); } |
CCActionManager同樣用一個HASH表來存儲動作CCAction,HASH表的鍵還是CCObject類型的指針。每個CCSprite可能同時在執行多個動作。CCActionManager將這些動作放在一個數組當中(tHashElement->actions)。CCActionManager::addAction先根據傳入的pTarget作爲鍵來查看HASH表中是否已經存在對應的Bucket。如果沒有找到,就現在HASH表中創建一個。然後,將現在的CCAction添加到數組的末尾。
到目前爲止,我們只是將需要執行的動作放在一個HASH表中。什麼時候執行呢?這就要看CCDirector::init。CCDirector初始化的時候,先創建一個CCScheduler,然後創建一個CCActionManager,隨後馬上調用CCScheduler::scheduleUpdateForTarget將自己註冊爲一個update調度器。並且update調度器的優先級設爲了最高優先級kCCPrioritySystem(INT_MIN)。
CCScheduler::scheduleUpdateForTarget的實現我們前面已經討論過了。它將註冊的update調度器根據優先級放到不同的列表當中。update調度器在每個幀間隔時間到期之後就會被調用。CCActionManager把自己註冊爲一個update調度器,那麼在每個幀間隔時間到期之後,就會調用CCActionManager的update函數。由於CCActionManager管理所有的動作,因此CCActionManager::update相當於管理動作的一個主循環。
注:到這裏,我們已經討論過不少Cocos2Dx的主循環:應用程序主循環、CCDirector的主循環、CCScheduler的主循環、CCScene的主循環、負責內存維護的棧循環。現在我們討論到了負責管理動作的CCActionManager主循環。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
void CCActionManager::update( float dt) { for (tHashElement *elt = m_pTargets; elt != NULL; ) { m_pCurrentTarget = elt; m_bCurrentTargetSalvaged = false ; if (! m_pCurrentTarget->paused) { for (m_pCurrentTarget->actionIndex = 0; m_pCurrentTarget->actionIndex < m_pCurrentTarget->actions->num; m_pCurrentTarget->actionIndex++) { m_pCurrentTarget->currentAction = (CCAction*)m_pCurrentTarget->actions->arr[m_pCurrentTarget->actionIndex]; if (m_pCurrentTarget->currentAction == NULL) { continue ; } m_pCurrentTarget->currentActionSalvaged = false ; m_pCurrentTarget->currentAction->step(dt); if (m_pCurrentTarget->currentActionSalvaged) { m_pCurrentTarget->currentAction->release(); } else if (m_pCurrentTarget->currentAction->isDone()) { m_pCurrentTarget->currentAction->stop(); CCAction *pAction = m_pCurrentTarget->currentAction; m_pCurrentTarget->currentAction = NULL; removeAction(pAction); } m_pCurrentTarget->currentAction = NULL; } } elt = (tHashElement*)(elt->hh.next); if (m_bCurrentTargetSalvaged && m_pCurrentTarget->actions->num == 0) { deleteHashElement(m_pCurrentTarget); } } m_pCurrentTarget = NULL; } |
CCActionManager::update的外循環遍歷所有的HASH表的Bucket,內循環遍歷CCSprite上的所有動作。執行動作是通過調用CCAction的step函數(多態調用)。不同類型的動作,可能實現了不同的step。我們後面再看不同的step實現。currentActionSalvaged用來標記動作是否被要求刪除。CCAction的isDone虛函數用來幫助CCActionManager判斷當前的動作是否已經完成。CCAction的stop虛函數是在清理動作之前執行的回調函數。如果動作執行完step之後,已經完成了,那麼動作就會從HASH表中Bucket的數組上刪除。最後,如果某個CCSprite上已經沒有附加任何動作,就刪除HASH表中這個Bucket。
我們可以看出,實現一個動作的關鍵是在繼承CCAction的基礎上實現這些虛函數:
-
void step(float dt)
-
void stop(void)
-
bool isDone(void)
-
void update(float time) 真正實現動作的地方,step相當於一個包裝器,它內部會調用update。
動作相關的類很多。它們放在\cocos2dx\actions下面。我們不討論所有的動作,挑選一兩個看下怎麼在CCAction的基礎上實現動作即可。點到爲止。
CCActionInterval和CCJumpBy
CCActionInterval是持續性動作。意味着動作的完成可能會持續大於一幀的時間。CCActionInterval和CCActionInstant都是繼承自CCFiniteTimeAction。對計算機來說,時間還是一個有限量。CCFiniteTimeAction本身只是提供了對動作持續時間duration的封裝。CCActionInstant的duration爲0而已。CCActionInterval的duration是構造函數傳入的。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
void CCActionInterval::step( float dt) { if (m_bFirstTick) { m_bFirstTick = false ; m_elapsed = 0; } else { m_elapsed += dt; } this ->update(MAX(0, MIN(1, m_elapsed / MAX(m_fDuration, FLT_EPSILON)))); } |
CCActionInterval的step累積已經流逝的時間,存放在m_elapsed當中。如果流逝的時間m_elapsed大於我們指定的動作執行時間duration,那麼動作就該完成了,這就是CCActionInterval::isDone做的事情。
需要注意的是,傳給動作update的時間是一個0-1的浮點數。它表示整個動作完成的程度。由於每幀都會調用step,那麼動作的update函數的執行次數就是按duration和幀間隔時間除的結果。step是用來控制動作執行過程的。
因此CCJumpBy需要做的就是在每幀調用update時,將CCSprite移動到合適的位置。
1
2
3
4
5
6
7
8
9
10
11
|
void CCJumpBy::update( float t) { if (m_pTarget) { float frac = fmodf( t * m_nJumps, 1.0f ); float y = m_height * 4 * frac * (1 - frac); y += m_delta.y * t; float x = m_delta.x * t; m_pTarget->setPosition(ccpAdd( m_startPosition, ccp(x,y))); } } |
CCSpeed
CCSpeed本身並不是一個獨立的動作。它作用於其他的繼續性動作(CCActionInterval),可以加快或者降低其他動作的執行速度。具體實現的辦法是在step的流逝時間參數上乘以了一個速度因子,實現非常簡單。
他的isDone和stop都是調用作用動作的對應實現。
CCActionInstant和CCPlace
CCActionInstant是在下一幀就會完成的動作。CCActionInstant::isDone的直接返回ture,因此CCActionInstant::step只會執行一次,對應的動作的update函數也就只會執行一次。
CCPlace繼承自CCActionInstant,它的update函數也就非常簡單了,直接將自己的位置設置爲目標位置即可。
由於動作的執行只是改變了CCSprite的屬性,但是還需要在當前幀上反應出來,因此調度器的調用應該在渲染場景之前,詳見CCDirector::drawScene。
還有一個特殊動作是CCAnimate,它和CCAnimation一起完成動畫效果。我們後面再討論。
CCCallFunc、CCCallFuncN、CCCallFuncND、CCCallFuncO
這類動作比較特殊,它們用來幫助執行自定義的回調函數。後綴代表的是不同的參數。