cocos2d-x切换场景时的研究

关于场景的切换,本人在项目中使用的方法总会存在一些问题。之前也有看过源码,不过没有深入研究下,都是找到当时问题的解决办法就没继续探究下去,算是浅尝辄止吧。今天又遇到问题了,不厌其烦,所以决定仔细研究下,寻个彻底的解决方案。

首先,调用切换场景的函数是:CCDirector下的void replaceScene(CCScene *pScene);

void CCDirector::replaceScene(CCScene *pScene)
{
    CCAssert(m_pRunningScene, "Use runWithScene: instead to start the director");
    CCAssert(pScene != NULL, "the scene should not be null");

    unsigned int index = m_pobScenesStack->count();

    m_bSendCleanupToScene = true;
    m_pobScenesStack->replaceObjectAtIndex(index - 1, pScene);

    m_pNextScene = pScene;
}

1.m_pobScenesStack是CCDirector里面场景的数组

查看一下其在其他地方的调用,发现其主要的作用是用在pushScene以及popScene方法中,这两个方法是另一种切换场景的方式,本文暂不做研究。

2. m_bSendCleanupToScene = true;

查看一下其的调用情况,发现void CCDirector::setNextScene(void)中有这么一段:

 // 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();
         }

setNextScene函数从字面意思来理解是,设置下一场景的意思,这个方法等下研究

从上面可以看出,m_bSendCleanupToScene 的作用是设置下一场景的时候,判断需不需要将当前运行的场景cleanup

3.m_pNextScene = pScene;设置下一个场景

查看其调用情况,发现在void CCDirector::drawScene(void)方法下面

 if (m_pNextScene)
    {
        setNextScene();
    }

有这一段,意思是设置了m_pNextScene ,后在此处,调用setNextScene();来进行场景的切换

上面可以看出,主要的切换场景的方法是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;//m_pNextScene 指针置空


    if ((! runningIsTransition) && m_pRunningScene)//当前运行场景不在过渡状态
    {
        m_pRunningScene->onEnter();
        m_pRunningScene->onEnterTransitionDidFinish();
    }
}

以上方法可以分为两种情况:

1)如果不使用过渡特效,则调用当前场景的onExitTransitionDidStart(),以及onExit()两个方法,然后cleanup()方法,再release()。然后把m_pRunningScene指向到m_pNextScene,再把m_pNextScene retain(),然后把指针m_pNextScene置空,并调用新的当前场景m_pRunningScene的onEnter() 以及onEnterTransitionDidFinish()方法

2)如果使用过渡特效,则调用m_pRunningScene的release()方法,然后把m_pRunningScene指向到m_pNextScene,再把m_pNextScene retain(),然后把指针m_pNextScene置空,并调用新的当前场景m_pRunningScene的onEnter() 以及onEnterTransitionDidFinish()方法


在此,总结一下,如果不使用过渡特效切换场景的话,流程如下:

1)创建新的Scene newScene

2)调用CCDirector的void replaceScene(newScene);方法

3)主线程会因为2)中设置了m_pNextScene,而调用CCDirector的void CCDirector::setNextScene(void)方法

4)在setNextScene方法中的调用流程是:

oldScene : onExitTransitionDidStart()

onExit()

cleanup()

release()

newScene: retain()

onEnter() 

onEnterTransitionDidFinish()

由此可以总结下,不过切换场景,场景方法调用的流程:

newScene: init()

oldScene : onExitTransitionDidStart()

onExit()

cleanup()

release()

newScene: retain()

onEnter() 

onEnterTransitionDidFinish()

oldScene ::~oldScene (void)


下面来研究下,使用过渡特效切换场景

最初的调用方法:

CCDirector::sharedDirector()->replaceScene(cocos2d::CCTransition特效::create(time, newScene));

查看下特效的创建方法CCTransition特效::create(time, newScene):(以一个特效为例)

CCTransitionFadeUp* CCTransitionFadeUp::create(float t, CCScene* scene)
{
    CCTransitionFadeUp* pScene = new CCTransitionFadeUp();
    if(pScene && pScene->initWithDuration(t, scene))
    {
        pScene->autorelease();
        return pScene;
    }
    CC_SAFE_DELETE(pScene);
    return NULL;
}
可以看出,它return的是一个新new出来的CCTransition特效 对象,这个对象是继承自CCTransitionScene的

再来看看pScene->initWithDuration(t, scene)方法里做了什么

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

    if (CCScene::init())//判断新场景有没有init,没有就init
    {
        m_fDuration = t;//过渡时间

        // retain
        m_pInScene = scene;//m_pInScene 记录过渡后场景
        m_pInScene->retain();//retain过渡后场景
        m_pOutScene = CCDirector::sharedDirector()->getRunningScene();//m_pOutScene 记录过渡前场景
        if (m_pOutScene == NULL)//如果过渡前场景为空,则创建个,并init
        {
            m_pOutScene = CCScene::create();
            m_pOutScene->init();
        }
        m_pOutScene->retain();//retain过渡前场景 AAA

        CCAssert( m_pInScene != m_pOutScene, "Incoming scene must be different from the outgoing scene" );
        
        sceneOrder();//场景排序

        return true;
    }
    else
    {
        return false;
    }
}
 sceneOrder(); 这个方法是干嘛的呢?看下源码


void CCTransitionScene::sceneOrder()
{
    m_bIsInSceneOnTop = true;//字面意思上,理解为过渡后场景在上
}
原来是设置m_bIsInSceneOnTop为true,

那这个m_bIsInSceneOnTop具体有什么作用呢?

查看其调用,发现其主要作用是

void CCTransitionScene::draw()
{
    CCScene::draw();

    if( m_bIsInSceneOnTop ) {
        m_pOutScene->visit();//过渡前场景
        m_pInScene->visit();//过渡后场景
    } else {
        m_pInScene->visit();//过渡后场景
        m_pOutScene->visit();//过渡前场景
    }
}
过渡场景draw的时候,用m_bIsInSceneOnTop来控制两个场景的访问顺序。



再看replaceScene(cocos2d::CCTransition特效::create(time, newScene));这个方法,其他跟之前一样

不过 m_pNextScene = pScene;这一句使得m_pNextScene 指向为一个继承自CCTransitionScene的对象

然后因为m_pNextScene 不为空,主线程会调用CCDirector的void CCDirector::setNextScene(void)方法,

再逐句分析下 CCDirector::setNextScene(void)方法,会有以下调用结果:

void CCDirector::setNextScene(void)
{
    bool runningIsTransition = dynamic_cast<CCTransitionScene*>(m_pRunningScene) != NULL;//该值为false
    bool newIsTransition = dynamic_cast<CCTransitionScene*>(m_pNextScene) != NULL;//因为之前m_pNextScene被赋值,此处newIsTransition 为true

    // 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)//过渡前场景release,注:这个场景不会被释放掉,因为在AAA ,该场景被retain
    {
        m_pRunningScene->release();
    }
    m_pRunningScene = m_pNextScene;//m_pRunningScene 指向m_pNextScene,即那个过渡场景
    m_pNextScene->retain();//过渡场景retain
    m_pNextScene = NULL;//m_pNextScene 指针置空,其实就是把主线程里调用该方法的开关关了

    if ((! runningIsTransition) && m_pRunningScene)//条件成立
    {
        m_pRunningScene->onEnter();//调用过渡场景onEnter方法
        m_pRunningScene->onEnterTransitionDidFinish();//调用过渡场景onEnterTransitionDidFinish方法
    }
}

下面看看调用过渡场景的onEnter方法里做了什么?上面举得例子CCTransitionFadeUp没有重写OnEnter方法,其父类CCTransitionFadeTR有重写

void CCTransitionFadeTR::onEnter()
{
    CCTransitionScene::onEnter();

    CCSize s = CCDirector::sharedDirector()->getWinSize();
    float aspect = s.width / s.height;
    int x = (int)(12 * aspect);
    int y = 12;

    CCActionInterval* action  = actionWithSize(CCSizeMake(x,y));

    m_pOutScene->runAction
    (
        CCSequence::create
        (
            easeActionWithAction(action),
            CCCallFunc::create(this, callfunc_selector(CCTransitionScene::finish)), 
            CCStopGrid::create(),
            NULL
        )
    );
}
本人查看了大部分的特效OnEnter,基本实现特效的一些设置,但是都有一个共同点,就是都有这一句

CCCallFunc::create(this, callfunc_selector(CCTransitionScene::finish))

意思是在,执行完过渡之后调用CCTransitionScene::finish方法。

当然还有调用了父类的CCTransitionScene的onEnter  方法

// custom onEnter
void CCTransitionScene::onEnter()
{
    CCScene::onEnter();//父类OnEnter
    
    // disable events while transitions
    CCDirector::sharedDirector()->getTouchDispatcher()->setDispatchEvents(false);//过渡期间关闭触摸事件响应
    
    // outScene should not receive the onEnter callback
    // only the onExitTransitionDidStart
    m_pOutScene->onExitTransitionDidStart();//调用过度前场景的onExitTransitionDidStart方法
    
    m_pInScene->onEnter();//调用过渡后场景的onEnter方法
}

那调用过渡场景的onEnterTransitionDidFinish方法里做了什么?这个方法,过渡场景类没有重写,就不看了


再来看看CCTransitionScene::finish方法

void CCTransitionScene::finish()
{
    // clean up     
     m_pInScene->setVisible(true);
     m_pInScene->setPosition(ccp(0,0));
     m_pInScene->setScale(1.0f);
     m_pInScene->setRotation(0.0f);
     m_pInScene->getCamera()->restore();
 
     m_pOutScene->setVisible(false);
     m_pOutScene->setPosition(ccp(0,0));
     m_pOutScene->setScale(1.0f);
     m_pOutScene->setRotation(0.0f);
     m_pOutScene->getCamera()->restore();

    //[self schedule:@selector(setNewScene:) interval:0];
    this->schedule(schedule_selector(CCTransitionScene::setNewScene), 0);

}

没什么看的,主要进行了切换完之后两个场景的一些恢复工作,最后调用CCTransitionScene::setNewScene方法

来看看

void CCTransitionScene::setNewScene(float dt)
{    
    CC_UNUSED_PARAM(dt);

    this->unschedule(schedule_selector(CCTransitionScene::setNewScene));
    
    // Before replacing, save the "send cleanup to scene"
    CCDirector *director = CCDirector::sharedDirector();
    m_bIsSendCleanupToScene = director->isSendCleanupToScene();
    
    director->replaceScene(m_pInScene);//切换场景到过渡后场景
    
    // issue #267
    m_pOutScene->setVisible(true);
}
该方法也主要是调用了CCDirector的replaceScene方法来将过渡后场景替换为新场景

replaceScene就不研究了,主要是把 m_pNextScene指向到m_pInScene过渡后场景

m_pNextScene不为空,然后在主线程当中,又会调用CCDirector的setNextScene()方法

void CCDirector::setNextScene(void)
{
    bool runningIsTransition = dynamic_cast<CCTransitionScene*>(m_pRunningScene) != NULL;//这个为true,过渡场景即为当前场景
    bool newIsTransition = dynamic_cast<CCTransitionScene*>(m_pNextScene) != NULL;//false,新场景不是过渡场景

    // If it is not a transition, call onExit/cleanup
     if (! newIsTransition)//true
     {
         if (m_pRunningScene)
         {
             m_pRunningScene->onExitTransitionDidStart();//过渡场景onExitTransitionDidStart
             m_pRunningScene->onExit();//过渡场景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();//过渡场景cleanup
         }
     }

    if (m_pRunningScene)
    {
        m_pRunningScene->release();//释放过渡场景
    }
    m_pRunningScene = m_pNextScene;//当前场景设置为过渡后场景
    m_pNextScene->retain();//过渡后场景retain
                 = NULL;//m_pNextScene 指针置空,其实就是把主线程里调用该方法的开关关了

    if ((! runningIsTransition) && m_pRunningScene)//这个为false,不进入
    {
        m_pRunningScene->onEnter();
        m_pRunningScene->onEnterTransitionDidFinish();
    }
}
来看下过渡场景的onExit()方法

// custom onExit
void CCTransitionScene::onExit()
{
    CCScene::onExit();//父类onExit方法
    
    // enable events while transitions
    CCDirector::sharedDirector()->getTouchDispatcher()->setDispatchEvents(true);//开启触摸事件
    
    m_pOutScene->onExit();//过渡前场景onExit

    // m_pInScene should not receive the onEnter callback
    // only the onEnterTransitionDidFinish
    m_pInScene->onEnterTransitionDidFinish();//过渡后场景onEnterTransitionDidFinish
}



   好了,来总结下流程吧:

newScene: init()

TransitionScene: create()

initWithDuration(float t, CCScene *scene)

newScene: retain()

oldScene: retain()

oldScene : release()

TransitionScene retain()

onEnter() 

oldScene: onExitTransitionDidStart()

newScene: onEnter() 

TransitionScene onEnterTransitionDidFinish()

过渡特效...

TransitionScene onExitTransitionDidStart()

onExit()

oldScene: onExit()

newScene: onEnterTransitionDidFinish()

TransitionScene cleanup()

release()

oldScene:                  release()

newScene: release()

retain()

大致是这样的流程,有些可能有错误,尤其最后,release什么的,因为是auto release,所以应该是下一帧释放


能够理解上面的流程,其实很多问题就都能够解决了~

说下我遇到的问题,我想将一个layer加到scene上去,我一般是这么做的,就是直接将layer加到当前scene上去,就是m_pRunningScene,这样做的好处是,我不需要每次指定哪个scene~直接添加就好,坏处也在这里,我想将layer加到新场景里,在新场景init的时候,我将layer加上去,但是这时候,m_pRunningScene指向的是旧scene

解决办法嘛,将layer在新场景onEnter的时候加到m_pRunningScene上去,这样可不可以呢?

在没有切换特效的时候OK

那么有切换特效的时候呢,找到newScene: onEnter() 这个时候的m_pRunningScene指向谁?可以看到这个时候指向过渡场景,所以,也是不行的,有没有什么办法解决呢?

我的想法是在onEnter里面加入,然后先判断m_pRunningScene是不是过渡场景,如果不是,则将layer加入到m_pRunningScene下;如果是,则将layer加入到过渡场景m_pRunningScene的下一个场景m_pInScene。但是m_pInScene是protected类型,无法访问。

所以还是只能直接指定layer确切的需要加入到哪个scene上去








你可能感兴趣的:(流程,cocos2d-x,切换场景)