场景,对于游戏来说,如果进入下一关,那么需要将所有的节点重新部署,在同一个场景中完成一系列操作会十分繁琐,所以引入场景的概念
首先新建一个场景,用于切换,我们在src
文件夹中添加一个.h
和.cpp
的文件
其中Level_01.h
为头文件,其中保存着以后创建的自定义变量,以及继承Scene
的部分函数
一个基本的头文件应该包括以下内容
#ifndef __LEVEL01_SCENE_H__
#define __LEVEL01_SCENE_H__
#include "cocos2d.h"
class Level_01 : public cocos2d::Scene
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
// a selector callback
void menuCloseCallback(cocos2d::Ref* pSender);
// implement the "static create()" method manually
CREATE_FUNC(Level_01);
};
#endif // __LEVEL01_SCENE_H__#pragma once
而Level_01.cpp
为用户用来写具体代码的位置
一个基本的cocos的类应该包括以下内容
#include "Level_01.h"
#include "SimpleAudioEngine.h"
USING_NS_CC;
Scene* Level_01::createScene()
{
return Level_01::create();
}
// Print useful error message instead of segfaulting when files are not there.
static void problemLoading(const char* filename)
{
printf("Error while loading: %s\n", filename);
printf("Depending on how you compiled you might have to add 'Resources/' in front of filenames in Level_01Scene.cpp\n");
}
// on "init" you need to initialize your instance
bool Level_01::init()
{
if (!Scene::init())
{
return false;
}
// 这两个参数一般用来计算相对位置的,比较有用,建议保留
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
return true;
}
void Level_01::menuCloseCallback(Ref* pSender)
{
Director::getInstance()->end();
}
场景之间的跳转
首先,我们应当注意,即使我们已经创建了.cpp
的文件,但是这对C++
来说,只是个类文件,对cocos引擎来说,并不是一个场景,我们需要在准备加载成场景之前,使用如下代码来创建
auto level_01_Scene = Level_01::createScene(); //注意,因为引用了Level_01这个类,所以,在别的场景中调用到Level_01,一定要记得在别的场景类中,增加Level_01.h这个头文件,用来引入create()函数
//例如:
#include "Level_01.h"
#include "HelloWorldScene.h"
#include "SimpleAudioEngine.h"
跳转场景不同的方法
- replaceScene() 使用传入的场景替换当前场景来切换画面,当前场景被释放。这是切换场景时最常用的方法。(原先之前的场景会被销毁)
Director::getInstance()->replaceScene(myScene);
- pushScene() 将当前运行中的场景暂停并压入到场景栈中,再将传入的场景设置为当前运行场景。只有存在正在运行的场景时才能调用该方法。(也就是说之前的场景并未销毁)
Director::getInstance()->pushScene(myScene);
- popScene() 释放当前场景,再从场景栈中弹出栈顶的场景,并将其设置为当前运行场景。如果栈为空,直接结束应用。
Director::getInstance()->popScene();
实际操作
首先,我们假设在默认的HelloWorldScene
中,有个操作需要进入到关卡01
,也就是说,需要从默认场景跳转到Level_01
,那么,首先,我们需要在HelloWorldScene.h
这个头文件中,声明一个函数用来跳转(理论上,不声明函数也可以,只要有上方的跳转语句,但是为了代码解耦,更适合的方法是增加函数)
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
class HelloWorld : public cocos2d::Scene
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
void menuCloseCallback(cocos2d::Ref* pSender);
//该函数用于跳转场景
void goToLevel_01();
CREATE_FUNC(HelloWorld);
};
#endif // __HELLOWORLD_SCENE_H__
接下来,我们需要在HelloWorldScene.cpp
中,定义这个函数体
//注意,这段函数是写在.cpp文件中的,请不要写在头文件中,.h头文件只用来声明函数
void HelloWorld::goToLevel_01() {
auto level_01_Scene = Level_01::createScene(); //利用Level_01类创建一个场景
Director::getInstance()->replaceScene(level_01_Scene); //替换原场景方式更换场景
return;
}
现在,我们有了跳转场景的方式,但是需要有个方式来触发这个函数,我们可以写一个菜单文字按钮(MenuItemFont)
,关于这个详细的回调用法,会在之后介绍,我们现在只需要知道,这段代码可以在用户点击时,调用我们自定义的函数
// 本段代码放置在HelloWorldScene.cpp中,准备在该场景跳往Level_01场景
auto changeScene = MenuItemFont::create("Go To The Level_01", CC_CALLBACK_0(HelloWorld::goToLevel_01, this));//一个用户可以点击的菜单按钮
auto myMenu = Menu::create(changeScene, NULL);//菜单按钮需要挂载在菜单下
myMenu->setPosition(Vec2(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y));
this->addChild(myMenu, 1); //将菜单放在默认场景的第一层上
HelloWorld场景
现在,我们在Level_01.cpp中,加入一个标签(Label)
节点,用来观察跳转之后的效果
//关于Label节点,会在之后补充,这里简单说明三个参数为,标签的内容,字体文件,字号大小
auto label = Label::createWithTTF("This is Level_01", "fonts/Marker Felt.ttf", 24);
label->setPosition(Vec2(origin.x + visibleSize.width / 2,
origin.y + visibleSize.height / 2));
this->addChild(label, 1);
点击后成功跳转场景
关于不同跳转方式的区别
首先,我们在HelloWorldScene.h
中,增加两个变量
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
#include "SimpleAudioEngine.h"
#include "cocos-ext.h" //为了使用户自己增加的变量可以在.cpp中,使用,需要增加次头文件
using namespace std;
using namespace CocosDenshion; //并且增加CocosDenshion的命名空间
USING_NS_CC_EXT; //别忘了补上这一句
USING_NS_CC;
class HelloWorld : public cocos2d::Scene
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
//增加的变量,由于VS遵守的代码定义较为严格,所以需要注意定义位置,尽量先定义变量
int clickCount; //普通int变量,用于计数
Label* clickLabel; //Cocos中的Label类,可以显示文字
//该函数用于跳转场景
void goToLevel_01();
void menuCloseCallback(cocos2d::Ref* pSender);
CREATE_FUNC(HelloWorld);
};
#endif // __HELLOWORLD_SCENE_H__
然后我们在HelloWorldScene.cpp
中,增加这个Label
,别忘了挂载到场景层中
clickLabel = Label::createWithTTF("Click time: 0", "fonts/Marker Felt.ttf", 20);
clickLabel->setPosition(Vec2(visibleSize.width / 2 + origin.x, visibleSize.height / 2 - 75 ));
this->addChild(clickLabel, 2);
现在,我们更新goToLevel_01()
函数,此时,我们选择**pushScene()**
的方法
void HelloWorld::goToLevel_01() {
clickCount++; //我们让每次点击的时候,计数参数++
string clickStr = "Click time: " + to_string(clickCount);
clickLabel->setString(clickStr); //更新当前点击的次数
auto level_01_Scene = Level_01::create();
Director::getInstance()->pushScene(level_01_Scene); //注意,已经更换成了pushScene的方式
return;
}
然后,为了能在Level_01中返回,我们采用同样的方法,增加一个返回菜单按钮,以及相关函数
bool Level_01::init()
{
//原本存在的部分默认代码已省略
auto changeScene = MenuItemFont::create("Go Back", CC_CALLBACK_0(Level_01::backToHelleWorldScene, this));
auto myMenu = Menu::create(changeScene, NULL);
myMenu->setPosition(Vec2(visibleSize.width / 2 + origin.x, visibleSize.height / 2 - 100));
this->addChild(myMenu, 1);
return true;
}
//别忘了该函数需要在Level_01.h的头文件中声明
void Level_01::backToHelleWorldScene() {
Director::getInstance()->popScene(); //pop掉当前的场景
}
现在,我们点击跳转到Level_01
,再点击返回,重复几次
可以发现,即使场景跳转了,pushScene()
方法也能保留原场景的所有节点数据
但如果我们使用replaceScene()的方法
Director::getInstance()->replaceScene(level_01_Scene);
其余代码不做修改,我们会发现,进入Level_01
之后,再次点击Go Back
按钮,我们的窗口会直接关闭,说明目前栈中,只剩下Level_01
的场景,执行popScene()
之后,将会直接退出,并且也说明,原先的场景已经被销毁了,里面的数据以及不存在了。
如果我们现在仍想返回,那么可以更改Level_01
的返回函数
void Level_01::backToHelleWorldScene() {
auto scene = HelloWorld::createScene();
Director::getInstance()->replaceScene(scene); //注意,现在已经不能使用popScene()了
}
可以发现,采用这种方法,返回原场景,数据并不保留
关于两种方法,可以看情况使用,一般情况下,replace
性能会优于push
,如果没有特殊需求的话,请尽量使用replace
场景切换的特效
在场景切换的过程中,你可以添加一些效果:
auto myScene = Scene::createScene();
// 淡化
Director::getInstance()->replaceScene(TransitionFade::create(0.5, myScene, Color3B(0,255,255)));
// 反转
Director::getInstance()->replaceScene(TransitionFlipX::create(2, myScene));
// 滑入
Director::getInstance()->replaceScene(TransitionSlideInT::create(1, myScene) );
其余效果
//慢慢淡化到另一场景
TransitionCrossFade::create(时间,目标场景);
//本场景变暗消失后另一场景慢慢出现
TransitionFade::create(时间,目标场景);
//本场景右上角到左下角方块消失到另一场景
TransitionFadeBL::create(时间,目标场景);
//本场景从上到下横条消失到另一场景
TransitionFadeDown::create(时间,目标场景);
//本场景左下角到右上角方块消失到另一场景
TransitionFadeTR::create(时间,目标场景);
//本场景从下到上横条消失到另一场景
TransitionFadeUp::create(时间,目标场景);
//本场景翻转消失到另一场景(斜上方)
TransitionFlipAngular::create(时间,目标场景,样式 );
//本场景翻转消失到另一场景(X轴)
TransitionFlipX::create(时间,目标场景,样式);
//本场景翻转消失到另一场景(Y轴)
TransitionFlipY::create(时间,目标场景);
//本场景跳动消失后另一场景跳动出现
TransitionJumpZoom::create(时间,目标场景);
//另一场景由整体从下面出现
TransitionMoveInB::create(时间,目标场景);
//另一场景由整体从左面出现
TransitionMoveInL::create(时间,目标场景);
//另一场景由整体从上面出现
TransitionMoveInT::create(时间,目标场景);
//另一场景由整体从右面出现
TransitionMoveInR::create(时间,目标场景);
//翻页切换,bool为true是向前翻。
TransitionPageTurn::create(时间,目标场景,bool);
//本场景从左到右消失同时另一场景出现
TransitionProgressHorizontal::create(时间,目标场景);
//本场景从中间到四周消失同时另一场景出现
TransitionProgressInOut::create(时间,目标场景);
//本场景从四周到中间消失同时另一场景出现
TransitionProgressOutIn::create(时间,目标场景);
//本场景逆时针消失到另一场景
TransitionProgressRadialCCW::create(时间,目标场景);
//本场景顺时针消失到另一场景
TransitionProgressRadialCW::create(时间,目标场景);
//本场景从上到下消失同时另一场景出现
TransitionProgressVertical::create(时间,目标场景);
//本场景旋转消失后另一场景旋转出现
TransitionRotoZoom::create(时间,目标场景);
//本场景缩小切换到另一场景放大
TransitionShrinkGrow::create(时间,目标场景);
//本场景向上滑动到另一场景
TransitionSlideInB::create(时间,目标场景);
//本场景向右滑动到另一场景
TransitionSlideInL::create(时间,目标场景);
//本场景向左滑动到另一场景
TransitionSlideInR::create(时间,目标场景);
//本场景向下滑动到另一场景
TransitionSlideInT::create(时间,目标场景);
//本场景三矩形上下消失后另一场景三矩形上下出现
TransitionSplitCols::create(时间,目标场景);
//本场景三矩形左右消失后另一场景三矩形左右出现
TransitionSplitRows::create(时间,目标场景);
//本场景小方块消失到另一场景
TransitionTurnOffTiles::create(时间,目标场景);
//本场景翻转消失到另一场景(斜上方)
TransitionZoomFlipAngular::create(时间,目标场景,样式);
//本场景翻转消失到另一场景(X轴)
TransitionZoomFlipX::create(时间,目标场景,样式);
//本场景翻转消失到另一场景(Y轴)
TransitionZoomFlipY::create(时间,目标场景,样式);
层(Layer)与场景(Scene)的关系
新版本中,一个场景中默认包含一个画布Canvas层
场景切换
//启动第一个场景时调用,不能多次调用
void runWithScene(Scene* scene);
//替代场景
void replaceScene(Scene* scene);
//加载一个新场景
void pushScene(Scene* scene);
//弹出一个场景
void popScene();
//弹出多级场景,回到根场景
void popToRootScene();
生命周期
以下函数将在特定时间被自动调用,但是注意,每个函数都是虚函数(头文件中声明时需要增加virtual)
bool init(); //初始化时被调用
void onEnter(); //进入层时被调用
void onEnterTransitionDidFinish(); //过渡动画结束时才播放
void onExit(); //退出层时被调用
void onExitTransitionDidStart(); //退出层,并且过渡动画开始一瞬间时调用
void cleanup(); //层被清理时被调用
注意:如果切换场景时使用replaceScene(),那么第二张图最后还需要调用 HelloWorld:cleanup();
上述两个是创建时,对于返回到原场景时,则有
电子书翻页效果实例代码参考
**Page.h**
#pragma once
#ifdef WIN32
#pragma execution_character_set("utf-8")
#ifndef __PAGE_SCENE_H__
#define __PAGE_SCENE_H__
#include "cocos2d.h"
#include <string>
using namespace std;
class Page : public cocos2d::Scene
{
public:
string pageContent;//文件内容
char filePath[64]; //文件路径(由于file指针的地址不支持string变量,所以用普通字符串就好)
int pageNumber;//当前文件的页码
int CR_limited;//最大行数限制,其实数的是回车符的个数,比如一页显示5行之内的,参数可以在init()中初始化
static cocos2d::Scene* createScene();
string getThisPageContent(string content, int pageNum); //获取全部内容中,当前页面的一部分内容,需要字符串切割
void initContent(char path[64], int pageNum); //我们让每个Page场景都能自己初始化内容
//一下三个是操作的回调函数
void nextPage();
void prePage();
void backToMenu();
virtual bool init();
CREATE_FUNC(Page);
};
#endif // __PAGE_SCENE_H__
#endif
**Page.cpp**
#include "Page.h" //注意引用头部文件
#include "SimpleAudioEngine.h"
USING_NS_CC;
Scene* Page::createScene()
{
return Page::create();
}
bool Page::init()
{
if (!Scene::init()){
return false;
}
//场景的相关大小参数
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
this->CR_limited = 5; //页数限制为5
auto bg = Sprite::create("page_background.jpg"); //创建一个背景,可以使用你自己的文件
bg->setScale(1.5f);
bg->setPosition(Vec2(origin.x + visibleSize.width / 2, origin.y + visibleSize.height / 2));
this->addChild(bg, 0);
return true;
}
//初始化场景中的内容
void Page::initContent(char path[64], int pageNum) {
//相关参数
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
this->pageNumber = pageNum; //初始化我们该场景的相关数据
strcpy(filePath, path);// 将路径保存到我们自己的filePate变量中,strcpy可以复制字符串
FileUtils* file = FileUtils::getInstance(); //这是一个文件指针,不是c++的那个,而是cocos为我们封装好的file类
//获取我们所有内容的指定界面的内容
pageContent = getThisPageContent(file->getStringFromFile(filePath),pageNumber);
//由于获取的内容是string变量,而不是简单的字符串,createWithTTF我本地会报错,用create转换就好
auto pageLabel = Label::create(pageContent, "fonts\TencentSans-W7.ttf", 24);
pageLabel->setColor(Color3B::BLACK); //指定颜色
pageLabel->setPosition(Vec2(origin.x + visibleSize.width / 2, origin.y + visibleSize.height / 2));
this->addChild(pageLabel,1);
//左下角操作按钮的回调函数
auto nextButton = MenuItemImage::create("right_arrow.png","right_arrow.png", CC_CALLBACK_0(Page::nextPage, this));
nextButton->setPosition(Vec2(0, 0));
auto preButton = MenuItemImage::create("left_arrow.png", "left_arrow.png", CC_CALLBACK_0(Page::prePage, this));
preButton->setPosition(Vec2(-100, 0));
auto returnButton = MenuItemImage::create("return_arrow.png", "return_arrow.png", CC_CALLBACK_0(Page::backToMenu, this));
returnButton->setPosition(Vec2(100, 0));
auto myMenu = Menu::create(nextButton, preButton, returnButton, NULL);
//注意,相关代码我并没有写分辨率适配的写法,请注意分辨率跟位置的计算关系
myMenu->setPosition(Vec2(visibleSize.width-150, 60));
this->addChild(myMenu, 2);
}
void Page::nextPage() {
this->pageNumber++; //页码++
auto scene = Page::create();
scene->initContent(this->filePath, this->pageNumber);
Director::getInstance()->replaceScene(TransitionPageTurn::create(1.0f, scene, false));
}
void Page::prePage() {
this->pageNumber--; //页码--
auto scene = Page::create();
scene->initContent(this->filePath, this->pageNumber);
Director::getInstance()->replaceScene(TransitionPageTurn::create(1.0f, scene, true));
}
void Page::backToMenu() {
Director::getInstance()->popScene(); //直接把page场景pop掉,所以其实可以发现上面都是replace,这里是pop
}
string Page::getThisPageContent(string content, int pageNum) {
if (pageNum <= 0) {
return "这里已经是文件最开始了"; //传入页码小于1时中止,并返回对应string
}
int CR_cnt = 0; //回车符的计数变量
int CR_begin = (pageNum - 1)*(this->CR_limited);
//开始位置的回车符个数,比如,第2页,每页5行,那么开始的回车符就是5,数到5个回车符之后,就开始保存接下来的内容
int CR_end = (pageNum)*(this->CR_limited); //终止位置的回车符个数
int CR_beginJudge = 0; //回车符是否开始的计数判断
int startPosition = -1; //开始位置,默认为-1,表示没有找到开始位置
int length = 0; //切割内容长度
//log("当前页数为:%d", pageNum);
for(int i = 0; i < content.length() ; i++) {
if (content[i] == '\n') { //计数
CR_beginJudge++;
CR_cnt++;
}
if (CR_beginJudge >= CR_begin) //当达到begin开始处时,开始计算长度
length++;
if (startPosition == -1 && CR_beginJudge >= CR_begin) //当到达begin开始处时,并且startPositon还为-1时,记录下标
startPosition = i;
if (CR_cnt >= CR_end) //当到达结尾回车符时退出
break;
}
if(startPosition!=-1)
return content.substr(startPosition, length);
else return "内容已经到头";
//return content;
}
**HelloWorldScene.h**
#ifndef __HELLOWORLD_SCENE_H__
#define __HELLOWORLD_SCENE_H__
#include "cocos2d.h"
#include "Page.h" //注意把Page.h引入到我们的HelloWorldScene中来
class HelloWorld : public cocos2d::Scene
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
CREATE_FUNC(HelloWorld);
};
#endif // __HELLOWORLD_SCENE_H__
HelloWorldScene.cpp
#ifdef WIN32
#pragma execution_character_set("utf-8") //如果你的vs中输入中文字符,但最终出错,可以试试加上该段代码,注意,需要在尾部还要有个#endif
#include "HelloWorldScene.h"
#include "SimpleAudioEngine.h"
USING_NS_CC;
Scene* HelloWorld::createScene()
{
return HelloWorld::create();
}
bool HelloWorld::init()
{
if ( !Scene::init() ){
return false;
}
auto visibleSize = Director::getInstance()->getVisibleSize();
Vec2 origin = Director::getInstance()->getVisibleOrigin();
//菜单Item需要更改字体时,使用该代码
MenuItemFont::setFontName("fonts\TencentSans-W7.ttf");
//匿名函数
auto startButton = MenuItemFont::create("开始阅读", [](Ref* sender) {
auto firstPageScene = Page::create(); //这次,我们不用createScene,因为这个获得的是个Scene,但是我们还需要调用里面的initContent函数,所以需要一个普通节点,于是使用create
firstPageScene->initContent("myBook.txt",1);
Director::getInstance()->pushScene(firstPageScene); //如果传入一个create的节点,pushScene会进行转换成Scene,继承与转换关系不做赘述
});//一个用户可以点击的菜单按钮
auto myMenu = Menu::create(startButton, NULL);//菜单按钮需要挂载在菜单下
myMenu->setPosition(Vec2(visibleSize.width / 2 + origin.x, visibleSize.height / 2 + origin.y));
this->addChild(myMenu, 1); //将菜单放在默认场景的第一层上
return true;
}
#endif //加上的头部那段代码,需要在尾部补上注释#endif