稍微了解cocos2d-x的朋友应该都知道cocos2d-x里面的动作都是通过CCAction的各种派生类来实现的。
比如我要将一个Sprite在1秒内从(0,0)移动到(400,400)
pSprite->setPosition(ccp(0,0));
CCMoveTo* moveTo = CCMoveTo::create(1.0f,,ccp(400,400));
pSprite->runAction(moveTo);
那么,让我们来看看Action在cocos2d-x到底是如何实现的。
首先必须要了解的几个类
1.CCAction和CCActionInterval
CCAction是所有动作的基类,从代码里可以看出它本身并不执行动作
void CCAction::update(float time)
{
CC_UNUSED_PARAM(time);
CCLOG("[Action update]. override me");
}
他的作用只是提供一部分接口,如stop,update等等,其本身的实际作用并不是太大
CCActionInterval反而相对重要些,这个是大部分常用动作的基类,如常用的CCMoveTo,CCRotateBy等都是继承自CCActionInterval,CCActionInterval还有很重要的一点就是他实现了step方法,这个方法是大部分其派生类更新动作的基础(后文会有讨论)
2.CCActionManager
从名字可以看出,这个类负责管理着所有的动作,
前面代码里的pSprite->runAction(moveTo);其实就是将moveTo这个Action和pSprite这个Node加入到了CCActionManager内部维护的m_pTargets的这个hash表中
看看代码:
CCAction * CCNode::runAction(CCAction* action)
{
CCAssert( action != NULL, "Argument must be non-nil");
m_pActionManager->addAction(action, this, !m_bIsRunning);
return action;
}
看看,内部是否是调用CCActionManager的addAction
接着我们再看看addAction的实现
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();
pElement->target = pTarget;
HASH_ADD_INT(m_pTargets, target, pElement);
}
actionAllocWithHashElement(pElement);
CCAssert(! ccArrayContainsObject(pElement->actions, pAction), "");
ccArrayAppendObject(pElement->actions, pAction);
pAction->startWithTarget(pTarget);
}
略去见面的Assert检查和后面的初始化设定不看,上面代码的大概意思就是将pAction(上文的moveTo),pTarget(上文的pSprite)加入到内部维护的m_Targets中,至于m_Targets到底是个什么东西我们可以不必深究,就理解为一个数组或者hash表就够了
贴出这个结构的源码,有兴趣的朋友可以看看
typedef struct _hashElement
{
struct _ccArray *actions;
CCObject *target;
unsigned int actionIndex;
CCAction *currentAction;
bool currentActionSalvaged;
bool paused;
UT_hash_handle hh;
} tHashElement;
这里要注意的一点,一个target是可以附带多个Action的,从上面actions的类型是一个数组我们可以加以确认。
OK,一切都设置妥当,我们开始讲解最核心的update方法
首先要说明一点,这个CCActionManager的update是在CCDisplayLinkDirector的mainLoop->drawScene里调用m_pScheduler->update(m_fDeltaTime);中被调用的
m_pScheduler是CCScheduler类型,他负责系统全部子系统的更新,比如Touch,Action,脚本,定时器等等都在这里update,Action是在优先级 < 0的那个list里面的
好了,继续讨论update,还是先看看源码
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;
}
其实update的代码很简单,就是遍历m_pTargets数组(或者链表,hash,随你高兴,不过他实际是hash),然后逐个更新每个tHashElement 里面的动作,当然他会有一些判断,比如动作是否暂停,是否结束之类的,并做相应处理,不如终止了自然要remove掉。如果一切正常,那就调用动作的step方法(这里必须知道多态的概念哦,因为step和后面的update方法都是virtual的)。
前面讲过CCActionInterval有实现step方法,好的,现在我们再来看看他做了什么事情
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
)
)
);
}
简而言之,就是根据传入的时间间隔(dt),增加m_elapsed(目前动作的执行到的位置),并将其处理在一个合法范围内(通过MAX,MIN宏),然后传给update方法,最后在update方法里面会进行实际的动作更新,接下来,我们以CCMoveTo为例,看看他的update是如何实现的
void CCMoveTo::update(float time)
{
if (m_pTarget)
{
m_pTarget->setPosition(ccp(m_startPosition.x + m_delta.x * time,
m_startPosition.y + m_delta.y * time));
}
}
看到了吧,他在这里就会根据他目前的elapse设置他所负责的Node的位置了。
大家有兴趣也可以看看其他动作,如Rotate,Jump之类,看看他们是如何来设置位置的。
OK,到此结束
由于本人才疏学浅,以上内容如有错误,欢迎大家多多斧正