上一节讲了ActionManager,这节就顺势讲讲Action吧。下面是Action的继承图,这个结构看起来有点不对称,我也想不透作者是基于这种理由这样设计的,或许阅读晚源码之后会有所了解吧:
我们先从最简单的CCMove开始讲起。可以知道,CCMove继承至ActionInterval。ActionInterval有个特点,需要在一段时间内不断的调用step函数。与之对应的是ActionInstant,它不需要反复调用,一次就能完成。这两种action又都继承至FiniteTimeAction,也就是有限时间的Action。而之对应,还有Follow和Speed两个特殊的Action。好吧,来看看CCMove的创建过程:
CCMoveBy* CCMoveBy::create(float duration,
const CCPoint& deltaPosition)
{
CCMoveBy *pRet = new CCMoveBy();
pRet->initWithDuration(duration, deltaPosition);
pRet->autorelease();
return pRet;
}
bool CCMoveBy::initWithDuration(float duration,
const CCPoint& deltaPosition)
{
if (CCActionInterval::initWithDuration(duration))
{
m_positionDelta = deltaPosition;
return true;
}
return false;
}
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)
{
m_fDuration = FLT_EPSILON;
}
m_elapsed = 0;
m_bFirstTick = true;
return true;
}
对于所有的CCActionInterval,他都需要一个持续时间来表述何时结束。对于CCMoveBy来讲init函数中m_positionDelta记录下了需要移动的距离。前面我们讲过如果Action创建出来,被CCNode调用runAction之后,它就被添加到ActonManager中去了。
voidCCActionManager::addAction(CCAction*pAction,
CCNode*pTarget,boolpaused)
{
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);
}
(cocos2d-x的缩进已经到了丧心病狂的地步,在一句之中同时用tab和space,又涨见识啦),注意到action本身是没有将target的引用计数加1的,将引用计数加1的只是ActionManager而已,而且只在第一次添加时加1。发现这点我突然觉得不应该再爱了。之后调用startWithTarget。
void CCMoveBy::startWithTarget(CCNode *pTarget)
{
CCActionInterval::startWithTarget(pTarget);
m_previousPosition = m_startPosition = pTarget->getPosition();
}
这个函数只是在做开始action的准备工作,如果在Node本身是活动的,这个action在添加到Manager的时候就在调用step了。叫Perpare或许更合适呢,因为它完全没有开始运行的逻辑。
有点意外的是CCMoveBy没有重载step函数,但却实现了update函数。
voidCCMoveBy::update(floatt)
{
if(m_pTarget)
{
#if CC_ENABLE_STACKABLE_ACTIONS
CCPointcurrentPos=m_pTarget->getPosition();
CCPointdiff=ccpSub(currentPos,m_previousPosition);
m_startPosition=ccpAdd(m_startPosition,diff);
CCPointnewPos= ccpAdd(m_startPosition,
ccpMult(m_positionDelta,t));
m_pTarget->setPosition(newPos);
m_previousPosition=newPos;
#else
m_pTarget->setPosition(ccpAdd(m_startPosition,
ccpMult(m_positionDelta,t)));
#endif // CC_ENABLE_STACKABLE_ACTIONS
}
}
我们知道,这里确实是做实际逻辑的地方,而且根据update的注释,那个float t是0~1.0范围的。cocos2d-x给出了2种实现,第一种是默认的,它的好处是能同时调用多个位置相关的Action,效果累加。而第二种就是直接覆盖了,位置很可能是最后一个Action的效果。这个update在step中被调用:
void CCActionInterval::step(float dt)
{
if (m_bFirstTick)
{
m_bFirstTick = false;
m_elapsed = 0;
}
else
{
m_elapsed += dt;
}
// needed for rewind. elapsed could be negative
this->update(MAX (0,
MIN(1, m_elapsed /
MAX(m_fDuration, FLT_EPSILON)
)
)
);
}
那个update中的0~1.0的范围便是如此而来。而且这个代码造型的确略显犀利了些。至于判断Action是否完成则十分简单:
boolCCActionInterval::isDone(void)
{
returnm_elapsed>=m_fDuration;
}
根据这个例子,大家应该知道CCActionInterval的运作方式了。如果你要实现一个自己的CCActionInterval(哪怕一个最简单的),需要重载update,startWithTarget,create,initWithDuration,copyWithZone。起码我认为后3个函数是值得改进的。实作update的时候千万别忘了范围是0~1.0之间。