【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.请求组件 ...
随机推荐
- 使用Quartz.NET进行任务调度管理
1.Quartz.NET 介绍 Quartz.NET是一个开源的作业调度框架,是OpenSymphony 的 Quartz API的.NET移植,它用C#写成,可用于winform和asp.net应用 ...
- 第三篇bootstrap 网格基础
Bootstrap 提供了一套响应式.移动设备优先的流式网格系统,随着屏幕或视口(viewport)尺寸的增加,系统会自动分为最多12列. 网格系统类似一个表格,有行和列,它必须放置在一个类型设置为c ...
- firefox,跨域ajax 调用方法
在A站点ajax 调用B站的页面(方法)时, 使用post,且dataType类型为jsonp 有时在IE会有No Transport的错误提示,请加 jQuery.support.cors = tr ...
- Theoretical comparison between the Gini Index and Information Gain criteria
Knowledge Discovery in Databases (KDD) is an active and important research area with the promise for ...
- JavaScript测试工具比较: QUnit, Jasmine, and Mocha
1. QUnit A JavaScript Unit Testing framework. QUnit is a powerful, easy-to-use JavaScript unit testi ...
- .NET微信模拟登录及{base_resp:{ret:-4,err_msg:nvalid referrer}}的解决办法
12年的时候写了些关于微信开发的内容,当时看好这个东西,可惜当时腾讯开放的权限太少,之后的一年多时间没有太关注了. 现在又要重新开始微信开发的阵容了,微信只是个入口,微网站才是趋势. 我是个水货,所以 ...
- url路由、模板语言、ajax、用django框架创建表
1.后台管理的左侧菜单,默认只有第一个页签下面的选项是显示的,点了别的页签再显示别的页签下面的选项,问题是:点了任何菜单的选项后,左侧菜单又成了第一个页签的选项显示,别的页签隐藏,也就是左侧的菜单刷新 ...
- net iis 部署中出现的问题及解决方案
1.HTTP500.21 错误 解决方法:重新注册asp.net C:\Windows\Microsoft.NET\Framework\v2.0.50727\aspnet_regiis.exe –i ...
- wxPYTHON图形化软件开发(一)---LOMO工具箱
最近学了wxPYTHON,这次就做了一个工具箱软件练手,软件主要是包含各种小工具,目前想到的有密码管理器,日记本,记账本,今天还看到一个网页浏览器,也可能加进来.目前实现的是密码管理器 软件GUI部分 ...
- liunx中字符驱动编写的简单模板
下面是关于字符驱动两个程序,主要是说明驱动编写的思想,理解驱动是怎么一步一步被实现的. 驱动的第一个实现程序,是相对于裸机编程的,主要是体会一下驱动编程思想: cdev.h: 所包含的头文件 #ifn ...