首先CCAction是所有动作的基类,如下图继承关系:
那么来看看CCAction的定义:
class CC_DLL CCAction : public CCObject { public: CCAction(void); virtual ~CCAction(void); const char* description(); virtual CCObject* copyWithZone(CCZone *pZone); //! return true if the action has finished virtual bool isDone(void); virtual void startWithTarget(CCNode *pTarget); /** called after the action has finished. It will set the 'target' to nil. IMPORTANT: You should never call "[action stop]" manually. Instead,use: "target->stopAction(action);" */ virtual void stop(void); //! called every frame with it's delta time. DON'T override unless you know what you are doing. virtual void step(float dt); virtual void update(float time); inline CCNode* getTarget(void) { return m_pTarget; } inline void setTarget(CCNode *pTarget) { m_pTarget = pTarget; } inline CCNode* getOriginalTarget(void) { return m_pOriginalTarget; } inline void setOriginalTarget(CCNode *pOriginalTarget) { m_pOriginalTarget = pOriginalTarget; } inline int getTag(void) { return m_nTag; } inline void setTag(int nTag) { m_nTag = nTag; } public: static CCAction* create(); protected: CCNode *m_pOriginalTarget; CCNode *m_pTarget; int m_nTag; };
在类定义最后有三个成员变量,而继承自CCAction的CCFiniteTimeAction主要新增加了一个用于保存该动作总完成时间的成员变量float m_fDuration;
对于其两个子类CCActionInstant和CCActionInterval,前者没有新增任何函数和变量,而后者增加了两个成员变量:float m_elapsed(记录从动作开始起逝去的时间);和bool m_bFirstTick(一个控制变量);
那么动作是如何执行的呢?
当一个节点调用runAction方法时,动作管理类CCActionManager(单例类)会将新的动作和节点添加到其管理的动作表中。
CCAction * CCNode::runAction(CCAction* action) { CCAssert( action != NULL,"Argument must be non-nil"); m_pActionManager->addAction(action,this,!m_bRunning); return action; }
在addAction中,将动作添加到动作队列后,就会对该动作调用其成员函数startWithTarget(CCNode* pTarget)来绑定该动作的执行节点,和初始化动作类的成员变量。
这些工作都完成后,每一帧刷新屏幕时,系统就会在CCActionManager中遍历动作表中的每一个动作,并调用动作的step(float)方法。而step方法主要负责计算m_elapsed的值,并调用update(float)方法。
void CCActionInterval::step(float dt) { if (m_bFirstTick) { m_bFirstTick = false; m_elapsed = 0; } else { m_elapsed += dt; } this->update(MAX (0,// needed for rewind. elapsed could be negative MIN(1,m_elapsed / MAX(m_fDuration,FLT_EPSILON) // division by 0 ) ) ); }
传入update方法的float型参数表示逝去的时间与动作完成需要的时间的比值,介于0-1之间,即动作完成的百分比。然后在update方法中,通过完成比例对节点的属性进行操作来达到动作的效果。
例如:对MoveBy调用update时,通过传入的比例调用setPosition直接修改节点的属性。
最后在每一帧结束后,CCActionManager的update会检查动作队列中每个动作的isDone函数是否返回true,如果返回true,则动作结束,将其从队列中删除。
——————————————————————————————————————————————————————————————————————————
从上面知道:动作都是由CCActionManager来管理。那我们再来看看CCActionManager的工作原理。
在CCDirector初始化时,执行了如下代码:
// scheduler m_pScheduler = new CCScheduler(); // action manager m_pActionManager = new CCActionManager(); m_pScheduler->scheduleUpdateForTarget(m_pActionManager,kCCPrioritySystem,false);
可见动作管理类在创建时就注册了Update定时器,那么CCScheduler在游戏的每一帧mainLoop更新中都会触发CCActionManager注册的update(float )方法。调度器原理请参照此链接:http://www.cnblogs.com/songcf/p/3162414.html
// main loop void CCActionManager::update(float dt) { //枚举动作表中的每一个节点 for (tHashElement *elt = m_pTargets; elt != NULL; ) { m_pCurrentTarget = elt; m_bCurrentTargetSalvaged = false; if (! m_pCurrentTarget->paused) { //枚举节点的每一个动作 actions数组可能会在循环中被修改 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) { // The currentAction told the node to remove it. To prevent the action from // accidentally deallocating itself before finishing its step,we retained // it. Now that step is done,it's safe to release it. m_pCurrentTarget->currentAction->release(); } else if (m_pCurrentTarget->currentAction->isDone()) { m_pCurrentTarget->currentAction->stop(); CCAction *pAction = m_pCurrentTarget->currentAction; // Make currentAction nil to prevent removeAction from salvaging it. m_pCurrentTarget->currentAction = NULL; removeAction(pAction); } m_pCurrentTarget->currentAction = NULL; } } // elt,at this moment,is still valid // so it is safe to ask this here (issue #490) elt = (tHashElement*)(elt->hh.next); // only delete currentTarget if no actions were scheduled during the cycle (issue #481) if (m_bCurrentTargetSalvaged && m_pCurrentTarget->actions->num == 0) { deleteHashElement(m_pCurrentTarget); } } // issue #635 m_pCurrentTarget = NULL; }