cocos2d-x 源码剖析(8)

写到第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的接口,你如果像能正常的使用的话,还是要做不少工作的。


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: cocos2d-x是一款流行的开游戏引擎,它可以用作开发本地移动游戏和桌面游戏。利用cocos2d-x游戏码,开发人员可以快速构建流畅、高效、具有吸引力的游戏。该引擎使用C ++语言和脚本语言Lua来实现游戏开发,用户可以根据自己的需要进行选择。 cocos2d-x游戏码包含许多强大的功能和工具,例如精灵、动画、场景管理、碰撞检测和音频控制等。通过这些功能,开发人员可以方便地创建各种类型的游戏,例如动作游戏、射击游戏、冒险游戏和益智游戏等。 此外,cocos2d-x游戏码还具有高度定制化的特性,允许开发人员根据他们的需求自定义游戏元素。这种定制化可以通过改进游戏画面,添加新的模式和关卡,甚至整个新的游戏玩法来完成。 总之,cocos2d-x游戏码是一款功能强大、易于使用和高度定制的游戏引擎,它可以帮助开发人员快速开发各种类型的游戏。无论你是专业开发人员还是无经验的游戏制作者,cocos2d-x都是一个极好的选择。 ### 回答2: Cocos2d-x游戏码是使用Cocos2d-x引擎进行开发的游戏程序代码。Cocos2d-x引擎是一个开的跨平台游戏引擎,可以支持iOS、Android、Windows Phone、Windows、MacOS和Linux等多个平台,并且提供了丰富的游戏开发API和工具集。Cocos2d-x游戏码包含了游戏的核心逻辑、UI设计、动画效果、音频效果等各方面的代码,可以作为开发者学习和借鉴的重要资料。 由于Cocos2d-x引擎支持多种编程语言(如C++、Lua等),因此Cocos2d-x游戏码也会有对应的编程语言版本。开发者可以通过阅读Cocos2d-x游戏码,了解游戏开发过程中的技术细节,学习如何使用Cocos2d-x引擎进行游戏开发。同时,开发者也可以通过对Cocos2d-x游戏码进行修改和优化,来满足游戏的特定需求,提升游戏的性能和用户体验。 总之,Cocos2d-x游戏码是游戏开发者必备的重要资,可以帮助开发者更好地了解游戏开发技术,提升游戏开发水平。 ### 回答3: cocos2d-x是一款优秀的游戏开发引擎,其码包含了许多功能强大的游戏开发工具和API。使用cocos2d-x可以帮助开发者更快速更高效地开发出优秀的游戏作品。 cocos2d-x的游戏码非常丰富,其包含了许多不同类型的游戏示例和模板,如平面射击、塔防、解谜等,这些示例可以帮助开发者更好地了解cocos2d-x的使用方法和原理。 cocos2d-x码还包含了许多强大的API和工具,例如场景管理、动画控制、音频引擎等,这些工具和API能够帮助开发者更好地完成游戏的开发和调试。 此外,cocos2d-x码还提供了完整的游戏开发框架,包括资管理、内存管理、事件机制等,这些框架可以帮助开发者更好地组织代码和提高代码的可维护性。 总的来说,cocos2d-x游戏码提供了丰富的工具和API,可以帮助开发者更高效地进行游戏开发,大大降低了开发难度和成本,是一款非常适合游戏开发者使用的引擎。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值