场景,对于游戏来说,如果进入下一关,那么需要将所有的节点重新部署,在同一个场景中完成一系列操作会十分繁琐,所以引入场景的概念
首先新建一个场景,用于切换,我们在src文件夹中添加一个.h.cpp的文件
image.png
image.png
其中Level_01.h为头文件,其中保存着以后创建的自定义变量,以及继承Scene的部分函数
一个基本的头文件应该包括以下内容

  1. #ifndef __LEVEL01_SCENE_H__
  2. #define __LEVEL01_SCENE_H__
  3. #include "cocos2d.h"
  4. class Level_01 : public cocos2d::Scene
  5. {
  6. public:
  7. static cocos2d::Scene* createScene();
  8. virtual bool init();
  9. // a selector callback
  10. void menuCloseCallback(cocos2d::Ref* pSender);
  11. // implement the "static create()" method manually
  12. CREATE_FUNC(Level_01);
  13. };
  14. #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"

跳转场景不同的方法


  1. replaceScene() 使用传入的场景替换当前场景来切换画面,当前场景被释放。这是切换场景时最常用的方法。(原先之前的场景会被销毁)
    Director::getInstance()->replaceScene(myScene);
    

  1. pushScene() 将当前运行中的场景暂停并压入到场景栈中,再将传入的场景设置为当前运行场景。只有存在正在运行的场景时才能调用该方法。(也就是说之前的场景并未销毁)
    Director::getInstance()->pushScene(myScene);
    

  1. 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); //将菜单放在默认场景的第一层上

image.png
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);

image.png
点击后成功跳转场景


关于不同跳转方式的区别

首先,我们在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,再点击返回,重复几次
image.pngimage.pngimage.pngimage.png
image.png
可以发现,即使场景跳转了,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()了
}

image.pngimage.png
image.png
可以发现,采用这种方法,返回原场景,数据并不保留


关于两种方法,可以看情况使用,一般情况下,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层
image.pngimage.png

image.png
注意,这里是旧版的写法,不过可以参考生成逻辑

场景切换

//启动第一个场景时调用,不能多次调用
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();    //层被清理时被调用

image.pngimage.png
注意:如果切换场景时使用replaceScene(),那么第二张图最后还需要调用 HelloWorld:cleanup();

上述两个是创建时,对于返回到原场景时,则有
image.png

电子书翻页效果实例代码参考

**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