写到第7节的时候,突然觉得cocos2d-x还没有我想的那么大啊,或许在50节以内就要了结了。这节继续看看CCNode这个节点,主要部分是action。虽然CCNode有不少的action相关的函数,起作用的实际上是Actionmanager。这节虽说是从CCNode开始,但是真正的内容在ActionManager中:
void CCNode::setActionManager(CCActionManager* actionManager)
{
if( actionManager != m_pActionManager ) {
this->stopAllActions();
CC_SAFE_RETAIN(actionManager);
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);
}
这种代理方式简化了CCNode本身的逻辑,很值得学习。在设计代码的时候,往往不自觉的有这种意识,但是如果能清晰的认识到这样做的优点的话,就能设计出更好代码了。
我们深入到ActionManager的内部,就会发现它其实被当成是一个单件,意外的是他没有采用之前的那种单件模式。而且原则上没有保证它必须是一个单件不可。在CCDirector的init函数中,它被new了出来。在CCNode函数中,他被赋值给了m_pActionManager,由于保存了一份本地引用,所以调用了retain一次,在析构的时候release了一次。
CCDirector*director=CCDirector::sharedDirector();
m_pActionManager=director->getActionManager();
m_pActionManager->retain();
一份本地引用不会增加多少逻辑,但是能减少一次函数调用,对于频繁使用的对象来说是值得的。而且这个ActionManager的管理方式十分有意思。在CCdirector中它被new出来之后就立马添加到了m_pScheduler中:
// scheduler
m_pScheduler = new CCScheduler();
// action manager
m_pActionManager = new CCActionManager();
m_pScheduler->scheduleUpdateForTarget(
m_pActionManager, kCCPrioritySystem, false);
这会导致每一帧它的update都会被调用到:
// main loop
voidCCActionManager::update(floatdt)
{
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();
}elseif(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;
}
所有被管理的action都保存在m_pTargets中,他的声明如下:
struct _hashElement *m_pTargets;
typedefstruct_hashElement
{
struct_ccArray *actions;
CCObject *target;
unsignedint actionIndex;
CCAction *currentAction;
bool currentActionSalvaged;
bool paused;
UT_hash_handle hh;
}tHashElement;
这是一个相对无脑的命名。可以看见ActionManager是以target为主导,来管理action的。关于缩进我就不再说什么了。这个结构很有意思,它其实是一个哈希链表。其内部细节我就不多做介绍了,注意这个结构的操作方式即可。
首先开始遍历这个链表,判断target是否暂停,也就是下面这两个函数所做的影响:
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;
}
}
在每一个target当中,遍历它的action,并且调用它的step函数。我们注意到,在次之前有一句代码比较可疑:
m_pCurrentTarget->currentActionSalvaged=false;
我们稍微想一想这句代码是做什么用的。注意进行step之后立马就进行了判断。那么能对这个值做出改变的就只有step了。而且搜索整个源码,能在外部改变这个值的只有removeAction接口,举其中一个作为例子:
void CCActionManager::removeActionAtIndex(unsigned int uIndex,
tHashElement *pElement)
{
CCAction *pAction = (CCAction*)pElement->actions->arr[uIndex];
if (pAction == pElement->currentAction &&
(! pElement->currentActionSalvaged))
{
pElement->currentAction->retain();
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);
}
}
}
这个函数虽然不是外部接口,却是核心实现。注意如果是当前action在step中被移去自己的话,那么就会先retain一下,然后做好并将它移去,因为已经retain了一次,在从容器中删除的时候不会析构。同理,如果删除这个action会导致当前的节点被删除,那么也将这个节点标记起来。如果不是当前的节点,那就立马删了。注意这个操作是发生在链表遍历之中的,看看遍历的实现就知道,移除其他节点是安全的。
由于step会导致2个意外逻辑,接下来的代码就好理解了。如果当前的action被标记了,就release一次。因外被标记那时就已经从容器里移去了,这里要做的就是让他析构而已。如果这个action完成了。那么就用removeAction移去它。从上面的例子可以看到,它执行的是:
ccArrayRemoveObjectAtIndex(pElement->actions,uIndex,true);
之所以能在一个循环里面删除数据,是因为随后又判断了一下,将索引减了1.
// update actionIndex in case we are in tick. looping over the actions
if(pElement->actionIndex>=uIndex)
{
pElement->actionIndex--;
}
还一个意外逻辑也就不难理解了,如果标记了当前的节点,并且这时候action确实为0那就删除这个节点。之所以要再判断一次数目,是因为step还可能添加新的action进来。
这份代码的实现与之其他代码相比要差很多,这恐怕也是导致Action经常出bug的根源。对于ActionManager的其他函数,我想在了解了update和remove之后,其它便不难掌握了。最后还要强调一点,虽然CCNode开出了setActionManager的接口,你如果像能正常的使用的话,还是要做不少工作的。