在上一篇的文章已经通过代码分析了场景的跳转是在主循环中setNextScene进行调用的,那么在跳转时便会开始调用生命周期函数。主要由以下四个函数组成
onEnter、onEnterTransitionDidFinish、onExitTransitionDidStart、onExit,这四个分别定义在CCNode的节点类中。所以很明显,CCScene场景类是继承自于CCNode。
CCScene的内容非常的简单,它本质上就是一个非常普通的节点类,然后再把其他的节点全部挂载在这个节点下进行渲染和控制,也就是说它一个装载了游戏内容的一个大容器。
class CC_DLL CCScene : public CCNode
{
public:
/**
* @js ctor
*/
CCScene();
/**
* @js NA
* @lua NA
*/
virtual ~CCScene();
bool init();
static CCScene *create(void);
};
CCScene::CCScene()
{
m_bIgnoreAnchorPointForPosition = true;
setAnchorPoint(ccp(0.5f, 0.5f));
}
CCScene::~CCScene()
{
}
bool CCScene::init()
{
bool bRet = false;
do
{
CCDirector * pDirector;
CC_BREAK_IF( ! (pDirector = CCDirector::sharedDirector()) );
this->setContentSize(pDirector->getWinSize());//初始化大小为屏幕窗口大小
// success
bRet = true;
} while (0);
return bRet;
}
CCScene *CCScene::create()
{
CCScene *pRet = new CCScene();
if (pRet && pRet->init())
{
pRet->autorelease();
return pRet;
}
else
{
CC_SAFE_DELETE(pRet);
return NULL;
}
}
这个类确实是很简单,没有多少内容,但它确实承载游戏内容的一个宿主,占据极大的一块内存空间,所以在跳转时要非常的清楚内存的控制。
因为在场景切换是不论是调用那种切换函数(pushScene或replaceScene),程序中都会有存储两个场景的内存开销,因为切换函数都是不会先销毁上一个场景,再创建新场景的。
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();
//替换场景时要清除通知,isSendCleanupToScene方法的注释中有提到
m_bSendCleanupToScene = true;
//将栈顶中的场景替换成即将要运行的场景,这样便销毁了上一个场景的资源(里面有一个release),但在这之前程序中始终会占有两个场景的内存空间
m_pobScenesStack->replaceObjectAtIndex(index - 1, pScene);
//将此栈赋给m_pNextScene,注意m_pNextScene是一个弱引用(这样在主循环中便会调用切换场景的方法setScene)
m_pNextScene = pScene;
}
void CCDirector::pushScene(CCScene *pScene)
{
//断言判断场景是否为空
CCAssert(pScene, "the scene should not null");
//入栈时不需要通知,isSendCleanupToScene方法的注释中有提到
m_bSendCleanupToScene = false;
//加入栈中的队列中,由一个数组来维护
m_pobScenesStack->addObject(pScene);
//将此栈赋给m_pNextScene,注意m_pNextScene是一个弱引用(这样在主循环中便会调用切换场景的方法setScene)
m_pNextScene = pScene;
}
void CCDirector::popScene(void)
{
CCAssert(m_pRunningScene != NULL, "running scene should not null");
//出栈——删除数组中的最后一个元素
m_pobScenesStack->removeLastObject();
//获取当前场景栈的数量
unsigned int c = m_pobScenesStack->count();
//如果当前栈没有场景,这退出主循环,结束游戏
if (c == 0)
{
end();
}
else
{
m_bSendCleanupToScene = true;//通知清除消息
m_pNextScene = (CCScene*)m_pobScenesStack->objectAtIndex(c - 1);//将最后一个场景赋给m_pNextScene(也就是出栈时的倒数第二个场景),注意m_pNextScene是一个弱引用(这样在主循环中便会调用切换场景的方法setScene)
}
}
通过上面的注释可以很清楚的看到在切换场景时引擎所做的事情,也解释了为什么跳转时推荐使用replaceScene(因为replaceScene会release掉被替换的场景,但这里的release还不会触发引擎去调用被替换场景的析构函数,主要是在切换场景函数中setNextScene中也有个retian,所以真正触发是在setNextScene的release中,这样做便能保证被替换的场景的生命周期函数可以完整的被执行,也就是onExitTransitionDidStart和onExit方法)。但上面的那个问题依旧还是会存在(如果两个场景都占有非常大的内存空间的话,这个问题便会十分严重),所以比较好的做法便是创建一个过渡场景,虽然也是个场景,但极低的内存资源,因为它与游戏的内容无关,只是用来过渡而已,这时便要配合场景的生命周期来实现。
具体的思路如下:
正在运行中的场景标记为sc1。
过渡场景标记为sc2。
即将要运行的场景标记为sc3.
1. 创建一个sc2,创建完后调用replaceScene切换,切换完后主循环便会检测到需要执行场景切换操作。
2. 在场景切换操作中会涉及到场景的生命周期函数的调用。从之前的代码可以看到其过程如下:
1)sc1调用onExitTransitionDidStart,然后再调用onExit,这时可以在这里进行清除sc1的操作。这样便把sc1的内存资源回收了。
2)sc2调用onEnter,然后再调用onEnterTransitionDidFinish,这时可以再onEneter中调用sc3的创建,然后再onEnterTransitionDidFinish中调用replaceScene进行场景切换。这样就能把即将要运行的场景入栈。
这里还要注意下是否是通过CCTransitionScene跳转的情况(大部分都是通过CCTransitionScene来跳转的)
对于上面来说是是没有CCTransitionScene或其子类来进行跳转的,其过程便是直接的sc1->onExitTransitionDidStart,sc1->onExit,sc1->onEnter,sc1->onEnterTransitionDidFinish。但对于通过CCTransitionScene来跳转的确又是不一样的,因为这种情况是通过CCTransitionScene来控制目标场景的生命周期函数的调用的。其过程还是用代码来说明:
首先先观察创建CCTransitionScene时的操作
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 CCDirector::setNextScene(void)
{
//当第一次运行时runningIsTransition肯定为假,因为是主函数直接pushScene进来的一个场景,是没有被CCTransitionScene所管理的
bool runningIsTransition = dynamic_cast(m_pRunningScene) != NULL;
//这里newIsTransition的为true,因为即将要运行的场景是通过CCTransitionScene包含进来的
bool newIsTransition = dynamic_cast(m_pNextScene) != NULL;
//跳过此if
if (! newIsTransition)//如果不是跳转进来的,而是直接切换的则直接清空上一个场景的资源
{
if (m_pRunningScene)//这里要先判断m_pRunningScene是否为空,因为第一次加载场景时m_pRunningScene肯定是为NULL
{
m_pRunningScene->onExitTransitionDidStart();//如果用CCTransitionScene跳转时会进入到CCTransitionScene的onExitTransitionDidStart中
m_pRunningScene->onExit();//如果用CCTransitionScene跳转时会进入到CCTransitionScene的onExit中
}
// issue #709. the root node (scene) should receive the cleanup message too
// otherwise it might be leaked.
if (m_bSendCleanupToScene && m_pRunningScene)//如果清除场景需要收到清除消息,则调用cleanup方法
{
m_pRunningScene->cleanup();
}
}
if (m_pRunningScene)//释放上一个场景的资源
{
m_pRunningScene->release();
}
m_pRunningScene = m_pNextScene;//将要运行的场景赋给表示正在运行的场景的指针(CCTransitionScene的指针)
m_pNextScene->retain();
m_pNextScene = NULL;//清掉m_pNextScene,直到有新场景入栈
//由于这里之的runningIsTransition为false,所以会进入以下两个函数的调用,这时的m_pRunningScene已经被CCTransitionScene所包含了
//所以下面的onEnter和onEnterTransitionDidFinish实际上是调用CCTransitionScene的onEnter和onEnterTransitionDidFinish,这样便间接的调用了目标场景的生命周期函数。
if ((! runningIsTransition) && m_pRunningScene)//进入新场景的生命周期
{
m_pRunningScene->onEnter();
m_pRunningScene->onEnterTransitionDidFinish();
}
}
这时已经调用了CCTransitionScene的onEnter和onEnterTransitionDidFinish方法(CCTransitionScene并没有重写onEnterTransitionDidFinish方法,也就是这里只通过onEnter来控制,然后再一次跳转时会调用onExit来控制)了。
void CCTransitionScene::onEnter()
{
CCScene::onEnter();
// disable events while transitions
CCDirector::sharedDirector()->getTouchDispatcher()->setDispatchEvents(false);
// outScene should not receive the onEnter callback
// only the onExitTransitionDidStart
//调用旧场景的onExitTransitionDidStart方法
m_pOutScene->onExitTransitionDidStart();
//调用新场景的onEnter
m_pInScene->onEnter();
}
这样整个生命周期函数便明朗了,现在为止,其调用的过程如下:
旧场景的onEnter和onExitTransitionDidStart先被调用,然后再切换时,会调用旧场景的onExitTransitionDidStart,然调用新场景的onEnter。
这里要清楚一点的是CCTransitionScene只是个过渡的场景,并不是真正的目标场景,自己也会有生命周期,也就是说当执行完以上过程后,还没有执行对真正的目标场景的跳转。但不幸的是,CCTransitionScene本身并没有执行最后的跳转,而是转交给了它的子类去实现跳转(这样便能实现多种多样的跳转效果了,这里以CCTransitionFade来举例),所以当以以下方式来跳转时,是不会发生真正的跳转的,也就是跳转失败,依旧停留在旧场景。
CCTransitionScene* t = CCTransitionScene::create(1.2f, TestScene::scene());
CCDirector::sharedDirector()->replaceScene(t);
void CCTransitionFade :: onEnter()
{
//调用父类的onEnter
CCTransitionScene::onEnter();
CCLayerColor* l = CCLayerColor::create(m_tColor);
m_pInScene->setVisible(false);
addChild(l, 2, kSceneFade);
CCNode* f = getChildByTag(kSceneFade);
CCActionInterval* a = (CCActionInterval *)CCSequence::create
(
CCFadeIn::create(m_fDuration/2),
CCCallFunc::create(this, callfunc_selector(CCTransitionScene::hideOutShowIn)),//CCCallFunc::create:self selector:@selector(hideOutShowIn)],
CCFadeOut::create(m_fDuration/2),
//注意这个回调函数
CCCallFunc::create(this, callfunc_selector(CCTransitionScene::finish)), //:self selector:@selector(finish)],
NULL
);
f->runAction(a);
}
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);
}
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();
//这里又执行了一次replaceScene的调用,这样便触发了对真正的即将要运行的场景的跳转,同时也将CCTransitionScene的资源进行了回收
director->replaceScene(m_pInScene);
// issue #267
m_pOutScene->setVisible(true);
}
void CCDirector::setNextScene(void)
{
//这时的runningIsTransition为true,因为当前运行的场景确实是CCTransitionScene
bool runningIsTransition = dynamic_cast(m_pRunningScene) != NULL;
//这里newIsTransition的为false,因为m_pNextScene已经被重置为目标场景了,而不是被CCTransitionScene所包含的场景
bool newIsTransition = dynamic_cast(m_pNextScene) != NULL;
if (! newIsTransition)//进入以下代码,完成生命周期的调用
{
//因为当前运行的场景是CCTransitionScene
//所以下面的onExitTransitionDidStart和onExit实际上是调用CCTransitionScene的onExitTransitionDidStart和onExit,这样便间接的完成了整个生命周期函数的调用
if (m_pRunningScene)//这里要先判断m_pRunningScene是否为空,因为第一次加载场景时m_pRunningScene肯定是为NULL
{
m_pRunningScene->onExitTransitionDidStart();//如果用CCTransitionScene跳转时会进入到CCTransitionScene的onExitTransitionDidStart中
m_pRunningScene->onExit();//如果用CCTransitionScene跳转时会进入到CCTransitionScene的onExit中
}
// issue #709. the root node (scene) should receive the cleanup message too
// otherwise it might be leaked.
if (m_bSendCleanupToScene && m_pRunningScene)//如果清除场景需要收到清除消息,则调用cleanup方法
{
m_pRunningScene->cleanup();
}
}
if (m_pRunningScene)//释放上一个场景的资源,也就是CCTransitionScene
{
m_pRunningScene->release();
}
m_pRunningScene = m_pNextScene;//将要运行的场景赋给表示正在运行的场景的指针(CCTransitionScene的指针)
m_pNextScene->retain();
m_pNextScene = NULL;//清掉m_pNextScene,直到有新场景入栈
//由于这里之的runningIsTransition为true,所以会不会进入以下执行
if ((! runningIsTransition) && m_pRunningScene)
{
m_pRunningScene->onEnter();
m_pRunningScene->onEnterTransitionDidFinish();
}
}
void CCTransitionScene::onExit()
{
CCScene::onExit();
// enable events while transitions
CCDirector::sharedDirector()->getTouchDispatcher()->setDispatchEvents(true);
//调用旧场景的onExit方法
m_pOutScene->onExit();
// m_pInScene should not receive the onEnter callback
// only the onEnterTransitionDidFinish
//调用新场景的onEnterTransitionDidFinish
m_pInScene->onEnterTransitionDidFinish();
}
总结以上过程便是最初始旧场景的onEnter,然后旧场景的onExitTransitionDidStart,然后旧场景的onExitTransitionDidStart,然后新场景的onEnter,然后旧场景的onExit,最后是新场景的onEnterTransitionDidFinish。如果又有场景通过CCTransitionScene的子类进行跳转的话又会重复以上的一个过程。
最后测试以上的结果:
首先是不通过CCTransitionScene来跳转。
void HelloWorld::menuCloseCallback(CCObject* pSender)
{
// "close" menu item clicked
//CCDirector::sharedDirector()->end();
//CCTransitionScene* t = CCTransitionFade::create(1.2f, TestScene::scene());
CCDirector::sharedDirector()->replaceScene(TestScene::scene());
}
打印出来的结果是:
Old Scene: onEnter
Old Scene: onEnterTransitionDidFinish
replaceScene is use(replaceScene 方法被调用)
Old Scene: onExitTransitionDidStart
Old Scene: onExit
New Scene: onEnter
New Scene: onEnterTransitionDidFinish
New Scene: onExitTransitionDidStart
New Scene: onExit
其实是通过CCTransitionScene来跳转。
void HelloWorld::menuCloseCallback(CCObject* pSender)
{
// "close" menu item clicked
//CCDirector::sharedDirector()->end();
CCTransitionScene* t = CCTransitionFade::create(1.2f, TestScene::scene());
CCDirector::sharedDirector()->replaceScene(t);
}
打印出来的结果是:
Old Scene: onEnter
Old Scene: onEnterTransitionDidFinish
replaceScene is use(replaceScene 方法被调用)
Old Scene: onExitTransitionDidStart
New Scene: onEnter
replaceScene is use(replaceScene 方法被调用)
Old Scene: onExit
New Scene: onEnterTransitionDidFinish
New Scene: onExitTransitionDidStart
New Scene: onExit
结果与分析无误。