好记性不如铅笔头

C && C++, cocos2dx, 编程

cocos2dx学习笔记:CCNode回调流程

最近项目中用到了cocos2dx来开发一个基于陀螺仪的demo,忙了半个星期,一边学习一边开发,总算搞了出来。这里备份下开发中学习到的一些cocos2dx知识。

CONTENTS

CCNode的回调流程

在使用CCLayer,CCScene的时候往往想在layer或者场景切换的时候处理一些资源类的东西,于是就在CCNode里发现了一些回调函数,但是这些回调函数如何调用,作者在网上也没有搜索,自己翻了下源码,发现比较简单。在这里备份下。

CCNode的几个回调函数:

class CC_DLL CCNode : public CCObject
{
public:
	。。。。。
	。。。。。
    virtual bool init();
    
    static CCNode * create(void);

    /** 
     * Event callback that is invoked every time when CCNode enters the 'stage'.
     * If the CCNode enters the 'stage' with a transition, this event is called when the transition starts.
     * During onEnter you can't access a "sister/brother" node.
     * If you override onEnter, you shall call its parent's one, e.g., CCNode::onEnter().
     * @js NA
     * @lua NA
     */
    virtual void onEnter();

    /** Event callback that is invoked when the CCNode enters in the 'stage'.
     * If the CCNode enters the 'stage' with a transition, this event is called when the transition finishes.
     * If you override onEnterTransitionDidFinish, you shall call its parent's one, e.g. CCNode::onEnterTransitionDidFinish()
     * @js NA
     * @lua NA
     */
    virtual void onEnterTransitionDidFinish();

    /** 
     * Event callback that is invoked every time the CCNode leaves the 'stage'.
     * If the CCNode leaves the 'stage' with a transition, this event is called when the transition finishes.
     * During onExit you can't access a sibling node.
     * If you override onExit, you shall call its parent's one, e.g., CCNode::onExit().
     * @js NA
     * @lua NA
     */
    virtual void onExit();

    /** 
     * Event callback that is called every time the CCNode leaves the 'stage'.
     * If the CCNode leaves the 'stage' with a transition, this callback is called when the transition starts.
     * @js NA
     * @lua NA
     */
    virtual void onExitTransitionDidStart();

    /// @} end of event callbacks.


    /** 
     * Stops all running actions and schedulers
     */
    virtual void cleanup(void);
    
    。。。。。
	  。。。。。

};

 cocos2dx程序框架流程:

首先进入main.cpp函数,可以发现程序调用了Appliaction的run方法:

int APIENTRY _tWinMain(HINSTANCE hInstance,
                       HINSTANCE hPrevInstance,
                       LPTSTR    lpCmdLine,
                       int       nCmdShow)
{
。。。。。
。。。。。
    return CCApplication::sharedApplication()->run();
}

 然后查看run方法,是一个while(1)循环,调用了Director的mainloop方法:

int CCApplication::run()
{
。。。。。
。。。。。
    while (1)
    {
。。。。。
     CCDirector::sharedDirector()->mainLoop();
。。。。。
    }
。。。。。
}

 查看mainLoop方法,发现其调用了drawScene方法:

void CCDisplayLinkDirector::mainLoop(void)
{
。。。。。
。。。。。
         drawScene();
。。。。。
}

 查看drawScene方法:

void CCDirector::drawScene(void)
{
。。。。。
。。。。。
    if (m_pNextScene)
    {
        setNextScene();
    }

。。。。。

    if (m_pRunningScene)
    {
        m_pRunningScene->visit();
    }
。。。。。
}

 查看代码到这里,就可以大致的知道cocos2dx程序的运行流程,通过不停的循环,来执行当前scene的visit方法绘制界面,如果有场景切换,就切换场景后继续visiit。这里作者不关心visit和draw方法,只关注于CCNode的回调流程。

没有场景切换动画时的回调流程:

void CCDirector::setNextScene(void):

void CCDirector::setNextScene(void)
{
    bool runningIsTransition = dynamic_cast<CCTransitionScene*>(m_pRunningScene) != NULL;
    bool newIsTransition = dynamic_cast<CCTransitionScene*>(m_pNextScene) != NULL;

    // If it is not a transition, call onExit/cleanup
     if (! newIsTransition)
     {
         if (m_pRunningScene)
         {
             m_pRunningScene->onExitTransitionDidStart();
             m_pRunningScene->onExit();
         }
 
         // issue #709. the root node (scene) should receive the cleanup message too
         // otherwise it might be leaked.
         if (m_bSendCleanupToScene && m_pRunningScene)
         {
             m_pRunningScene->cleanup();
         }
     }

    if (m_pRunningScene)
    {
        m_pRunningScene->release();
    }
    m_pRunningScene = m_pNextScene;
    m_pNextScene->retain();
    m_pNextScene = NULL;

    if ((! runningIsTransition) && m_pRunningScene)
    {
        m_pRunningScene->onEnter();
        m_pRunningScene->onEnterTransitionDidFinish();
    }
}

 通过查看setNextScene的方法,就可以发现在没有场景切换动画时,各个回调函数的调用流程很清楚了。

首先调用要退出场景的 onExitTransitionDidStart 方法和 onExit 方法,如果当前是切换场景不是push场景,就继续调用cleanup方法,最后在执行新场景的 onEnter 和 onEnterTransitionDidFinish 方法。

我们查看下这4个onXXXX方法,CCScene和CCLayer均继承自CCNode,这4个方法也是CCNode的方法。我们以onEnter为例,

void CCNode::onEnter()
{
    arrayMakeObjectsPerformSelector(m_pChildren, onEnter, CCNode*);
。。。。。
}

 可以发现里面有个宏,这个就是关键所在~~,宏的定义如下:

#define arrayMakeObjectsPerformSelector(pArray, func, elementType)    \
do {                                                                  \
    if(pArray && pArray->count() > 0)                                 \
    {                                                                 \
        CCObject* child;                                              \
        CCARRAY_FOREACH(pArray, child)                                \
        {                                                             \
            elementType pNode = (elementType) child;                  \
            if(pNode)                                                 \
            {                                                         \
                pNode->func();                                        \
            }                                                         \
        }                                                             \
    }                                                                 \
}                                                                     \
while(false)

 由此就很清晰了,当场景切换时,新老场景的各个回调方法会被回调,这些回调方法又会递归调用其children的相同的回调方法,由于CCScene,CCLayer,CCSprite等在cocos2dx的场景构建时为父子关系,那么场景中的各种元素都会回调一次。

有场景切换动画时的回调流程:

有场景动画时回调比较复杂一点,首先需要明确的是场景切换动画也是场景。这样当场景A通过动画X切换到场景B时,实际上是有3个场景:A,X,B。为了便于分析,我们假设场景切换动画X为 CCTransitionShrinkGrow 类的动画。

从场景A切换到场景X:

回到函数 void CCDirector::setNextScene(void) 中,当从A切到X时,m_pRunningScene 是场景A,m_pNextScene 是场景X,A不是场景切换动画类CCTransitionShrinkGrow(CCTransitionScene),X是场景切换动画类。那么根据函数逻辑,函数流程会直接走向场景X的onEnter和onEnterXXX函数,但不会走向场景A的onExitXXX和onExit函数。

CCTransitionScene和CCTransitionShrinkGrow片段:

代码较多,但是根据代码的走向就很清晰了。

class CC_DLL CCTransitionScene : public CCScene
{
protected:
    CCScene    * m_pInScene;
    CCScene    * m_pOutScene;
。。。。。
。。。。。
}
class CC_DLL CCTransitionShrinkGrow : public CCTransitionScene , public CCTransitionEaseScene
{
。。。。。
。。。。。
}

CCTransitionScene * CCTransitionScene::create(float t, CCScene *scene)
{
    CCTransitionScene * pScene = new CCTransitionScene();
    if(pScene && pScene->initWithDuration(t,scene))
    {
        pScene->autorelease();
        return pScene;
    }
    CC_SAFE_DELETE(pScene);
    return NULL;
}

bool CCTransitionScene::initWithDuration(float t, CCScene *scene)
{
    CCAssert( scene != NULL, "Argument scene must be non-nil");

    if (CCScene::init())
    {
        m_fDuration = t;

        // retain
        m_pInScene = scene;
        m_pInScene->retain();
        m_pOutScene = CCDirector::sharedDirector()->getRunningScene();
        if (m_pOutScene == NULL)
        {
            m_pOutScene = CCScene::create();
            m_pOutScene->init();
        }
        m_pOutScene->retain();

        CCAssert( m_pInScene != m_pOutScene, "Incoming scene must be different from the outgoing scene" );
        
        sceneOrder();

        return true;
    }
    else
    {
        return false;
    }
}
void CCTransitionScene::finish()
{
。。。。。
。。。。。
    this->schedule(schedule_selector(CCTransitionScene::setNewScene), 0);
}
void CCTransitionScene::setNewScene(float dt)
{    
   。。。。。
    CCDirector *director = CCDirector::sharedDirector();
	 。。。。。
    director->replaceScene(m_pInScene);
   。。。。。
}

// custom onEnter
void CCTransitionScene::onEnter()
{
    CCScene::onEnter();
    
    // disable events while transitions
    CCDirector::sharedDirector()->getTouchDispatcher()->setDispatchEvents(false);
    
    // outScene should not receive the onEnter callback
    // only the onExitTransitionDidStart
    m_pOutScene->onExitTransitionDidStart();
    
    m_pInScene->onEnter();
}

// custom onExit
void CCTransitionScene::onExit()
{
    CCScene::onExit();
    
    // enable events while transitions
    CCDirector::sharedDirector()->getTouchDispatcher()->setDispatchEvents(true);
    
    m_pOutScene->onExit();

    // m_pInScene should not receive the onEnter callback
    // only the onEnterTransitionDidFinish
    m_pInScene->onEnterTransitionDidFinish();
}

void CCTransitionShrinkGrow::onEnter()
{
    CCTransitionScene::onEnter();
    。。。。。
    。。。。。
   
    m_pInScene->runAction(this->easeActionWithAction(scaleIn));
    m_pOutScene->runAction
    (
        CCSequence::create
        (
            this->easeActionWithAction(scaleOut),
            CCCallFunc::create(this, callfunc_selector(CCTransitionScene::finish)), 
            NULL
        )
    );
}

 首先,当走向场景X的onEnter时,CCTransitionShrinkGrow类的onEnter函数首先调用了父类CCTransitionScene的onEnter函数,然后调用了CCTransitionScene的finish函数。

根据CCTransitionScene的initXX函数,可以知道在CCTransitionScene中,m_pInScene是场景B,m_pOutScene是场景A,那么根据CCTransitionScene的onEnter函数的逻辑,知道此时场景A的onExitXXX被调用,场景B的onEnter被调用。

再来分析CCTransitionScene的finish函数,该函数又调用了CCTransitionScene的setNewScene函数,在该函数中,我们发现程序调用了我们熟悉的ReplaceScene函数,前面已经知道了m_pInScene是场景B,即程序现在开始执行由场景X切换场景B。

由场景X切换场景B:

和上面一样,我们分析void CCDirector::setNextScene(void) 中,当从X切到B时,m_pRunningScene 是场景X,m_pNextScene 是场景B,B不是场景切换动画类CCTransitionShrinkGrow(CCTransitionScene),X是场景切换动画类。那么根据函数逻辑,函数流程会走向场景X的onExitXXX和onExit函数,但是不会执行场景B的onEnter和onEnterXXX函数。

场景X类是CCTransitionShrinkGrow类,执行场景X的onExitXXX函数和onExit函数时,会进入CCTransitionScene函数的onExit函数,由于cocos2dx是类实例(指针)传递,因此这里的实例和上面由A切到X的类实例是一个,那么m_pInScene依然是场景B,m_pOutScene依然是场景A,在CCTransitionScene函数的onExit函数里,场景A的onExit被调用,场景B的onEnterXXX被调用。

此时,整个场景切换过程已经完成。

综上可得:

没有切换动画时,调用流程:

A: onExitTransitionDidStart

A: onExit

B: onEnter

B: onEnterTransitionDidFinish

有切换动画时,调用流程:

A: onExitTransitionDidStart

B: onEnter

A: onExit

B: onEnterTransitionDidFinish

发表评论

2 × 3 =

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据