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打交道,系统会自动为我们打点好一切。

posted @ 2012-03-06 13:12  Bugs Bunny  阅读(14107)  评论(1编辑  收藏  举报