【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.请求组件 ...
随机推荐
- 检索 COM 类工厂中 CLSID 为 {820280E0-8ADA-4582-A1D9-960A83CE8BB5} 的组件失败,原因是出现以下错误: 80040154 没有注册类 (异常来自 HRESULT:0x80040154 (REGDB_E_CLASSNOTREG))。
检索 COM 类工厂中 CLSID 为 {820280E0-8ADA-4582-A1D9-960A83CE8BB5} 的组件失败,原因是出现以下错误: 80040154 没有注册类 (异常来自 HRE ...
- Java接口中的方法
接口中可以含有变量和方法.但是,接口中的变量会被隐式地指定为public static final变量(并且只能是public static final变量,用private修饰会报编译错误),而方法 ...
- 一个自定义控件的Demo
里面包括Button.Checkbock.listview.popupwindow的自定义 import android.app.Activity; import android.content.In ...
- python模块httplib的使用
GET: #lianxi-httplib.HTTPConnection.request-get.py import httplib class HttpRequestGETTest(object): ...
- ajax 中boolean值技巧
// 利用判断 数据重复 function checkId () { var flag = true; $.ajax({ url: "", type: "post&quo ...
- String类和StringBuffer类的区别
首先,String和StringBuffer主要有2个区别: (1)String类对象为不可变对象,一旦你修改了String对象的值,隐性重新创建了一个新的对象,释放原String对象,StringB ...
- Amazon AWS 架设EC2服务器(datizi)fanqiang (更新手机VPN/L2TP设置)
今天用AWS在东京架设了一台服务器用来个人fanqiang.为什么用AWS呢,阿里云学生价9.9可以搭在香港,但是我的学制今年2月份在学信网上就到期了,腾讯云holy shit,我司AZURE据说员工 ...
- 2016-7-15(1)使用gulp构建一个项目
gulp是前端开发过程中自动构建项目的工具,相同作用的还有grunt.构建工具依 靠插件能够自动监测文件变化以及完成js/sass/less/html/image/css/coffee等文件的语法检查 ...
- MySQL命令行导出数据库
MySQL命令行导出数据库:1,进入MySQL目录下的bin文件夹:cd MySQL中到bin文件夹的目录如我输入的命令行:cd C:\Program Files\MySQL\MySQL Server ...
- demo和实际项目的距离
回家的路上想到一个很形象的类比,关于学生时期的实验(以及一些简单的demo)和实际工作项目的差别. 实现了同样的功能,比如要制作一把椅子,如果是简单的demo,那么就如同是给你了一个单独的房间,里面已 ...