【Cocos2d-x 3.x】 场景切换生命周期、背景音乐播放和场景切换原理与源码分析
大部分游戏里有很多个场景,场景之间需要切换,有时候切换的时候会进行背景音乐的播放和停止,因此对这块内容进行了总结。
场景切换生命周期
bool Setting::init()
{
if( !Layer::init() )
{
returnfalse;
} log("Settinginit");
......
returntrue;
} void Setting::onEnter()
{
Layer::onEnter();
log("SettingonEnter");
} void Setting::onEnterTransitionDidFinish()
{
Layer::onEnterTransitionDidFinish();
log("SettingonEnterTransitionDidFinish");
} void Setting::onExit()
{
Layer::onExit();
log("SettingonExit");
} void Setting::onExitTransitionDidStart()
{
Layer::onExitTransitionDidStart();
log("SettingonExitTransitionDidStart");
} void Setting::cleanup()
{
Layer::cleanup();
log("Settingcleanup");
}
常见的三种方式为:
1. replaceScene
这种情况下场景的切换顺序为:
2. pushScene
3. popScene
可以看出,replaceScene方式会释放掉需要替换掉的场景,而pushScene和popScene不会释放掉。
场景切换时进行背景音乐的调整
1 . 播放背景音乐
推荐在Setting::onEnterTransitionDidFinish()函数里进行开启背景音乐,因为onEnterTransitionDidFinish函数是进入层且在场景动画结束后调用,播放音乐的代码放在这里用考虑前面场景是否有调用背景音乐停止语句,也不会出现用户先听到背景音乐,后看到界面现象。
2. 停止播放背景音乐
如果从HelloWorld到Setting是pushScene方法进入Setting的,则推荐停止播放背景音乐在HelloWorld::cleanup()中调用停止播放背景音乐的函数,因为这种方式进行切换场景时不会调用HellWorld::cleanup();
如果是用replaceScene进行场景切换, 实际上可以不进行背景音乐停止的调用的,因为在Setting::onEnterTransitionFinish()进行背景音乐的播放的调用,如果在HellWorld::cleanup()进行停止播放音乐的调用,则在Setting中停止播放背景音乐,因此在replaceScene的方式中可以不停止播放的。
场景切换原理(包含过渡场景)
<CCTransition.h>头文件里定义了很多场景切换的过渡动画,常见的场景切换为:
auto scene = cocos2d::HelloWorldScene::createScene();
auto transitionScene = cocos2d::TransitionScene::create(1.0, scene);
cocos2d::Director::getInstance()->replaceScene(transitionScene);
过渡场景进入舞台时,正是旧的场景离开舞台和新的场景切入舞台的时候;
过渡场景离开舞台时,旧的场景离开了舞台,新的场景已经进入了舞台。
【原理】
先创建一个新的场景,然后创建一个过渡场景,先看看在创建过渡场景发生了什么:
TransitionScene * TransitionScene::create(float t, Scene *scene)
{
TransitionScene * pScene = new (std::nothrow) TransitionScene();
if(pScene && pScene->initWithDuration(t,scene))
{
pScene->autorelease();
return pScene;
}
CC_SAFE_DELETE(pScene);
return nullptr;
} bool TransitionScene::initWithDuration(float t, Scene *scene)
{
CCASSERT( scene != nullptr, "Argument scene must be non-nil"); if (Scene::init())
{
_duration = t; // 场景切换过渡时间 // retain
_inScene = scene; // _inScene表示要进入的场景
_inScene->retain();
_outScene = Director::getInstance()->getRunningScene(); // _outScene表示要替换掉的场景
if (_outScene == nullptr)
{
_outScene = Scene::create();
}
_outScene->retain(); CCASSERT( _inScene != _outScene, "Incoming scene must be different from the outgoing scene" ); sceneOrder(); return true;
}
else
{
return false;
}
} void TransitionScene::sceneOrder()
{
_isInSceneOnTop = true; // 表示切入的场景在切出场景的上面
}
可以看出,新创建一个过渡场景时,初始化了基本的变量,比如_inScene, _outScene,_duration,_isInSceneOnTop等。
创建好之后,通过导演类Director来进行replaceScene,看看repalceScene发生了什么:
void Director::replaceScene(Scene *scene)
{
//CCASSERT(_runningScene, "Use runWithScene: instead to start the director");
CCASSERT(scene != nullptr, "the scene should not be null");
// 如果当前场景不存在,则直接运行新场景
if (_runningScene == nullptr) {
runWithScene(scene);
return;
}
// 如果两个场景相同,则什么也不做
if (scene == _nextScene)
return;
// 如果下个场景不为空,则进行相应的清理工作,很明显在切换前并没有给这个变量赋值,因此_nextScene为空,此时跳过这一步
if (_nextScene)
{
if (_nextScene->isRunning())
{
_nextScene->onExit();
}
_nextScene->cleanup();
_nextScene = nullptr;
}
// _sceneStack是一个场景的栈
ssize_t index = _scenesStack.size();
// 将最后一个场景替换为当前场景,当前场景为过渡场景
_sendCleanupToScene = true;
_scenesStack.replace(index - 1, scene);
// 初始化_nextScene为过渡场景
_nextScene = scene;
}
我们知道,游戏开始运行后,每帧都会调用Director的mainLoop函数,在mainLoop中有这么一段代码:
else if (! _invalid)
{
drawScene();
// release the objects
PoolManager::getInstance()->getCurrentPool()->clear();
}
然后进入drawScene后,有这么一个判断:
if (_nextScene)
{
setNextScene();
}
因为这一帧检测到_nextScene不是nullptr,因此进入到setNextScene:
void Director::setNextScene()
{
// runningIsTransition表示此时运行的场景是否为过渡场景,很明显此时不是,runningIsTransition为false
bool runningIsTransition = dynamic_cast<TransitionScene*>(_runningScene) != nullptr;
// newIsTransition表示新的场景是否为过渡场景,很明显是,因此newIsTransition为true
bool newIsTransition = dynamic_cast<TransitionScene*>(_nextScene) != nullptr; // If it is not a transition, call onExit/cleanup
// 这一帧里不会进入到这个函数
if (! newIsTransition)
{
if (_runningScene)
{
_runningScene->onExitTransitionDidStart();
_runningScene->onExit();
} // issue #709. the root node (scene) should receive the cleanup message too
// otherwise it might be leaked.
if (_sendCleanupToScene && _runningScene)
{
_runningScene->cleanup();
}
}
// 对现在运行的场景的引用计数-1
if (_runningScene)
{
_runningScene->release();
}
// 将正在运行的场景设置为_nextScene过渡场景
_runningScene = _nextScene;
// 增加引用计数
_nextScene->retain();
_nextScene = nullptr; // 此时会进入这个if语句,过渡场景的进入舞台
if ((! runningIsTransition) && _runningScene)
{
_runningScene->onEnter();
_runningScene->onEnterTransitionDidFinish();
}
}
然后主要就是onEnter和onEnterTransitionDidFinish的调用,随便选择一种过渡方式来查看:
void TransitionRotoZoom:: onEnter()
{
// 首先调用基类的onEnter()
TransitionScene::onEnter(); _inScene->setScale(0.001f);
_outScene->setScale(1.0f); _inScene->setAnchorPoint(Vec2(0.5f, 0.5f));
_outScene->setAnchorPoint(Vec2(0.5f, 0.5f)); ActionInterval *rotozoom = (ActionInterval*)(Sequence::create
(
Spawn::create
(
ScaleBy::create(_duration/2, 0.001f),
RotateBy::create(_duration/2, 360 * 2),
nullptr
),
DelayTime::create(_duration/2),
nullptr
)); // 切出场景执行切出的动画
_outScene->runAction(rotozoom);
// 切入的场景执行切入动画
_inScene->runAction
(
Sequence::create
(
rotozoom->reverse(),
CallFunc::create(CC_CALLBACK_0(TransitionScene::finish,this)),
nullptr
)
);
}
然后我们看看基类的onEnter()都做了些什么:
void TransitionScene::onEnter()
{
Scene::onEnter(); // disable events while transitions
_eventDispatcher->setEnabled(false); // outScene should not receive the onEnter callback
// only the onExitTransitionDidStart
_outScene->onExitTransitionDidStart(); _inScene->onEnter();
}
可以看出,过渡场景进入舞台时,先设置不可触摸,毕竟在切换场景,触摸可能会发生意外。 然后是切出场景的退出舞台,切入场景的进入舞台。
然后继续看TransitionRotoZoom:: onEnter(),接下来就是设置切出场景切出所执行的动画和切入场景切入所执行的动画。
切入场景执行动作时,调用了基类的finish函数,这个函数就是在动画执行结束后调用的:
void TransitionScene::finish()
{
// clean up
_inScene->setVisible(true);
_inScene->setPosition(0,0);
_inScene->setScale(1.0f);
_inScene->setRotation(0.0f);
_inScene->setAdditionalTransform(nullptr); _outScene->setVisible(false);
_outScene->setPosition(0,0);
_outScene->setScale(1.0f);
_outScene->setRotation(0.0f);
_outScene->setAdditionalTransform(nullptr); //[self schedule:@selector(setNewScene:) interval:0];
this->schedule(CC_SCHEDULE_SELECTOR(TransitionScene::setNewScene), 0);
}
finish函数将切出场景和切入场景恢复原状后,调用了setNewScene函数:
void TransitionScene::setNewScene(float dt)
{
CC_UNUSED_PARAM(dt); this->unschedule(CC_SCHEDULE_SELECTOR(TransitionScene::setNewScene)); // Before replacing, save the "send cleanup to scene"
Director *director = Director::getInstance();
_isSendCleanupToScene = director->isSendCleanupToScene(); director->replaceScene(_inScene); // issue #267
_outScene->setVisible(true);
}
_isSendCleanupToScene获取到Director类清楚场景的标记,然后再一次调用Director类的replaceScene,这一次将_nextScene设置为刚开始创建的切入场景scene,然后在mainLoop函数的drawScene函数中再次检测到_nextScene不为空,再次进入setNextScene():
void Director::setNextScene()
{
// runningIsTransition表示此时运行的场景是否为过渡场景,很明显此时是,runningIsTransition为true
bool runningIsTransition = dynamic_cast<TransitionScene*>(_runningScene) != nullptr;
// newIsTransition表示新的场景是否为过渡场景,很明显不是,因此newIsTransition为false
bool newIsTransition = dynamic_cast<TransitionScene*>(_nextScene) != nullptr; // If it is not a transition, call onExit/cleanup
// 这一帧里会进入到这个函数
if (! newIsTransition)
{
// 此时正在运行的场景是过渡场景
if (_runningScene)
{
// 过渡场景退出舞台,此时会执行切出场景的onExit(),切出场景也会退出舞台
_runningScene->onExitTransitionDidStart();
_runningScene->onExit();
} // 已经得到清理场景的命令,因此_sendCleanupToScene为真,并且_runningScene存在
if (_sendCleanupToScene && _runningScene)
{
// 将过渡场景清空
_runningScene->cleanup();
}
}
// 减少过渡场景的引用计数,然后这一帧结束后会释放
if (_runningScene)
{
_runningScene->release();
}
// 将正在运行的场景设置为切入场景
_runningScene = _nextScene;
// 增加引用计数
_nextScene->retain();
_nextScene = nullptr; // 此时不会进入这个if语句,因为此时运行的是过渡场景,_runningScene表示切入场景,切入场景已经进入了舞台
if ((! runningIsTransition) && _runningScene)
{
_runningScene->onEnter();
_runningScene->onEnterTransitionDidFinish();
}
}
这一次进入setNextScene,将过渡场景和切出场景退出舞台,并且执行cleanup,减少过渡场景的引用计数,在这一帧结束后会清理过渡场景和切出场景。然后设置_runningScene为新的切入场景,并且增加引用计数。然后从下一帧开始就运行新场景。
到此为止,整个场景切换的过程就说明白了。
PS:开源就是好,有了源代码原理就一清二楚~
【Cocos2d-x 3.x】 场景切换生命周期、背景音乐播放和场景切换原理与源码分析的更多相关文章
- cocos进阶教程(3)Cocos2d-x多场景切换生命周期
在多个场景切换时候,场景的生命周期会更加复杂.这一节我们介绍一下场景切换生命周期. 多个场景切换时候分为几种情况: 情况1,使用pushScene函数从实现HelloWorld场景进入Setting场 ...
- Cocos2d-x多场景切换生命周期
在多个场景切换时候,场景的生命周期会更加复杂.这一节我们介绍一下场景切换生命周期. 多个场景切换时候分为几种情况: 情况1,使用pushScene函数从实现HelloWorld场景进入Setting场 ...
- Cocos2d-x Lua中多场景切换生命周期
在多个场景切换时候,场景的生命周期会更加复杂.这一节我们介绍一下场景切换生命周期.多个场景切换时候分为几种情况:情况1,使用pushScene函数从实现GameScene场景进入SettingScen ...
- APIview的请求生命周期源码分析
目录 APIview的请求生命周期源码分析 请求模块 解析模块 全局配置解析器 局部配置解析器 响应模块 异常处理模块 重写异常处理函数 渲染模块 APIview的请求生命周期源码分析 Django项 ...
- VueJs 源码分析 ---(二)实力化生命周期,以及解析模版和监听数据变化
Vue 源码第二步 当前 Vue 的版本 V2.2.2 生命周期 相关介绍 我们可以从 setp1 中 去看到那张 vue 的生命周期图中看到,vue 的生命周期钩子. 具体的钩子时干什么的? 以及在 ...
- Linux进程调度与源码分析(二)——进程生命周期与task_struct进程结构体
1.进程生命周期 Linux操作系统属于多任务操作系统,系统中的每个进程能够分时复用CPU时间片,通过有效的进程调度策略实现多任务并行执行.而进程在被CPU调度运行,等待CPU资源分配以及等待外部事件 ...
- Vue.js 源码分析(九) 基础篇 生命周期详解
先来看看官网的介绍: 主要有八个生命周期,分别是: beforeCreate.created.beforeMount.mounted.beforeupdate.updated .beforeDes ...
- Django框架深入了解_01(Django请求生命周期、开发模式、cbv源码分析、restful规范、跨域、drf的安装及源码初识)
一.Django请求生命周期: 前端发出请求到后端,通过Django处理.响应返回给前端相关结果的过程 先进入实现了wsgi协议的web服务器--->进入django中间件--->路由f分 ...
- DRF框架(一)——restful接口规范、基于规范下使用原生django接口查询和增加、原生Django CBV请求生命周期源码分析、drf请求生命周期源码分析、请求模块request、渲染模块render
DRF框架 全称:django-rest framework 知识点 1.接口:什么是接口.restful接口规范 2.CBV生命周期源码 - 基于restful规范下的CBV接口 3.请求组件 ...
随机推荐
- NGUI屏幕自适应
NGUI确实是非常棒的一个做界面的插件,比起U3D自带的GUI要好很多,当然也有一些不好之处,毕竟什么都不可能那么完美. 最近在用Unity写游戏使用NGUI遇到了一个很多人都在遇到的问题,就是关于屏 ...
- gucci fake bags is usually really a sign of luxurious
As soon as the violent trembling from the planet, standing company, people will certainly need to st ...
- cheap gucci bags for women finish fashion jewellery has to move
Is certainly his dresser seem or dress creation process into video clip. Bus dropped???? Especially ...
- [转]显卡帝揭秘3D游戏画质特效
显卡帝揭秘3D游戏画质特效 近几年来,大量采用最新技术制作的大型3D游戏让大部分玩家都享受到了前所未有的游戏画质体验,同时在显卡硬件方面的技术革新也日新月异.对于经常玩游戏的玩家来说,可能对游戏画质提 ...
- tail 显示文件最后若干行内容
功能:tail命令可以输出文件的尾部内容,默认情况下它显示文件的最后十行.显示每个指定文件的最后10 行到标准输出.若指定了多于一个文件,程序会在每段输出的开始添加相应文件名作为头.如果不指定文件或文 ...
- What does it mean to “delegate to a sister class” via virtual inheritance?
Consider the following example: class Base { public: ; ; }; class Der1 : public virtual Base { publi ...
- 64位系统下找不到office 32位组件
如果系统式64位的,而装的是32位的office软件,在运行栏中输入命令:dcomcnfg,打开组件服务管理窗口,但是却发现找不到Microsoft Excel程序, 这主要是64位系统的问题,exc ...
- Spring中的IOC\DI\AOP等概念的简单学习
IoC(Inversion of Control,控制反转).这是spring的核心,贯穿始终, 所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系.Spr ...
- C# 字符串操作类
using System; using System.Collections.Generic; using System.Text; using System.Collections; using S ...
- angular 指令作用域 scope
转载自:https://segmentfault.com/a/1190000002773689 下面我们就来详细分析一下指令的作用域. 在这之前希望你对AngularJS的Directive有一定的了 ...