cocos2d-x動作系統淺析

尊重作者勞動,轉載時請標明文章出處。
作者:Bugs Bunny
地址:http://www.cnblogs.com/cocos2d-x/archive/2012/03/13/2393898.html

在上一篇博文中我們對cocos2d-x的動作使用有了初步瞭解。今天,我們將通過閱讀部分cocos2d-x源碼來了解這個動作系統是如何運作的,以及在使用時還有什麼細節需要特別注意。

小弟初學cocos2d-x,如果文章中存在什麼錯誤或者不足,望各位不吝賜教,及時指出。

http://www.cnblogs.com/cocos2d-x/

動作的祖先 - CCAction

CCAction是所有動作類的基類,我們來看一下CCAction的聲明:

複製代碼
 1 /** 
2 @brief Base class for CCAction objects.
3 */
4 class CC_DLL CCAction : public CCObject
5 {
6 public:
7 CCAction(void);
8 virtual ~CCAction(void);
9
10 char * description();
11
12 virtual CCObject* copyWithZone(CCZone *pZone);
13
14 //! return true if the action has finished
15 virtual bool isDone(void);
16
17 //! called before the action start. It will also set the target.
18 virtual void startWithTarget(CCNode *pTarget);
19
20 /**
21 called after the action has finished. It will set the 'target' to nil.
22 IMPORTANT: You should never call "[action stop]" manually. Instead, use: "target->stopAction(action);"
23 */
24 virtual void stop(void);
25
26 //! called every frame with it's delta time. DON'T override unless you know what you are doing.
27 virtual void step(ccTime dt);
28
29 /**
30 called once per frame. time a value between 0 and 1
31
32 For example:
33 - 0 means that the action just started
34 - 0.5 means that the action is in the middle
35 - 1 means that the action is over
36 */
37 virtual void update(ccTime time);
38
39 inline CCNode* getTarget(void) { return m_pTarget; }
40 /** The action will modify the target properties. */
41 inline void setTarget(CCNode *pTarget) { m_pTarget = pTarget; }
42
43 inline CCNode* getOriginalTarget(void) { return m_pOriginalTarget; }
44 /** Set the original target, since target can be nil.
45 Is the target that were used to run the action.
46 Unless you are doing something complex, like CCActionManager, you should NOT call this method.
47 The target is 'assigned', it is not 'retained'.
48 @since v0.8.2
49 */
50 inline void setOriginalTarget(CCNode *pOriginalTarget) { m_pOriginalTarget = pOriginalTarget; }
51
52 inline int getTag(void) { return m_nTag; }
53 inline void setTag(int nTag) { m_nTag = nTag; }
54
55 public:
56 /** Allocates and initializes the action */
57 static CCAction* action();
58
59 protected:
60 CCNode *m_pOriginalTarget;
61 /** The "target".
62 The target will be set with the 'startWithTarget' method.
63 When the 'stop' method is called, target will be set to nil.
64 The target is 'assigned', it is not 'retained'.
65 */
66 CCNode *m_pTarget;
67 /** The action tag. An identifier of the action */
68 int m_nTag;
69 };
複製代碼

1)description

跳過構造和析構函數,第一個映入眼簾的是description成員函數,沒有註釋。從字面上來看,這個函數應該是返回類的描述,那麼它是如何定義的呢?

複製代碼
1 char * CCAction::description()
2 {
3 char *ret = new char[100] ;
4 sprintf(ret,"<CCAction | Tag = %d>", m_nTag);
5 return ret;
6 }
複製代碼

可以看到description像strdup之類的函數那樣返回了在堆上分配的內存。所以,如果你調用了description,那麼你就要時刻注意,不要忘記將其釋放。

2)isDone

再往下看可以看到copyWithZone成員函數,這是一個繼承自父類的函數,跟動作的關係不大。接下來的函數叫做isDone,通過它可以查詢動作是否執行完畢。大體上來說,這個函數是通過對比流逝時間與目標時間來得出結果。但不同子類可能有着不同的實現,例如:瞬時動作總是返回true,而CCRepeat使用執行次數來取代流逝時間等等。

3)startWithTarget/stop/step

在cocos2d-x中,一個動作從startWithTarget開始,到stop結束。在動作的執行過程中,每幀調用一次step函數。這些構成了一個動作的完整流程。

4)update

此函數接受一個百分比參數,它表示動作的完成進度。update根據這個百分比將目標對象(可能是一個CCSprite對象,也可能是別的什麼)做出相應的調整。
筆者經過統計發現,只有2種函數調用過update,一個是step,另一個就是update本身。在第一種情況中,step通過update來更新動作的表現,在第二種情況中,這多半是一個包含了其它動作的複雜動作(比如CCActionEase類系)。

內務府總管 - CCActionManager

前面我們提到startWithTarget、stop、step構成了一個動作的完整流程,那麼這一整套流程是由誰來驅動的呢?下面我們就着重分析這個問題。

1)startWithTarget

我們知道,在runAction一個動作之前,必須先創建這個動作。
以CCMoveTo爲例,我們是調用它的actionWithDuration成員函數來創建動作的,那麼是不是它調用了startWithTarget開始這個動作呢?
稍微思考一下,不難發現,絕對不是actionWithDuration調用了startWithTarget。因爲startWithTarget需要一個Target,也就是動作的執行者,而這裏僅僅是創建動作,這個執行者的參數無從獲取。
按照這個思路,我們想到了runAction函數。此時動作對象已創建,執行者也是明確的。

複製代碼
 1 CCAction * CCNode::runAction(CCAction* action)
2 {
3 CCAssert( action != NULL, "Argument must be non-nil");
4 CCActionManager::sharedManager()->addAction(action, this, !m_bIsRunning);
5 return action;
6 }
7
8 void CCActionManager::addAction(CCAction *pAction, CCNode *pTarget, bool paused)
9 {
10 CCAssert(pAction != NULL, "");
11 CCAssert(pTarget != NULL, "");
12
13 tHashElement *pElement = NULL;
14 // we should convert it to CCObject*, because we save it as CCObject*
15 CCObject *tmp = pTarget;
16 HASH_FIND_INT(m_pTargets, &tmp, pElement);
17 if (! pElement)
18 {
19 pElement = (tHashElement*)calloc(sizeof(*pElement), 1);
20 pElement->paused = paused;
21 pTarget->retain();
22 pElement->target = pTarget;
23 HASH_ADD_INT(m_pTargets, target, pElement);
24 }
25
26 actionAllocWithHashElement(pElement);
27
28 CCAssert(! ccArrayContainsObject(pElement->actions, pAction), "");
29 ccArrayAppendObject(pElement->actions, pAction);
30
31 pAction->startWithTarget(pTarget);
32 }
複製代碼

可以看到,在CCActionManager的addAction裏,startWithTarget被調用了,這個動作開始了執行。

2)step

還記得我們在介紹CCAction時說過,step函數會按照每幀一次的速度被調用,因此必然存在一套驅動機制。通過對CCMoveTo的update下斷點,可以得到這樣的函數調用關係圖。

可以看出CCActionManager的update調用了動作的step函數(第23行),驅動着動作的執行。

複製代碼
 1 // main loop
2 void CCActionManager::update(ccTime dt)
3 {
4 for (tHashElement *elt = m_pTargets; elt != NULL; )
5 {
6 m_pCurrentTarget = elt;
7 m_bCurrentTargetSalvaged = false;
8
9 if (! m_pCurrentTarget->paused)
10 {
11 // The 'actions' CCMutableArray may change while inside this loop.
12 for (m_pCurrentTarget->actionIndex = 0;
m_pCurrentTarget->actionIndex < m_pCurrentTarget->actions->num;
13 m_pCurrentTarget->actionIndex++)
14 {
15 m_pCurrentTarget->currentAction =
(CCAction*)m_pCurrentTarget->actions->arr[m_pCurrentTarget->actionIndex];
16 if (m_pCurrentTarget->currentAction == NULL)
17 {
18 continue;
19 }
20
21 m_pCurrentTarget->currentActionSalvaged = false;
22
23 m_pCurrentTarget->currentAction->step(dt);
24
25 if (m_pCurrentTarget->currentActionSalvaged)
26 {
27 // The currentAction told the node to remove it. To prevent the action from
28 // accidentally deallocating itself before finishing its step, we retained
29 // it. Now that step is done, it's safe to release it.
30 m_pCurrentTarget->currentAction->release();
31 } else
32 if (m_pCurrentTarget->currentAction->isDone())
33 {
34 m_pCurrentTarget->currentAction->stop();
35
36 CCAction *pAction = m_pCurrentTarget->currentAction;
37 // Make currentAction nil to prevent removeAction from salvaging it.
38 m_pCurrentTarget->currentAction = NULL;
39 removeAction(pAction);
40 }
41
42 m_pCurrentTarget->currentAction = NULL;
43 }
44 }
45
46 // elt, at this moment, is still valid
47 // so it is safe to ask this here (issue #490)
48 elt = (tHashElement*)(elt->hh.next);
49
50 // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
51 if (m_bCurrentTargetSalvaged && m_pCurrentTarget->actions->num == 0)
52 {
53 deleteHashElement(m_pCurrentTarget);
54 }
55 }
56
57 // issue #635
58 m_pCurrentTarget = NULL;
59 }
複製代碼

而在CCActionManager初始化的時候就已經向任務調度系統提交了註冊,保證自己每一幀都得到更新。

複製代碼
1 bool CCActionManager::init(void)
2 {
3 CCScheduler::sharedScheduler()->scheduleUpdateForTarget(this, 0, false);
4 m_pTargets = NULL;
5 return true;
6 }
複製代碼

3)stop

同理,從上面的代碼中(第32行~第40行)可以看到,每次執行step後,會判斷一下動作是否執行完畢,如果完畢則調用stop善後處理,並移除該動作。

小結

CCAction與CCActionManager相互配合,一個井然有序的世界就這樣建立起來了。
需要特別指出的是,大部分時候我們無需直接與CCActionManager打交道,系統會自動爲我們打點好一切。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章