1. 程式人生 > >Cocos2d-x入門之旅[4]場景

Cocos2d-x入門之旅[4]場景

我們之前講了場景圖(Scene Graph) 的概念,繼續之前你先要知道

  • 場景圖決定了場景內節點物件的渲染順序
  • 渲染時 z-order 值大的節點物件會後繪製,值小的節點物件先繪製

HelloWorld

你還記得HelloWorld場景是如何啟動的麼?回看我們工程裡的AppDelegate.cpp,滾到applicationDidFinishLaunching()的尾部:

// create a scene. it's an autorelease object
auto helloWorldscene = HelloWorld::createScene();
// run
director->runWithScene(helloWorldscene);

Ctrl+滑鼠左鍵 點選createScene()檢視定義,可以看到這個函式在HelloWorldScnen.h內宣告,在HelloWorldScnen.cpp內定義

// HelloWorldScnen.h
static cocos2d::Scene* createScene();

// HelloWorldScnen.cpp
Scene* HelloWorld::createScene()
{
    return HelloWorld::create();
}

我們可以通過該函式獲取一個HelloWorld場景物件

之後就是場景的初始化,選單,精靈等物件的Set都在這裡進行

bool HelloWorld::init()
{
    ...
}

最後我們看到一個回撥函式

void HelloWorld::menuCloseCallback(Ref* pSender)
{
    //Close the cocos2d-x game scene and quit the application
    Director::getInstance()->end();
}

C++基礎差的同學可能還不理解回撥的概念,但你只需要知道,這個函式實現了:HelloWorld場景內點選關閉按可鈕關閉視窗 的功能,就行了、

SecondScene

現在我們對著HelloWorld的程式碼來建立一個SecondScene

和HelloWorld一樣,首先我們需要一份SecondScene.h存放宣告,然後是一份SecondScene.cpp存放定義

注意VS內新建檔案時,一定要儲存到Class資料夾內,不然你是不能直接include“xxxxxx”的(萬惡的VS預設儲存路徑不是Class)

修改到專案目錄下的Class

SecondScene.h

首先是套一層巨集保護到頭尾

#ifndef __SECOND_SCENE_H__
#define __SECOND_SCENE_H__

#endif // __SECOND_SCENE_H__

然後

#include "cocos2d.h"

現在我開始我們要做一點點小變化,我們讓SecondScene繼承Layer而不是Scene

class SecondScene : public cocos2d::Layer {
public:
    static cocos2d::Scene* createScene();
    virtual bool init();
    CREATE_FUNC(SecondScene);
    //暫時不需要回調函式
};

實際上之前的cocos例項工程裡HelloWorld都是繼承自Layer的,不知為何現在改了,但是無妨,我們藉此機會介紹Layer和Scene的區別

SecondScene.cpp

首先要include我們之前寫好的SecondScene.h,為了方便我們也和HelloWorld一樣using namespace cocos2d

你可能看到了HelloWorld.cpp裡寫的是USING_NS_CC而不是using namespace cocos2d,但其實效果是一樣的

接下來我們定義creatSceen()函式,也和HelloWorld::createScene()略有不同

Scene* SecondScene::createScene() {
    //建立一個Scene類的物件scene
    auto scene = Scene::create();
    //建立一個SecondScene類的物件layer
    auto SecondScene = SecondScene::create();
    //把layer新增到scene裡
    scene->addChild(layer);
    //返回scene
    return scene;
}

理解起來很簡單:首先我們建立了一個Scene類的物件scene,然後建立了一個SecondScene類的物件layer(別忘了我們的SecondScene繼承自layer),再把layer新增到了scene裡,最後返回我們的scene

之後就可以開始寫初始化函數了

bool SecondScene::init() {
    auto visibleSize = Director::getInstance()->getVisibleSize();
    Vec2 origin = Director::getInstance()->getVisibleOrigin();

    Label* label = Label::create("Second Test", "fonts/Marker Felt.ttf", 24);
    label->setPosition(
        Vec2(
            origin.x + visibleSize.width / 2,
            origin.y + visibleSize.height - label->getContentSize().height
        )
    );
    
    this->addChild(label);  // 預設z-order=0

    return true;
}

我們不用加太多東西,加入一個label讓自己知道這是SecondScene場景就行

現在我們去程式的入口,AppDelegate.cpp裡看看,還記得啟動HelloWorld場景的那兩行程式碼麼,我們改成啟動SecondScene場景:

(記得先要加入#include "SecondScene.h"到AppDelegate.cpp裡)

// create a scene. it's an autorelease object
auto helloWorldscene = HelloWorld::createScene();
// run
director->runWithScene(helloWorldscene);

改成

auto secondScene = SecondScene::createScene();
director->runWithScene(secondScene);

執行測試:

沒有問題

場景切換

接下來我們把AppDelegate.cpp還原回去(Crtl+Z),讓程式執行時還是從HelloWorld場景開始,然後嘗試使用回撥函式切換到SecondScene

由於我們要在HelloWorld切換到Second,所以我們需要在HelloWorld場景裡新增一個回撥事件,剛好HelloWolrd裡就有一個關閉按鈕的回撥,我們改改程式碼就行(這裡重點是演示如何切換,真的不是我懶)

追蹤到HelloWorld::menuCloseCallback()

Director::getInstance()->END改成

Director::getInstance()->replaceScene(
    SecondScene::createScene()
);

執行測試,不出意外再點選HelloWorld右下角的關閉按鈕,就會一瞬間切換到Second

切換動畫

Cocos內建了很多切換場景的動畫,比如

Director::getInstance()->replaceScene(
    TransitionSlideInT::create(
        3.0f, SecondScene::createScene()
    )
);

執行,Second會在3秒內從上平滑切換掉HelloWorld

別的切換動畫就不贅述了,和Actions又異曲同工之妙,各自己試試吧

場景棧

我們之前都是在使用replaceScene進行場景切換,replaceScene會使前一個場景被釋放(簡單來說就是刪掉了,不再佔用記憶體,想找回來只能重新建立一個),節省了記憶體資源,但有時我們不希望場景被釋放怎麼辦呢

Cocos還提供了 推進pushScene()和彈出popScene兩種方法,把一系列場景儲存到棧內,按需彈出(釋放)和推進新場景

棧是一種資料結構,你可以簡單理解為一個彈匣,對棧有兩種操作:

  1. 入棧(push),裝入子彈
  2. 出棧(pop),射出子彈

子彈總是最上面的先被射出(pop時棧頂的元素最先出棧),也就是“先進後出”

新加入場景時使用pushScene(),新舊場景就會被存入場景棧,新在上舊在下(也就是說舊的場景沒有釋放),popScene會將新的場景釋放,舊的場景就被彈了上來原來的位子,顯示舊場景

你可以在場景初始化函式內寫一段等待x秒的程式碼(換個思路,可以寫一個計時器),驗證pop後顯示的舊場景不是新建立的:

bool HelloWorld::init()
{
    ... 
        
    for(int i = 0; i <19999999; i++)// 耗時操作   
        
    ...
}

發揮你的奇思妙想,還有什麼方法,自己實驗一下

層和場景

這個好理解,想想你玩過的2d遊戲,或者Photoshop的圖層介面,一個場景裡可以有好幾個層

場景內建立層很簡單:

Layer* layer2 = Layer::create();

當然一個層必須新增到場景內才會生效,層無法離開場景獨立存在

之後的學習中我們會進一步體會到層的作