cocos2d-x中的动作分析

时间:2023-02-08 07:51:13

在cocos2d-x中动作的执行调度是受cocos2d-x的全局定时器控制的,当初始完导演后便已经启动用于管理动作的update定时器。

bool CCDirector::init(void)
{
...
m_pActionManager = new CCActionManager();
m_pScheduler->scheduleUpdateForTarget(m_pActionManager, kCCPrioritySystem, false);
...

return true;
}
也就说主循环每帧都会调用这个定时器的调度,且优先级是小于0的,是一个高优先级的定时器。从上一篇文章可以得到的一个结果是当执行这个定时器调度时,由于是update的定时器,所以直接是每帧的调用m_pActionManager的update方法。

所以先看下这个动作管理器的定义:

//动作管理类
class CC_DLL CCActionManager : public CCObject
{
public:
/**
* @js ctor
*/
//构造
CCActionManager(void);
/**
* @js NA
* @lua NA
*/
//析构
~CCActionManager(void);

// actions

/** Adds an action with a target.
If the target is already present, then the action will be added to the existing target.
If the target is not present, a new instance of this target will be created either paused or not, and the action will be added to the newly created target.
When the target is paused, the queued actions won't be 'ticked'.
*/
//添加一个动作到管理器中
void addAction(CCAction *pAction, CCNode *pTarget, bool paused);

/** Removes all actions from all the targets.
*/
//从管理器中移除所有动作
void removeAllActions(void);

/** Removes all actions from a certain target.
All the actions that belongs to the target will be removed.
*/
//从管理器中移除节点的所有动作
void removeAllActionsFromTarget(CCObject *pTarget);

/** Removes an action given an action reference.
*/
//从管理器中移除一个动作
void removeAction(CCAction *pAction);

/** Removes an action given its tag and the target */
//从管理器中根据动作标记移除一个动作
void removeActionByTag(unsigned int tag, CCObject *pTarget);

/** Gets an action given its tag an a target
@return the Action the with the given tag
*/
//从管理器中获取一个带有指定标记的动作
CCAction* getActionByTag(unsigned int tag, CCObject *pTarget);

/** Returns the numbers of actions that are running in a certain target.
* Composable actions are counted as 1 action. Example:
* - If you are running 1 Sequence of 7 actions, it will return 1.
* - If you are running 7 Sequences of 2 actions, it will return 7.
*/
//返回指定节点中正在运行的动作的数量
unsigned int numberOfRunningActionsInTarget(CCObject *pTarget);

/** Pauses the target: all running actions and newly added actions will be paused.
*/
//暂停节点的动作
void pauseTarget(CCObject *pTarget);

/** Resumes the target. All queued actions will be resumed.
*/
//恢复节点的动作
void resumeTarget(CCObject *pTarget);

/** Pauses all running actions, returning a list of targets whose actions were paused.
*/
//暂停所有正在运行的动作,并返回这些动作的集合
CCSet* pauseAllRunningActions();

/** Resume a set of targets (convenience function to reverse a pauseAllRunningActions call)
*/
//恢复集合中指定的动作
void resumeTargets(CCSet *targetsToResume);

protected:
// declared in CCActionManager.m
//根据索引移除
void removeActionAtIndex(unsigned int uIndex, struct _hashElement *pElement);
//删除哈希表中指定的项
void deleteHashElement(struct _hashElement *pElement);
//为动作申请内存空间
void actionAllocWithHashElement(struct _hashElement *pElement);
//更新,由主循环的update定时器调用更新
void update(float dt);

protected:
struct _hashElement *m_pTargets;//存储目标节点的哈希表
struct _hashElement *m_pCurrentTarget;//当前的节点的哈希元素
bool m_bCurrentTargetSalvaged;//可回收标记
};

从上面的注释可以看出它的功能就是将动作加到自己的管理器中进行管理,当动作执行完后便会把这个动作从管理器中移除掉,另外还定义了两个哈希表来管理多个节点和执行节点和一个辅助的回收标记变量,跟定时器是设计十分相像:

typedef struct _hashElement
{
struct _ccArray *actions;//动作数组,即一个节点可以允许有多个动作
CCObject *target;//执行动作的目标节点
unsigned int actionIndex;//索引
CCAction *currentAction;//当前正在执行的动作
bool currentActionSalvaged;//当前正在执行的动作是否被标记为可回收
bool paused;//是否暂停
UT_hash_handle hh;//操作哈希表的句柄
} tHashElement;
整个实现细节如下(最终要的便是update是每帧都会被主循环所调用):‘

CCActionManager::CCActionManager(void)
: m_pTargets(NULL),
m_pCurrentTarget(NULL),
m_bCurrentTargetSalvaged(false)
{

}

CCActionManager::~CCActionManager(void)
{
CCLOGINFO("cocos2d: deallocing %p", this);

removeAllActions();//移除多有的动作
}

// private

void CCActionManager::deleteHashElement(tHashElement *pElement)
{
ccArrayFree(pElement->actions);//释放资源
HASH_DEL(m_pTargets, pElement);//从哈希表中删除元素
pElement->target->release();//动作目标的引用计数减1
free(pElement);//释放哈希元素
}

void CCActionManager::actionAllocWithHashElement(tHashElement *pElement)
{
// 4 actions per Node by default
if (pElement->actions == NULL)//每个节点初始化可存放4个节点
{
pElement->actions = ccArrayNew(4);
}else
if (pElement->actions->num == pElement->actions->max)//如果节点的动作数量大于当前的已拥有的动作数量
{
ccArrayDoubleCapacity(pElement->actions);//将容量扩大2倍
}

}

void CCActionManager::removeActionAtIndex(unsigned int uIndex, tHashElement *pElement)
{
CCAction *pAction = (CCAction*)pElement->actions->arr[uIndex];

if (pAction == pElement->currentAction && (! pElement->currentActionSalvaged))//如果待移除的动作与当前正在执行的动作相等且没有被标记可回收,那么将其标记可回收
{
pElement->currentAction->retain();//引用计数加1,保证当前的动作能完整的被执行完
pElement->currentActionSalvaged = true;//标记为可回收该动作
}

ccArrayRemoveObjectAtIndex(pElement->actions, uIndex, true);//将该动作从动作管理器中移除掉

// update actionIndex in case we are in tick. looping over the actions
if (pElement->actionIndex >= uIndex)//保证动作被移除时循环能被正确执行
{
pElement->actionIndex--;
}

if (pElement->actions->num == 0)//如果节点的没有动作列表
{
if (m_pCurrentTarget == pElement)//如果待移除的动作与当前正在执行的动作相等
{
m_bCurrentTargetSalvaged = true;//将该正在执行的动作标记为可回收
}
else
{//不相等
deleteHashElement(pElement);//直接从哈希表中移除掉
}
}
}

// pause / resume

void CCActionManager::pauseTarget(CCObject *pTarget)
{
tHashElement *pElement = NULL;
HASH_FIND_INT(m_pTargets, &pTarget, pElement);//在哈希表中查找该节点的哈希项
if (pElement)//找到,设置暂停
{
pElement->paused = true;
}
}

void CCActionManager::resumeTarget(CCObject *pTarget)
{
tHashElement *pElement = NULL;
HASH_FIND_INT(m_pTargets, &pTarget, pElement);//在哈希表中查找该节点的哈希项
if (pElement)//找到,恢复运行
{
pElement->paused = false;
}
}

CCSet* CCActionManager::pauseAllRunningActions()
{
CCSet *idsWithActions = new CCSet();//创建一个收集即将被设置为暂停的集合
idsWithActions->autorelease();//放到内存回收池

for (tHashElement *element=m_pTargets; element != NULL; element = (tHashElement *)element->hh.next) //遍历动作管理器
{
if (! element->paused) //如果不是暂停状态
{
element->paused = true;//设置暂停
idsWithActions->addObject(element->target);//加到集合中
}
}
//返回集合
return idsWithActions;
}

void CCActionManager::resumeTargets(cocos2d::CCSet *targetsToResume)
{
CCSetIterator iter;
for (iter = targetsToResume->begin(); iter != targetsToResume->end(); ++iter)//遍历集合中的动作
{
resumeTarget(*iter);//恢复动作
}
}

// run

void CCActionManager::addAction(CCAction *pAction, CCNode *pTarget, bool paused)
{
//参数检查
CCAssert(pAction != NULL, "");
CCAssert(pTarget != NULL, "");

tHashElement *pElement = NULL;
// we should convert it to CCObject*, because we save it as CCObject*
CCObject *tmp = pTarget;
HASH_FIND_INT(m_pTargets, &tmp, pElement);//在哈希表中查找该节点的哈希项
if (! pElement)//没找到
{
//创建一个新的哈希项
pElement = (tHashElement*)calloc(sizeof(*pElement), 1);
pElement->paused = paused;//设置暂停
pTarget->retain();//引用计数加1
pElement->target = pTarget;//设置目标节点
HASH_ADD_INT(m_pTargets, target, pElement);//将新创建的哈希项放到哈希表中
}
//为哈希项申请存放内存空间
actionAllocWithHashElement(pElement);

CCAssert(! ccArrayContainsObject(pElement->actions, pAction), "");
ccArrayAppendObject(pElement->actions, pAction);//加到动作管理器中
//初始化执行目标节点
pAction->startWithTarget(pTarget);
}

// remove

void CCActionManager::removeAllActions(void)
{
for (tHashElement *pElement = m_pTargets; pElement != NULL; )//遍历动作管理器中的所有动作
{
CCObject *pTarget = pElement->target;//执行动作的目标节点
pElement = (tHashElement*)pElement->hh.next;//下一个元素
removeAllActionsFromTarget(pTarget);//将该目标节点从动作管理器中移除掉
}
}

void CCActionManager::removeAllActionsFromTarget(CCObject *pTarget)
{
// explicit null handling
if (pTarget == NULL)//参数检查
{
return;
}

tHashElement *pElement = NULL;
HASH_FIND_INT(m_pTargets, &pTarget, pElement);//在哈希表中查找该节点的哈希项
if (pElement)//找到
{
if (ccArrayContainsObject(pElement->actions, pElement->currentAction) && (! pElement->currentActionSalvaged))//如果待移除的动作与当前正在执行的动作相等且没有被标记可回收,那么将其标记可回收
{
pElement->currentAction->retain();//引用计数加1,保证当前的动作能完整的被执行完
pElement->currentActionSalvaged = true;//标记为可回收
}

ccArrayRemoveAllObjects(pElement->actions);//将该动作从动作管理器中移除掉
if (m_pCurrentTarget == pElement)//如果待移除的动作与当前正在执行的动作相等
{
m_bCurrentTargetSalvaged = true;//将该正在执行的动作标记为可回收
}
else
{//不相等
deleteHashElement(pElement);//直接从哈希表中移除掉
}
}
else
{
// CCLOG("cocos2d: removeAllActionsFromTarget: Target not found");
}
}

void CCActionManager::removeAction(CCAction *pAction)
{
// explicit null handling
if (pAction == NULL)
{
return;
}

tHashElement *pElement = NULL;
CCObject *pTarget = pAction->getOriginalTarget();//获取原始的动作执行节点
HASH_FIND_INT(m_pTargets, &pTarget, pElement);//在哈希表中查找该节点的哈希项
if (pElement)//找到
{
unsigned int i = ccArrayGetIndexOfObject(pElement->actions, pAction);//获取该动作在动作管理中的索引值
if (UINT_MAX != i)//如果是有效的索引值
{
removeActionAtIndex(i, pElement);//将该动作从动作管理器中移除掉
}
}
else
{//没找到,输出没找到日志
CCLOG("cocos2d: removeAction: Target not found");
}
}

void CCActionManager::removeActionByTag(unsigned int tag, CCObject *pTarget)
{
//参数检查
CCAssert((int)tag != kCCActionTagInvalid, "");
CCAssert(pTarget != NULL, "");

tHashElement *pElement = NULL;
HASH_FIND_INT(m_pTargets, &pTarget, pElement);//在哈希表中查找该节点的哈希项

if (pElement)//找到
{
unsigned int limit = pElement->actions->num;
for (unsigned int i = 0; i < limit; ++i)
{
CCAction *pAction = (CCAction*)pElement->actions->arr[i];

if (pAction->getTag() == (int)tag && pAction->getOriginalTarget() == pTarget)//标记和目标都相等
{
removeActionAtIndex(i, pElement);//从动作管理器中移除该动作
break;
}
}
}
}

// get

CCAction* CCActionManager::getActionByTag(unsigned int tag, CCObject *pTarget)
{
CCAssert((int)tag != kCCActionTagInvalid, "");

tHashElement *pElement = NULL;
HASH_FIND_INT(m_pTargets, &pTarget, pElement);//在哈希表中查找该节点的哈希项

if (pElement)
{//找到
if (pElement->actions != NULL)
{
unsigned int limit = pElement->actions->num;
for (unsigned int i = 0; i < limit; ++i)
{
CCAction *pAction = (CCAction*)pElement->actions->arr[i];

if (pAction->getTag() == (int)tag)//找到标记位相等的动作
{
return pAction;//找到,则返回
}
}
}
CCLOG("cocos2d : getActionByTag(tag = %d): Action not found", tag);
}
else
{
// CCLOG("cocos2d : getActionByTag: Target not found");
}

return NULL;
}

unsigned int CCActionManager::numberOfRunningActionsInTarget(CCObject *pTarget)
{
tHashElement *pElement = NULL;
HASH_FIND_INT(m_pTargets, &pTarget, pElement);//在哈希表中查找该节点的哈希项
if (pElement)//找到,返回数量
{
return pElement->actions ? pElement->actions->num : 0;
}

return 0;
}

// main loop
void CCActionManager::update(float dt)
{
for (tHashElement *elt = m_pTargets; elt != NULL; )
{
m_pCurrentTarget = elt;
m_bCurrentTargetSalvaged = false;

if (! m_pCurrentTarget->paused)//如果已暂停,不执行
{
// The 'actions' CCMutableArray may change while inside this loop.
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;
}
既然动作类是被这个管理器所管理的那么它的实现肯定是相当重要。

cocos2d-x中的动作分析

(CCActionInterval的子类没截完)

从上图可以看出动作类CCAction有三个子类分别是:

1)有限时间的执行的动作(CCFiniteTimeAction)

2)跟随动作(CCFollow)

3)可变速度动作(CCSpeed)

而其中有限时间的执行的动作又有两个重要的子类,分别是:

1)瞬时动作(CCActionInstant):持续时间为0,

2)持续动作(CCActionInterval):拥有自己的持续时间

这两个类衍生很多通用的动作供开发者使用。


接下来是动作类的头文件:

enum {
//! Default tag
//动作的默认标记
kCCActionTagInvalid = -1,
};

/**
* @addtogroup actions
* @{
*/

/**
@brief Base class for CCAction objects.
*/
//所有动作的基类
class CC_DLL CCAction : public CCObject
{
public:
/**
* @js ctor
*/
//构造
CCAction(void);
/**
* @js NA
* @lua NA
*/
//析构
virtual ~CCAction(void);
/**
* @js NA
* @lua NA
*/
//描述该动作
const char* description();
/**
* @js NA
* @lua NA
*/
//拷贝一个动作
virtual CCObject* copyWithZone(CCZone *pZone);

//! return true if the action has finished
//返回动作已经执行完毕
virtual bool isDone(void);

//! called before the action start. It will also set the target.
//在动作开始前会设置一个执行目标
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);

/**
called once per frame. time a value between 0 and 1

For example:
- 0 means that the action just started
- 0.5 means that the action is in the middle
- 1 means that the action is over
*/
//动作的实现方法
virtual void update(float time);
//获取执行目标
inline CCNode* getTarget(void) { return m_pTarget; }
/** The action will modify the target properties. */
//设置执行目标
inline void setTarget(CCNode *pTarget) { m_pTarget = pTarget; }
//获取原始的执行目标
inline CCNode* getOriginalTarget(void) { return m_pOriginalTarget; }
/** Set the original target, since target can be nil.
Is the target that were used to run the action. Unless you are doing something complex, like CCActionManager, you should NOT call this method.
The target is 'assigned', it is not 'retained'.
@since v0.8.2
*/
//设置原始的执行目标
inline void setOriginalTarget(CCNode *pOriginalTarget) { m_pOriginalTarget = pOriginalTarget; }
//获取动作的标记
inline int getTag(void) { return m_nTag; }
//设置动作的标记
inline void setTag(int nTag) { m_nTag = nTag; }

public:
/** Create an action */
//创建一个动作
static CCAction* create();
protected:
//原始执行目标
CCNode *m_pOriginalTarget;
/** The "target".
The target will be set with the 'startWithTarget' method.
When the 'stop' method is called, target will be set to nil.
The target is 'assigned', it is not 'retained'.
*/
//执行目标
CCNode *m_pTarget;
/** The action tag. An identifier of the action */
//动作标记,默认为kCCActionTagInvalid(-1)
int m_nTag;
};

/**
@brief
Base class actions that do have a finite time duration.
Possible actions:
- An action with a duration of 0 seconds
- An action with a duration of 35.5 seconds

Infinite time actions are valid
*/
//有限时间型动作(包括瞬时和持续型的)
class CC_DLL CCFiniteTimeAction : public CCAction
{
public:
/**
* @js ctor
*/
//构造,内联实现,初始化执行时间为0,即瞬时执行完毕
CCFiniteTimeAction()
: m_fDuration(0)
{}
/**
* @js NA
* @lua NA
*/
//析构
virtual ~CCFiniteTimeAction(){}
//! get duration in seconds of the action
//获取执行动作的所需时间(单位:秒)
inline float getDuration(void) { return m_fDuration; }
//! set duration in seconds of the action
//设置执行动作的所需时间(单位:秒)
inline void setDuration(float duration) { m_fDuration = duration; }

/** returns a reversed action */
//返回一个反向动作
virtual CCFiniteTimeAction* reverse(void);
protected:
//! duration in seconds
//动作时长
float m_fDuration;
};

class CCActionInterval;
class CCRepeatForever;

/**
@brief Changes the speed of an action, making it take longer (speed>1)
or less (speed<1) time.
Useful to simulate 'slow motion' or 'fast forward' effect.
@warning This action can't be Sequenceable because it is not an CCIntervalAction
*/
//可变速度的动作
class CC_DLL CCSpeed : public CCAction
{
public:
/**
* @js ctor
*/
//够着,内联实现
CCSpeed()
: m_fSpeed(0.0)
, m_pInnerAction(NULL)
{}
/**
* @js NA
* @lua NA
*/
//析构
virtual ~CCSpeed(void);
//返回当前动作速度
inline float getSpeed(void) { return m_fSpeed; }
/** alter the speed of the inner function in runtime */
//设置当前动作速度
inline void setSpeed(float fSpeed) { m_fSpeed = fSpeed; }

/** initializes the action */
//初始动作
bool initWithAction(CCActionInterval *pAction, float fSpeed);
/**
* @js NA
* @lua NA
*/
//重载基类CCAtion的方法
virtual CCObject* copyWithZone(CCZone *pZone);
virtual void startWithTarget(CCNode* pTarget);
virtual void stop();
virtual void step(float dt);
virtual bool isDone(void);
//返回一个反向动作
virtual CCActionInterval* reverse(void);
//设置一个要控制的动画(内部动画)
void setInnerAction(CCActionInterval *pAction);
//返回一个要控制的动画(内部动画)
inline CCActionInterval* getInnerAction()
{
return m_pInnerAction;
}

public:
/** create the action */
//创建可变速度的动作
static CCSpeed* create(CCActionInterval* pAction, float fSpeed);
protected:
//执行速度
float m_fSpeed;
//内部动作
CCActionInterval *m_pInnerAction;
};

/**
@brief CCFollow is an action that "follows" a node.

Eg:
layer->runAction(CCFollow::actionWithTarget(hero));

Instead of using CCCamera as a "follower", use this action instead.
@since v0.99.2
*/
//跟随动作
class CC_DLL CCFollow : public CCAction
{
public:
/**
* @js ctor
*/
//构造,内联实现
CCFollow()
: m_pobFollowedNode(NULL)
, m_bBoundarySet(false)
, m_bBoundaryFullyCovered(false)
, m_fLeftBoundary(0.0)
, m_fRightBoundary(0.0)
, m_fTopBoundary(0.0)
, m_fBottomBoundary(0.0)
{}
/**
* @js NA
* @lua NA
*/
//析构
virtual ~CCFollow(void);
//获取是否有设置边界
inline bool isBoundarySet(void) { return m_bBoundarySet; }
/** alter behavior - turn on/off boundary */
//设置是否有边界
inline void setBoudarySet(bool bValue) { m_bBoundarySet = bValue; }

/** initializes the action with a set boundary */
//初始化执行目标
bool initWithTarget(CCNode *pFollowedNode, const CCRect& rect = CCRectZero);
/**
* @js NA
* @lua NA
*/
//重载基类CCAction的方法
virtual CCObject* copyWithZone(CCZone *pZone);
virtual void step(float dt);
virtual bool isDone(void);
virtual void stop(void);

public:
/** creates the action with a set boundary,
It will work with no boundary if @param rect is equal to CCRectZero.
*/
//创建一个跟随动作
static CCFollow* create(CCNode *pFollowedNode, const CCRect& rect = CCRectZero);
protected:
// node to follow
//被跟随的节点
CCNode *m_pobFollowedNode;

// whether camera should be limited to certain area
//是否现在摄像头的可见区域内
bool m_bBoundarySet;

// if screen size is bigger than the boundary - update not needed
//边界是否只是一个点
bool m_bBoundaryFullyCovered;

// fast access to the screen dimensions
//用于快速获取屏幕此存信息
//保存屏幕一半的大小
CCPoint m_obHalfScreenSize;
//保存屏幕全部的大小
CCPoint m_obFullScreenSize;

// world boundaries
//边界,左、右、上、下
float m_fLeftBoundary;
float m_fRightBoundary;
float m_fTopBoundary;
float m_fBottomBoundary;
};

实现:

CCAction::CCAction()
:m_pOriginalTarget(NULL)
,m_pTarget(NULL)
,m_nTag(kCCActionTagInvalid)
{
}

CCAction::~CCAction()
{
CCLOGINFO("cocos2d: deallocing");
}

CCAction* CCAction::create()
{
CCAction * pRet = new CCAction();//创建一个新动作实例
pRet->autorelease();//放入内存回收池
return pRet;
}

const char* CCAction::description()
{
return CCString::createWithFormat("<CCAction | Tag = %d>", m_nTag)->getCString();//返回绑定tag的描述
}

CCObject* CCAction::copyWithZone(CCZone *pZone)
{
CCZone *pNewZone = NULL;
CCAction *pRet = NULL;
if (pZone && pZone->m_pCopyObject)//如果传进来的参数的对象不为空
{
pRet = (CCAction*)(pZone->m_pCopyObject);//直接强转
}
else
{//传进来的参数的对象为空
pRet = new CCAction();//创建一个新的动作
pNewZone = new CCZone(pRet);//将该动作的指针让CCZone保存(这个指针所指向的内存空间在这个方法返回前被释放了,也就是这句话是没用的)
}
//copy member data
pRet->m_nTag = m_nTag;//设置动作的标记
CC_SAFE_DELETE(pNewZone);//释放pNewZone的资源
return pRet;
}

void CCAction::startWithTarget(CCNode *aTarget)
{
m_pOriginalTarget = m_pTarget = aTarget;//初始化原始执行目标和执行目标都为aTarget
}

void CCAction::stop()
{
m_pTarget = NULL;//将执行目标置空,动作结束后会调用它
}

bool CCAction::isDone()
{
return true;//动作执行完毕,直接返回true
}

void CCAction::step(float dt)
{
//空实现,全部交由子类实现
CC_UNUSED_PARAM(dt);
CCLOG("[Action step]. override me");
}

void CCAction::update(float time)
{
//空实现,全部交由子类实现
CC_UNUSED_PARAM(time);
CCLOG("[Action update]. override me");
}

//
// FiniteTimeAction
//

CCFiniteTimeAction *CCFiniteTimeAction::reverse()
{
//空实现,由持续性动作子类重载
CCLOG("cocos2d: FiniteTimeAction#reverse: Implement me");
return NULL;
}

//
// Speed
//
CCSpeed::~CCSpeed()
{
CC_SAFE_RELEASE(m_pInnerAction);//释放内部动作的资源
}

CCSpeed* CCSpeed::create(CCActionInterval* pAction, float fSpeed)
{
CCSpeed *pRet = new CCSpeed();//创建一个实例
if (pRet && pRet->initWithAction(pAction, fSpeed))//初始化
{
pRet->autorelease();//放入内存回收池
return pRet;
}
CC_SAFE_DELETE(pRet);//初始化失败,释放资源
return NULL;
}

bool CCSpeed::initWithAction(CCActionInterval *pAction, float fSpeed)
{
CCAssert(pAction != NULL, "");
pAction->retain();//引用计数加1,不然在下一帧动作执行时会出错
m_pInnerAction = pAction;//设置内部动作
m_fSpeed = fSpeed;//设置速度
return true;
}

CCObject *CCSpeed::copyWithZone(CCZone *pZone)
{
//拷贝,过程跟CCAction类似
CCZone* pNewZone = NULL;
CCSpeed* pRet = NULL;
if(pZone && pZone->m_pCopyObject) //in case of being called at sub class
{
pRet = (CCSpeed*)(pZone->m_pCopyObject);
}
else
{
pRet = new CCSpeed();
pZone = pNewZone = new CCZone(pRet);
}
CCAction::copyWithZone(pZone);

pRet->initWithAction( (CCActionInterval*)(m_pInnerAction->copy()->autorelease()) , m_fSpeed );

CC_SAFE_DELETE(pNewZone);
return pRet;
}

void CCSpeed::startWithTarget(CCNode* pTarget)
{
CCAction::startWithTarget(pTarget);//初始化执行目标
m_pInnerAction->startWithTarget(pTarget);//内部动作初始化执行目标
}

void CCSpeed::stop()
{
m_pInnerAction->stop();//停止内部动作
CCAction::stop();//调用父类的停止方法
}

void CCSpeed::step(float dt)
{
m_pInnerAction->step(dt * m_fSpeed);//内部动作进行计算执行进度
}

bool CCSpeed::isDone()
{
return m_pInnerAction->isDone();//返回内部动作是否执行完毕
}

CCActionInterval *CCSpeed::reverse()
{
return (CCActionInterval*)(CCSpeed::create(m_pInnerAction->reverse(), m_fSpeed));//执行内部动作的反向动作,并以相同的速度执行
}

void CCSpeed::setInnerAction(CCActionInterval *pAction)
{
if (m_pInnerAction != pAction)//如果传进来的内部动作与当前的内部动作不相等
{
CC_SAFE_RELEASE(m_pInnerAction);//释放掉上一个内部动作
m_pInnerAction = pAction;//绑定新的内部动作
CC_SAFE_RETAIN(m_pInnerAction);//新的内部动作的引用计数加1
}
}

//
// Follow
//
CCFollow::~CCFollow()
{
CC_SAFE_RELEASE(m_pobFollowedNode);
}

CCFollow* CCFollow::create(CCNode *pFollowedNode, const CCRect& rect/* = CCRectZero*/)
{
CCFollow *pRet = new CCFollow();//创建一个新的跟随动作
if (pRet && pRet->initWithTarget(pFollowedNode, rect))//初始化
{
pRet->autorelease();//放入内存回收池
return pRet;
}
CC_SAFE_DELETE(pRet);//初始化失败,释放资源
return NULL;
}

bool CCFollow::initWithTarget(CCNode *pFollowedNode, const CCRect& rect/* = CCRectZero*/)
{
CCAssert(pFollowedNode != NULL, "");

pFollowedNode->retain();//被跟随的节点引用计数加1
m_pobFollowedNode = pFollowedNode;//设置跟随动作
if (rect.equals(CCRectZero))//如果参数2无效,无边界
{
m_bBoundarySet = false;
}
else//参数2有效,有边界
{
m_bBoundarySet = true;
}

m_bBoundaryFullyCovered = false;

CCSize winSize = CCDirector::sharedDirector()->getWinSize();//窗口大小
m_obFullScreenSize = CCPointMake(winSize.width, winSize.height);//绑定全屏的大小
m_obHalfScreenSize = ccpMult(m_obFullScreenSize, 0.5f);//绑定全屏大小的一半

if (m_bBoundarySet)//如果有设置边界
{
m_fLeftBoundary = -((rect.origin.x+rect.size.width) - m_obFullScreenSize.x);//计算左边界
m_fRightBoundary = -rect.origin.x ;//计算右边界
m_fTopBoundary = -rect.origin.y;//计算顶部边界
m_fBottomBoundary = -((rect.origin.y+rect.size.height) - m_obFullScreenSize.y);//计算底部边界

//假设屏幕的大小是480*320,设置的边界矩形是(0,0,720,480),那么计算后的结果是:
//最左边是(0 + 720) - 480 = 240,也就是可以向右移动最多240个单位,因为是向右移,所以取负数,即最终的结果是-240,代表最多向右移动240个单位,其余三个边界类似

if(m_fRightBoundary < m_fLeftBoundary)//如果右边界大于左边界,即移动过头了,则将左右边界都设置为中间位置
{
// screen width is larger than world's boundary width
//set both in the middle of the world
m_fRightBoundary = m_fLeftBoundary = (m_fLeftBoundary + m_fRightBoundary) / 2;
}
if(m_fTopBoundary < m_fBottomBoundary)//如果顶部边界大于底部边界,即移动过头了,则将顶部底部边界都设置为中间位置
{
// screen width is larger than world's boundary width
//set both in the middle of the world
m_fTopBoundary = m_fBottomBoundary = (m_fTopBoundary + m_fBottomBoundary) / 2;
}
//如果左右边界位置相等且顶部底部边界位置相等,则认为边界矩形就只是一个点。
if( (m_fTopBoundary == m_fBottomBoundary) && (m_fLeftBoundary == m_fRightBoundary) )
{
m_bBoundaryFullyCovered = true;
}
}

return true;
}

CCObject *CCFollow::copyWithZone(CCZone *pZone)
{
//拷贝,过程跟CCAction类似
CCZone *pNewZone = NULL;
CCFollow *pRet = NULL;
if(pZone && pZone->m_pCopyObject) //in case of being called at sub class
{
pRet = (CCFollow*)(pZone->m_pCopyObject);
}
else
{
pRet = new CCFollow();
pZone = pNewZone = new CCZone(pRet);
}
CCAction::copyWithZone(pZone);
// copy member data
pRet->m_nTag = m_nTag;
CC_SAFE_DELETE(pNewZone);
return pRet;
}

void CCFollow::step(float dt)
{
CC_UNUSED_PARAM(dt);

if(m_bBoundarySet)//如果设置类边界限制
{
// whole map fits inside a single screen, no need to modify the position - unless map boundaries are increased
if(m_bBoundaryFullyCovered)//如果边界是一个点,则不需改变
return;
//计算相对于屏幕中间的点
CCPoint tempPos = ccpSub( m_obHalfScreenSize, m_pobFollowedNode->getPosition());
//更新目标的坐标
m_pTarget->setPosition(ccp(clampf(tempPos.x, m_fLeftBoundary, m_fRightBoundary),
clampf(tempPos.y, m_fBottomBoundary, m_fTopBoundary)));
}
else
{
//更新目标的坐标
m_pTarget->setPosition(ccpSub(m_obHalfScreenSize, m_pobFollowedNode->getPosition()));
}
}

bool CCFollow::isDone()
{
return ( !m_pobFollowedNode->isRunning() );//如果跟随节点不在运行中,返回true
}

void CCFollow::stop()
{
m_pTarget = NULL;//执行目标重置为空
CCAction::stop();//调用父类的停止动作方法
}
实现都较为简单。

那么接下来便分析几个常见的动作的具体实现。

每个cocos2d-x的开发者都很清楚,如果某个节点要执行某个动作都是调用节点的runAction方法来执行动作的。所以节点肯定是封装了常用的动作的操作,隐藏了动作类的真正细节,其思路跟定时器的思路一模一样:

首先在构造中每个节点都会从导演中获取动作管理器供自己管理动作。

CCNode::CCNode(void)...
{
// set default scheduler and actionManager
CCDirector *director = CCDirector::sharedDirector();
m_pActionManager = director->getActionManager();
m_pActionManager->retain();
...
}
然后再通过这个管理器间接的调用动作的各个方法:

void CCNode::setActionManager(CCActionManager* actionManager)//设置一个新的动作管理器
{
if( actionManager != m_pActionManager ) {//如果传进来新的动作管理器与当前的动作管理器不相等
this->stopAllActions();//先停止所有的动作调用
CC_SAFE_RETAIN(actionManager);//将新的管理器的引用计数加1,防止被回收池回收
CC_SAFE_RELEASE(m_pActionManager);//删掉上一个的动作管理器
m_pActionManager = actionManager;//设置新的动作管理器
}
}

CCActionManager* CCNode::getActionManager()
{
return m_pActionManager;//返回当前的动作管理器
}

CCAction * CCNode::runAction(CCAction* action)//运行动作
{
CCAssert( action != NULL, "Argument must be non-nil");
m_pActionManager->addAction(action, this, !m_bRunning);//将要运行的动作加到动作管理器中,动作管理器会在合适的时间调用它
return action;//返回运行的动作
}

void CCNode::stopAllActions()
{
m_pActionManager->removeAllActionsFromTarget(this);//移除掉该节点的所有动作
}

void CCNode::stopAction(CCAction* action)
{
m_pActionManager->removeAction(action);//从动作管理器中移除该动作
}

void CCNode::stopActionByTag(int tag)
{
CCAssert( tag != kCCActionTagInvalid, "Invalid tag");
m_pActionManager->removeActionByTag(tag, this);//根据标记位移除动作
}

CCAction * CCNode::getActionByTag(int tag)
{
CCAssert( tag != kCCActionTagInvalid, "Invalid tag");
return m_pActionManager->getActionByTag(tag, this);//根据标记位获取动作
}

unsigned int CCNode::numberOfRunningActions()
{
return m_pActionManager->numberOfRunningActionsInTarget(this);//获取当前节点的正在运行的动作的数量
}
可以看到调用runAction其实就是将其加入到动作管理器中,然后动作管理器会自动的在合适的时间去调用它。


下面再看两个拥有大量子类的动作的实现:

1)瞬时动作:

class CC_DLL CCActionInstant : public CCFiniteTimeAction //<NSCopying>
{
public:
/**
* @js ctor
*/
//构造
CCActionInstant();
/**
* @js NA
* @lua NA
*/
//析构
virtual ~CCActionInstant(){}
// CCAction methods
/**
* @js NA
* @lua NA
*/
//重载父类方法
virtual CCObject* copyWithZone(CCZone *pZone);
virtual bool isDone(void);
virtual void step(float dt);
virtual void update(float time);
//CCFiniteTimeAction method
virtual CCFiniteTimeAction * reverse(void);
};
CCActionInstant::CCActionInstant() {}CCObject * CCActionInstant::copyWithZone(CCZone *pZone) {	//拷贝操作    CCZone *pNewZone = NULL;    CCActionInstant *pRet = NULL;    if (pZone && pZone->m_pCopyObject) {        pRet = (CCActionInstant*) (pZone->m_pCopyObject);    } else {        pRet = new CCActionInstant();        pZone = pNewZone = new CCZone(pRet);    }    CCFiniteTimeAction::copyWithZone(pZone);    CC_SAFE_DELETE(pNewZone);    return pRet;}bool CCActionInstant::isDone() {	//是否执行完    return true;}void CCActionInstant::step(float dt) {    CC_UNUSED_PARAM(dt);    update(1);//执行调度}void CCActionInstant::update(float time) {	//空实现,子类实现    CC_UNUSED_PARAM(time);    // nothing}CCFiniteTimeAction * CCActionInstant::reverse() {	//返回一个复制动作    return (CCFiniteTimeAction*) (copy()->autorelease());}
以上是瞬时动作的基本实现,那么下面便用它的一个子类来分析如何去使用它。

class CC_DLL CCHide : public CCActionInstant//继承瞬时动作类CCActionInstant
{
public:
/**
* @js ctor
* @lua NA
*/
//构造
CCHide(){}
/**
* @js NA
* @lua NA
*/
//析构
virtual ~CCHide(){}
//super methods
/**
* @lua NA
*/
//重写父类方法
virtual void update(float time);
virtual CCFiniteTimeAction * reverse(void);
/**
* @js NA
* @lua NA
*/
virtual CCObject* copyWithZone(CCZone *pZone);
public:

/** Allocates and initializes the action */
static CCHide * create();//创建一个具体的瞬时动作类
};
CCHide * CCHide::create() {    CCHide *pRet = new CCHide();//创建一个即时隐藏节点的动作    if (pRet) {        pRet->autorelease();    }    return pRet;}void CCHide::update(float time) {    CC_UNUSED_PARAM(time);    m_pTarget->setVisible(false);//设置节点为不可见}CCFiniteTimeAction *CCHide::reverse() {	//它的反动作是显示动作,也就是CCShow,所以之创建了一个CCShow类后直接返回    return (CCFiniteTimeAction*) (CCShow::create());}CCObject* CCHide::copyWithZone(CCZone *pZone) {	//拷贝    CCZone *pNewZone = NULL;    CCHide *pRet = NULL;    if (pZone && pZone->m_pCopyObject) {        pRet = (CCHide*) (pZone->m_pCopyObject);    } else {        pRet = new CCHide();        pZone = pNewZone = new CCZone(pRet);    }    CCActionInstant::copyWithZone(pZone);    CC_SAFE_DELETE(pNewZone);    return pRet;}
可以看到如果要自定义一个瞬时动作,便可以按照以上的方式来实现,首先要继承瞬时动作类,然后从写拷贝方法,使拷贝出来的类确定是自定义的类,然后再update中实现自定义动作真正要执行的逻辑,如果有必要,也可以重写它的反向动作的实现。


2)持续动作:

class CC_DLL CCActionInterval : public CCFiniteTimeAction
{
public:
/** how many seconds had elapsed since the actions started to run. */
//返回记录了已执行的时间
inline float getElapsed(void) { return m_elapsed; }

/** initializes the action */
//初始化动作
bool initWithDuration(float d);

/** returns true if the action has finished */
//返回是否已执行完毕
virtual bool isDone(void);
/**
* @js NA
* @lua NA
*/
//重写父类方法
virtual CCObject* copyWithZone(CCZone* pZone);
virtual void step(float dt);
virtual void startWithTarget(CCNode *pTarget);
/** returns a reversed action */
virtual CCActionInterval* reverse(void);

public:

/** creates the action */
static CCActionInterval* create(float d);//创建持续动作

public:
//extension in CCGridAction
//与表格特效相关
void setAmplitudeRate(float amp);
float getAmplitudeRate(void);

protected:
float m_elapsed;//记录已累计的执行时间
bool m_bFirstTick;//是否是第一次运算
};
CCActionInterval* CCActionInterval::create(float d){    CCActionInterval *pAction = new CCActionInterval();//创建新的持续动作类    pAction->initWithDuration(d);//初始化    pAction->autorelease();//放入内存回收池    return pAction;}bool CCActionInterval::initWithDuration(float d){    m_fDuration = d;//记录时长    // prevent division by 0    // This comparison could be in step:, but it might decrease the performance    // by 3% in heavy based action games.    if (m_fDuration == 0)//如果时长为0,那么为了防止计算时除数为0,将时长设置一个值    {        m_fDuration = FLT_EPSILON;    }    m_elapsed = 0;    m_bFirstTick = true;    return true;}CCObject* CCActionInterval::copyWithZone(CCZone *pZone){	//拷贝操作    CCZone* pNewZone = NULL;    CCActionInterval* pCopy = NULL;    if(pZone && pZone->m_pCopyObject)     {        //in case of being called at sub class        pCopy = (CCActionInterval*)(pZone->m_pCopyObject);    }    else    {        pCopy = new CCActionInterval();        pZone = pNewZone = new CCZone(pCopy);    }        CCFiniteTimeAction::copyWithZone(pZone);    CC_SAFE_DELETE(pNewZone);    pCopy->initWithDuration(m_fDuration);    return pCopy;}bool CCActionInterval::isDone(void){    return m_elapsed >= m_fDuration;//如果以累积的时间长度大于执行时长,则返回true}void CCActionInterval::step(float dt){    if (m_bFirstTick)//如果是第一次执行运算    {        m_bFirstTick = false;//非第一次运算        m_elapsed = 0;//将累计时间置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                          )                      )                 );}void CCActionInterval::setAmplitudeRate(float amp){    CC_UNUSED_PARAM(amp);    // Abstract class needs implementation    CCAssert(0, "");}float CCActionInterval::getAmplitudeRate(void){    // Abstract class needs implementation    CCAssert(0, "");    return 0;}void CCActionInterval::startWithTarget(CCNode *pTarget){    CCFiniteTimeAction::startWithTarget(pTarget);//设置执行动作的目标节点    m_elapsed = 0.0f;    m_bFirstTick = true;}CCActionInterval* CCActionInterval::reverse(void){	//空实现,子类实现(有的子类有实现,有的没有,一般是相对型的动作有实现)    CCAssert(false, "CCIntervalAction: reverse not implemented.");    return NULL;}
以上是持续动作的基本实现,那么下面便用它的两个子类来分析如何去使用它。

class CC_DLL CCRotateBy : public CCActionInterval{public:    /** creates the action */    static CCRotateBy* create(float fDuration, float fDeltaAngle);//创建旋转动作    /** initializes the action */    bool initWithDuration(float fDuration, float fDeltaAngle);//初始化        static CCRotateBy* create(float fDuration, float fDeltaAngleX, float fDeltaAngleY);//创建旋转动作    bool initWithDuration(float fDuration, float fDeltaAngleX, float fDeltaAngleY);//初始化    /**     *  @js NA     *  @lua NA     */    //重写父类构造方法    virtual CCObject* copyWithZone(CCZone* pZone);    virtual void startWithTarget(CCNode *pTarget);    virtual void update(float time);    virtual CCActionInterval* reverse(void);    protected:    float m_fAngleX;//x轴的旋转角度    float m_fStartAngleX;//初始的x轴角度    float m_fAngleY;//y轴的旋转角度    float m_fStartAngleY;//初始的y轴角度};
CCRotateBy* CCRotateBy::create(float fDuration, float fDeltaAngle){    CCRotateBy *pRotateBy = new CCRotateBy();//创建一个新的动作    pRotateBy->initWithDuration(fDuration, fDeltaAngle);//初始化    pRotateBy->autorelease();//放入内存回收池    return pRotateBy;}bool CCRotateBy::initWithDuration(float fDuration, float fDeltaAngle){    if (CCActionInterval::initWithDuration(fDuration))//调用父类的初始化时长    {        m_fAngleX = m_fAngleY = fDeltaAngle;//初始化要旋转的角度        return true;    }    return false;}CCRotateBy* CCRotateBy::create(float fDuration, float fDeltaAngleX, float fDeltaAngleY){    CCRotateBy *pRotateBy = new CCRotateBy();//创建一个新的动作    pRotateBy->initWithDuration(fDuration, fDeltaAngleX, fDeltaAngleY);//初始化    pRotateBy->autorelease();//放入内存回收池        return pRotateBy;}bool CCRotateBy::initWithDuration(float fDuration, float fDeltaAngleX, float fDeltaAngleY){    if (CCActionInterval::initWithDuration(fDuration))//调用父类的初始化时长    {        m_fAngleX = fDeltaAngleX;//初始化要旋转的x轴角度        m_fAngleY = fDeltaAngleY;//初始化要旋转的y轴角度        return true;//返回执行成功    }    //返回执行失败    return false;}CCObject* CCRotateBy::copyWithZone(CCZone *pZone){	//拷贝操作    CCZone* pNewZone = NULL;    CCRotateBy* pCopy = NULL;    if(pZone && pZone->m_pCopyObject)     {        //in case of being called at sub class        pCopy = (CCRotateBy*)(pZone->m_pCopyObject);    }    else    {        pCopy = new CCRotateBy();        pZone = pNewZone = new CCZone(pCopy);    }    CCActionInterval::copyWithZone(pZone);    pCopy->initWithDuration(m_fDuration, m_fAngleX, m_fAngleY);    CC_SAFE_DELETE(pNewZone);    return pCopy;}void CCRotateBy::startWithTarget(CCNode *pTarget){    CCActionInterval::startWithTarget(pTarget);//设置目标节点    m_fStartAngleX = pTarget->getRotationX();//初始化节点的初始x轴角度    m_fStartAngleY = pTarget->getRotationY();//初始化节点的初始y轴角度}void CCRotateBy::update(float time){    // XXX: shall I add % 360    if (m_pTarget)//如果存在执行节点    {        m_pTarget->setRotationX(m_fStartAngleX + m_fAngleX * time);//设置新的x轴角度        m_pTarget->setRotationY(m_fStartAngleY + m_fAngleY * time);//设置新的y轴角度    }}CCActionInterval* CCRotateBy::reverse(void){    return CCRotateBy::create(m_fDuration, -m_fAngleX, -m_fAngleY);//创建反向动作}
实现一个持续动作的过程跟瞬时动作的过程类似,只不过这里在初始化的时候要调用父类的initWithDuration方法来设置调用时长,这里也调用父类的startWithTarget来绑定待执行的目标节点。

总结:由于动作管理器是受控于定时器,所以当控制定时器和动过的暂停和恢复工作时是统一的一个操作,没有任何的同步问题。