閱讀121 返回首頁    go 阿裏雲 go 技術社區[雲棲]


使用Cocos2d-x製作三消類遊戲Sushi Crush(第一部分)

https://github.com/cocos2d/cocos-docs/blob/master/tutorial/how-to-make-a-sushicrash-game-by-cocos2dx/part1/zh.md

歡迎大家斧正錯誤,提交PR。

視頻教程地址:[教主視頻教程]使用Cocos2d-x製作三消類遊戲Sushi Crush(第一部分)

原創: 任珊

一直以來,消除類遊戲以其簡單明快的節奏、濃厚的趣味性和智慧性而被廣大玩家所喜愛。其分支三消類遊戲更是倍受廣大遊戲玩家的推崇,最近的CandyCrush、開心消消樂、天天愛消除等三消遊戲火的那是一個一塌煳塗啊。下麵,我們就將和大家一起探討一下如何製作一款屬於自己的仿CandyCrush三消遊戲——“SushiCrush”。

項目介紹

引擎版本:本教程使用當前最新版本的 Cocos2d-x-3.0rc0 引擎。

效果圖: 

遊戲框架:為了使項目的代碼結構清晰,好的前期規劃是很有必要的,下圖是該節遊戲工程的主要類結構,先從整體看一下,項目的組織結構,然後我們會對其內部實現做些解說。

其中:

  • AppDelegate.cpp:程序入口,分辨率適配設置。
  • PlayLayer.cpp:遊戲場景層,遊戲中所有的Node節點都在其內,它同時負責管理SushiSprite。
  • SushiSprite.cpp:壽司精靈層,即遊戲中可被消除和操作的對象。

在本章節教程中,我們將主要完成以下功能:

  • 分辨率適配

  • 壽司的創建、布局和下落

程序入口

AppDelegate.cpp是Cocos2d-x自動生成的一個類,它控製著遊戲的生命周期,是Cocos2d-x遊戲的通用入口文件,類似於一般 Windows 工程中主函數所在的文件。打開AppDelegate.cpp文件,在applicationDidFinishLaunching()函數中我們可以設置第一個啟動的遊戲場景:

auto scene = PlayLayer::createScene();
director->runWithScene(scene);

分辨率適配

為了能更好的適應各種分辨率大小和屏幕寬高比的移動終端設備,遊戲的開始,我們還是先來看看分辨率的適配設置。 

打開AppDelegate.cpp文件,在applicationDidFinishLaunching函數裏麵添加如下代碼,以便我們的遊戲,能夠更好的適應不同的運行環境。

// 分辨率適配

glview->setDesignResolutionSize(320.0, 480.0, ResolutionPolicy::FIXED_WIDTH);
std::vector<std::string> searchPath;
searchPath.push_back("w640");
CCFileUtils::getInstance()->setSearchPaths(searchPath);
director->setContentScaleFactor(640.0 / 320.0);

設計分辨率是通過setDesignResolutionSize(width, height, resolutionPolicy)方法來設置的,第一,二個參數分別是設計分辨率的寬度和高度,第三個參數是你想要的模式。這裏設置的分辨率大小是開發時為基準的屏幕分辨率大小。

模式有五種:

  • EXACT_FIT 整個遊戲內容都會在屏幕內可見,並且不用提供比例係數。x,y會被拉伸,使內容鋪滿屏幕,所以可能會出現形變,所有的應用程序看起來可能會是拉伸或者壓縮的。

  • NO_BORDER 一個方向鋪滿屏幕,另外一個方向超出屏幕,不會變形,但是可能有一些裁剪。

  • SHOW_ALL 該模式會盡可能按原始寬高比放大遊戲世界,同時使得遊戲內容全部可見。內容不會形變,不過可能會出現兩條黑邊,即屏幕中會有留白。

  • FIXED_WIDTH 該模式會橫向放大遊戲世界內的內容以適應屏幕的寬度,縱向按原始寬高比放大。
  • FIXED_HEIGHT 與上一中模式相反。

setSearchPaths()方法設置資源搜索路徑,這裏w640是搜索的文件夾名。
setContentScaleFactor()方法設置內容縮放因子,顧名思義,就是設置整個遊戲內容放大或者縮小的比例係數。

更多分辨率適配細節,參考《Cocos2d-x 多分辨率適配完全解析》

壽司精靈 SushiSprite

SushiSprite類繼承於Sprite,用來創建單個的壽司精靈,下麵是它的類定義:

class SushiSprite :  public Sprite
{
public:
    static SushiSprite *create(int row, int col);//隨機創建不同種類的壽司精靈
    static float getContentWidth();//得到精靈圖片的寬(精靈圖片為正方形,寬等於高),方便後麵計算精靈在場景中的位置。
    //暫時沒用到,在下一節的教程中我們將用來定位行列。
    CC_SYNTHESIZE(int, m_row, Row);
    CC_SYNTHESIZE(int, m_col, Col);
    CC_SYNTHESIZE(int, m_imgIndex, ImgIndex);
};

CC_SYNTHESIZE的定義如下:

#define CC_SYNTHESIZE(varType, varName, funName)\
protected: varType varName;\
public: virtual varType get##funName(void) const { return varName; }\
public: virtual void set##funName(varType var){ varName = var; }

CC_SYNTHESIZE的作用是定義一個保護型的變量,並聲明一個getfunName函數和setfunName函數,你可以用getfunName函數得到變量的值,用setfunName函數設置變量得值。 

參數varType是變量的類型,m_row是變量名,funName是要聲明函數的“後半截”名字,如:CC_SYNTHESIZE(int, m_row, Row)的作用是聲明一個int型的m_row變量和一個函數名為getRow以及setRow的函數。

壽司精靈的創建:

SushiSprite *SushiSprite::create(int row, int col)
{
    SushiSprite *sushi = new SushiSprite();
    sushi->m_row = row;
    sushi->m_col = col;
    sushi->m_imgIndex = arc4random() % TOTAL_SUSHI;
    sushi->initWithSpriteFrameName(sushiNormal[sushi->m_imgIndex]);
    sushi->autorelease();
    return sushi;
}   

arc4random()方法獲取隨機數比較精確,並且不需要生成隨即種子,arc4random() % TOTAL_SUSHI是獲得 0 ~ TOTAL_SUSHI - 1之間的整數。

遊戲主場景 PlayLayer

PlayLayer是遊戲的主場景,同時也負責管理SushiSprite,在該章教程中,PlayLayer裏我們隻實現了壽司的布局和它的下落。下麵我們會詳細講解,先看看PlayLayer的初始化:

bool PlayLayer::init()
{
    if (!Layer::init()) {
        return false;
    }    
    // 創建遊戲背景
    Size winSize = Director::getInstance()->getWinSize();
    auto background = Sprite::create("background.png");
    background->setAnchorPoint(Point(0, 1));
    background->setPosition(Point(0, winSize.height));
    this->addChild(background);

    // 初始化壽司精靈表單
    SpriteFrameCache::getInstance()->addSpriteFramesWithFile("sushi.plist");
    spriteSheet = SpriteBatchNode::create("sushi.pvr.ccz");
    addChild(spriteSheet);

    // 初始化矩陣的寬和高,MATRIX_WIDTH、MATRIX_HEIGHT通過宏定義
    m_width = MATRIX_WIDTH;
    m_height = MATRIX_HEIGHT;

    // 初始化壽司矩陣左下角的點
    m_matrixLeftBottomX = (winSize.width - SushiSprite::getContentWidth() * m_width - (m_width - 1) * SUSHI_GAP) / 2;
    m_matrixLeftBottomY = (winSize.height - SushiSprite::getContentWidth() * m_height - (m_height - 1) * SUSHI_GAP) / 2;

    // 初始化數組
    int arraySize = sizeof(SushiSprite *) * m_width * m_height;//數組尺寸大小
    m_matrix = (SushiSprite **)malloc(arraySize);//為m_matrix分配內存,這裏m_matrix是指向指針類型的指針,其定義為:SushiSprite **m_matrix。所以可以理解為m_matrix是一個指針數組
    memset((void*)m_matrix, 0, arraySize);//初始化數組m_matrix

    //初始化壽司矩陣
    initMatrix();
    return true;
}

上麵的初始化函數中有以下幾點需要說明一下:

  • plist 和 pvr.ccz文件
  • SpriteFrameCache和SpriteBatchNode
  • 壽司矩陣起始點的初始化
  • 壽司精靈如何布局

1. plist 和 pvr.ccz文件

遊戲中一般會有比較多的圖片資源,如果有很多很多的資源,那加載這些資源是非常費時間和內存的,所以如何高效地使用圖片資源對於一款遊戲是相當重要的。在Cocos2d中,我們一般會將圖片資源打包成一張大圖,這樣加載圖片不僅節省了空間,而且還提升了速度。

在Cocos2d-x引擎開發中,常又到的兩種圖片編輯打包工具,即 Zwoptex 和 Texturepacker。我們的教程裏用到的是Texturepacker,你可以到它的官方網站下載並安裝。

打開Texturepacker,界麵如下圖所示。 

Texturepacker工具的每個設置項都給出了相應的提示信息,這裏就不一一介紹。接下來,你就可以根據提示把本章節所需要的6張壽司圖片資源打包了。導出的時候勾選 output-》Texture format-》zlib compr.PVR,然後單擊Publish按鈕進行導出,這樣就會導出我們需要的plist 和 pvr.ccz文件了。 

plist文件是圖片信息的屬性列表文件。

PVR圖像是專門為ios設備上麵的PowerVR圖形芯片指定的圖像容器。它們在ios設備上非常好用,因為可以直接加載到顯卡上麵,而不需要經過中間的轉化。pvr.ccz文件則是pvr文件格式的壓縮格式,使用這種圖片格式的好處有兩點:1、可以使你的應用程序更小,因為圖片是被壓縮過了的。2、你的遊戲能夠啟動地更快。

2. SpriteFrameCache和SpriteBatchNode

上麵,我們用TexturePacker工具打包生成了plist和pvr.ccz文件,那麼下一步,我們就該獲取plist中的信息了。 

Cocos2d中SpriteFrameCache通常用來處理plist文件,並能與SpriteBatchNode結合使用來達到批處理渲染精靈的目的。

  • 精靈幀緩存類SpriteFrameCache 

    精靈幀緩存類SpriteFrameCache 用來存儲精靈幀,緩存精靈幀有助於提高程序的效率。 SpriteFrameCache是一個單例模式,不屬於某個精靈,是所有精靈共享使用的。

  • 精靈批處理節點SpriteBatchNode 

    當你需要渲染顯示兩個或兩個以上相同的精靈時,如果逐個渲染精靈,每一次渲染都會調用 OpenGL ES 的 draw 函數,這樣做自然降低了渲染效率。不過幸好,Cocos2d為開發者提供了一個SpriteBatchNode類,它能一次渲染多個精靈。並可以用來批處理這些精靈,比如我們遊戲中的壽司精靈。用SpriteBatchNode作為父層來創建子精靈,並且使用它來管理精靈類,這樣可以提高程序的效率。

在init()方法中調用SpriteFrameCache的addSpriteFramesWithFile方法,傳入plist文件名稱,它會從plist屬性列表文件的元數據部分獲取各個紋理的紋理名,載入到紋理緩存中。並解析屬性列表文件,使用SpriteFrame對象來內部地跟蹤所有精靈的信息。

在Cocos2d中高效使用圖片總結:

使用TexturePacker打包圖片成pvr.ccz文件,使用SpriteBatchNode優化繪製,使用SpriteFrameCache緩存讀取,使用spriteWithFrameName獲取單張圖片。

3. 壽司矩陣起始點的初始化

在遊戲中,我們用來存儲SushiSprite的數據結構是一個指針數組,其實它也就相當於一個矩陣。壽司矩陣的起始點其實就是壽司精靈開始布局的起始點,在我們的遊戲教程中,它位於屏幕的左下角,它由左下角的點開始逐行逐列的初始化壽司精靈。計算該點的公式如下:

m_matrixLeftBottomX = (winSize.width - SushiSprite::getContentWidth() * m_width - (m_width - 1) * SUSHI_GAP) / 2;
m_matrixLeftBottomY = (winSize.height - SushiSprite::getContentWidth() * m_height - (m_height - 1) * SUSHI_GAP) / 2;

其原理可簡單描述為下圖所示的過程(隻以計算m_matrixLeftBottomX的值為例,即X軸方向坐標值): 

 

看原理圖其實就已經一目了然了,上圖N代表的是橫向布局的壽司精靈數,m_matrixLeftBottomX的值 = ( 屏幕的寬 - 壽司的寬*N個壽司 - ( N-1 )*壽司之間的間隙) / 2。

4. 如何布局

加載完壽司精靈圖片,計算好壽司精靈布局的起始點以後,我們就可以開始壽司精靈的布局和它的下落顯示了,下麵是代碼行:

//矩陣的初始化
void PlayLayer::initMatrix()
{
    for (int row = 0; row < m_height; row++) {
        for (int col = 0; col < m_width; col++) {
            createAndDropSushi(row, col);
        }
    }
}
//創建SushiSprite,並下落到指定位置
void PlayLayer::createAndDropSushi(int row, int col)
{
    Size size = Director::getInstance()->getWinSize();

    SushiSprite *sushi = SushiSprite::create(row, col);

    // 創建並執行下落動畫
    Point endPosition = positionOfItem(row, col);
    Point startPosition = Point(endPosition.x, endPosition.y + size.height / 2);
    sushi->setPosition(startPosition);
    float speed = startPosition.y / (2 * size.height);
    sushi->runAction(MoveTo::create(speed, endPosition));

    //將壽司添加到精靈表單裏。注意,如果沒有添加到精靈表單裏,而是添加到層裏的話,就不會得到性能的優化。
    spriteSheet->addChild(sushi);

    //給指定位置的數組賦值
    m_matrix[row * m_width + col] = sushi;
}
//得到對應行列精靈的坐標值
Point PlayLayer::positionOfItem(int row, int col)
{
    float x = m_matrixLeftBottomX + (SushiSprite::getContentWidth() + SUSHI_GAP) * col + SushiSprite::getContentWidth() / 2;
    float y = m_matrixLeftBottomY + (SushiSprite::getContentWidth() + SUSHI_GAP) * row + SushiSprite::getContentWidth() / 2;
    return Point(x, y);
}

我們先來看怎樣獲取指定行列精靈在屏幕上的坐標值,即positionOfItem(row, col)方法的實現。同樣附上原理圖,方便理解。 

 

上圖矩陣的起始點已知(m_matrixLeftBottomX,m_matrixLeftBottomY),計算第row行col列的壽司精靈的坐標值。 

需要說明的是,精靈圖片的錨點默認在圖片的中心位置,錨點關係到紋理貼圖的位置。例如:如果把一個精靈設置在(0,0)點的位置,那麼它的錨點也就會和(0,0)點重合,在屏幕上也就隻能顯示四分之一的精靈。所以往往為了避免這種問題,在貼精靈圖片的時候我們會加上它寬高的一半。 

言歸正傳,結合上麵的原理圖,你將很容易理解(x, y)是如何計算的。

最後,壽司精靈的創建和下落方法:createAndDropSushi(row, col)。
壽司精靈的創建一幕了然,它的下落是通過讓壽司精靈執行MoveTo動作來實現的,具體方法是把壽司精靈的起點設置在比終點(可以通過positionOfItem方法獲取)高size.height / 2的地方,再讓其以一定的速度從起點移動到終點。原理如下圖所示:

where to go

至此,我們第一章的內容久講完了。

本節代碼下載地址:https://github.com/iTyran/SushiCrush/tree/Part1

在下一章中,我們將實現初次掉落的消除檢查,並自動消除三個連在一起的壽司,然後掉落新的壽司補齊空缺。

最後更新:2017-04-03 12:56:05

  上一篇:go 圖論總述
  下一篇:go Android中的基本控件(上)--按鈕控件Button