1.
动作类的结构
打开CCAction.h文档,按照注释我们可以找到下面几个比较重要的方法。
//! 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);
isDone判断动作是否完成,startWithTarget设定执行该动作的对象,stop停止当前动作,step累积时间并在指定周期更新动作,update按照百分比更新动作。
接着我们看看它的一个使用最多的一个子类CCFiniteTimeAction,发现它新增了一个用于保存该动作完成时间的变量:ccTimem_fDuration。按照猜想,应该这个变量是用于区分实现瞬时动作和延时动作的。
进一步去观察源代码,发现有一个CCActionManager的类,顾名思义就是动作管理类。通过查看资料发现,这个类主要功能为把动作对象添加到队列,然后根据一定周期更新动作列表里的动作,根据指示标志来停止动作或者动作完成之后把动作从队列中删除。
搜索代码中CCActionManager的初始化,我们发现初始化代码在CCDirector::init()中,代码如下:
M_pActionManager = new CCActionManager();
M_pScheduler->scheduleUpdateForTarget(m_pActionManager, kCCPrioritySystem, false)
可以知道CCActionManager在创建对象的时候即注册了一个定期更新的服务。也就是说动作调度其实也就是借助CCScheduler的控制,这应该也简化了动作暂停、恢复等功能的实现,只要控制定时器即可。根据我们前面一篇文章对于定时机制的分析,我们可以知道这是一种update类型的定时器,也就是只要重载update函数,并初始化的时候调用scheduleUpdate即可。接着我们去查看CCActionManager::update(float dt)函数。
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;
}
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 ) ) ); }
这个是延时动作类的step函数,可以看到一个关键的字眼就是update,这个方法的time参数表示逝去的时间与动作完成需要的时间的比值,取值为0-1之间的百分比。究竟这个update实现了什么,我们可以进一步找个具体动作来看看。
// // RotateTo // CCRotateTo* CCRotateTo::create(float fDuration, float fDeltaAngle) { CCRotateTo* pRotateTo = new CCRotateTo(); pRotateTo->initWithDuration(fDuration, fDeltaAngle); pRotateTo->autorelease(); return pRotateTo; } bool CCRotateTo::initWithDuration(float fDuration, float fDeltaAngle) { if (CCActionInterval::initWithDuration(fDuration)) { m_fDstAngleX = m_fDstAngleY = fDeltaAngle; return true; } return false; } CCRotateTo* CCRotateTo::create(float fDuration, float fDeltaAngleX, float fDeltaAngleY) { CCRotateTo* pRotateTo = new CCRotateTo(); pRotateTo->initWithDuration(fDuration, fDeltaAngleX, fDeltaAngleY); pRotateTo->autorelease(); return pRotateTo; } bool CCRotateTo::initWithDuration(float fDuration, float fDeltaAngleX, float fDeltaAngleY) { if (CCActionInterval::initWithDuration(fDuration)) { m_fDstAngleX = fDeltaAngleX; m_fDstAngleY = fDeltaAngleY; return true; } return false; } CCObject* CCRotateTo::copyWithZone(CCZone *pZone) { CCZone* pNewZone = NULL; CCRotateTo* pCopy = NULL; if(pZone && pZone->m_pCopyObject) { //in case of being called at sub class pCopy = (CCRotateTo*)(pZone->m_pCopyObject); } else { pCopy = new CCRotateTo(); pZone = pNewZone = new CCZone(pCopy); } CCActionInterval::copyWithZone(pZone); pCopy->initWithDuration(m_fDuration, m_fDstAngleX, m_fDstAngleY); //Action *copy = [[[self class] allocWithZone: zone] initWithDuration:[self duration] angle: angle]; CC_SAFE_DELETE(pNewZone); return pCopy; } void CCRotateTo::startWithTarget(CCNode *pTarget) { CCActionInterval::startWithTarget(pTarget); // Calculate X m_fStartAngleX = pTarget->getRotationX(); if (m_fStartAngleX > 0) { m_fStartAngleX = fmodf(m_fStartAngleX, 360.0f); } else { m_fStartAngleX = fmodf(m_fStartAngleX, -360.0f); } m_fDiffAngleX = m_fDstAngleX - m_fStartAngleX; if (m_fDiffAngleX > 180) { m_fDiffAngleX -= 360; } if (m_fDiffAngleX < -180) { m_fDiffAngleX += 360; } //Calculate Y: It's duplicated from calculating X since the rotation wrap should be the same m_fStartAngleY = m_pTarget->getRotationY(); if (m_fStartAngleY > 0) { m_fStartAngleY = fmodf(m_fStartAngleY, 360.0f); } else { m_fStartAngleY = fmodf(m_fStartAngleY, -360.0f); } m_fDiffAngleY = m_fDstAngleY - m_fStartAngleY; if (m_fDiffAngleY > 180) { m_fDiffAngleY -= 360; } if (m_fDiffAngleY < -180) { m_fDiffAngleY += 360; } } void CCRotateTo::update(float time) { if (m_pTarget) { m_pTarget->setRotationX(m_fStartAngleX + m_fDiffAngleX * time); m_pTarget->setRotationY(m_fStartAngleY + m_fDiffAngleY * time); } }
这个是CCActionInterval.cpp中提取的一个RotateTo动作的实现,可以看到,它主要是重写CCActionInterval中的一些基本方法,而实现动作变化的关键是update函数,可以看到update通过设置当前节点的属性值Rotation就完成了该工作的效果。
根据前面的简单分析,也就是动作的处理过程是先受到系统定时器的调度,当周期到达的时候,调用CCActionManager中的update来遍历动作列表,并进行处理。处理的一个重点是调用step函数来记录时长,并再次调用所实现动作的update函数来进行动作的更新。假如我们需要自定义自己的动作的时候,我们可以通过继承CCAction的子类,然后关键的逻辑控制在于重载step和update,借助这两个函数来实现动作的触发和效果。
如有错误和不足,请及时支出,谢谢指导!