https://blog.csdn.net/wwj_748/article/details/38168649
本篇博客給大家介紹如何快速開發2048這樣一款休閒遊戲,理解整個2048遊戲的開發流程,從本篇博客你將可以學習到以下內容:
這裏註明一下,本教程來自極客學院,小巫對其中代碼進行了解釋。
- 2048遊戲的邏輯
- Cocos2d-x中上下左右手勢的識別
- 遊戲中卡片類的創建
- 添加卡片到遊戲中
- 遊戲中的邏輯實現
- 遊戲中隨機卡片的生成
- 遊戲結束判斷
- 遊戲分數的添加
- 遊戲美化
筆者的開發環境:
Cocos2d-x 3.1.1(開發引擎)
Visual Studio 2012(Win32)
Xcode 5.1(Mac系統下)
理解2048遊戲邏輯
2048遊戲邏輯並不複雜,4*4的卡片佈局,玩家通過手勢上下左右滑動來累加卡片數值,直到累加到2048。筆者用一張圖說明:
這是一張遊戲中的圖,在圖中同一方向並且數值相同的卡片可以進行疊加,比如128和128在同一行,玩家可以通過向左或向右的手勢,對其進行疊加。筆者向右滑動手勢,則會變成以下效果:
Cocos2d-x中上下左右手勢的識別
玩家在玩2048遊戲時,手勢是最頻繁的操作,所以我們需要對手勢所產生的事件進行監聽。
在HelloWorldScene.h頭文件中聲明兩個需要實現的監聽事件:
- // 加入手勢識別的事件
- virtual bool onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event);
- virtual void onTouchEnded(cocos2d::Touch *touch, cocos2d::Event *unused_event);
聲明點擊的位置屬性
- // 點擊的元素位置
- int firstX, firstY, endX, endY;
一個是觸摸開始的事件,一個是觸摸結束的事件。
然後再HelloWorldScene.cpp文件中實現這兩個方法:
- // 加入手勢識別的事件
- bool HelloWorld::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event){
- // 觸摸點
- Point touchP0 = touch->getLocation();
-
- firstX = touchP0.x;
- firstY = touchP0.y;
-
-
- return true;
- }
-
- // 觸摸結束觸發
- void HelloWorld::onTouchEnded(cocos2d::Touch *touch, cocos2d::Event *unused_event){
- // 獲取觸摸點位置
- Point touchP0 = touch->getLocation();
- // 獲取X軸和Y軸的移動距離
- endX = firstX - touchP0.x;
- endY = firstY - touchP0.y;
-
- // 判斷X軸和Y軸的移動距離,如果X軸的絕對值大於Y軸的絕對值就是左右否則是上下
- if (abs(endX) > abs(endY)){
- // 左右
- if (endX + 5 > 0) {
- // 左邊
- if(doLeft()) {
- autoCreateCardNumber();
- doCheckGameOver();
- }
-
- } else {
- // 右邊
- if(doRight()){
- autoCreateCardNumber();
- doCheckGameOver();
- }
- }
-
- } else {
- // 上下
- if (endY + 5 > 0) {
- // 下邊
- if(doDown()) {
- autoCreateCardNumber();
- doCheckGameOver();
- }
- } else {
- // 上邊
- if(doUp()) {
- autoCreateCardNumber();
- doCheckGameOver();
- };
- }
-
- }
- }
在HelloWorld的init方法中設置監聽器的這兩個方法的監聽回調:
- // 創建手勢識別的事件監聽器
- auto touchListener = EventListenerTouchOneByOne::create();
- touchListener->onTouchBegan = CC_CALLBACK_2(HelloWorld::onTouchBegan, this);
- touchListener->onTouchEnded = CC_CALLBACK_2(HelloWorld::onTouchEnded, this);
- // 添加事件監聽
- _eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);
上面中根據計算開始位置到結束位置X軸和Y軸的移動距離來判斷是向左、向右、向上還是向下的方向:
在頭文件中聲明四個方向的方法:
- // 上下左右的方法
- bool doLeft();
- bool doRight();
- bool doUp();
- bool doDown();
然後對其進行實現。這四個方法的邏輯實現放到後面進行講解。
遊戲中卡片類的創建
卡片類是組成2048遊戲的基礎,4*4的方格的16個位置放置不同的卡片,每個位置爲獨立的一張卡片。
創建CardSprite.h的頭文件:
- //
- // CardSprite.h
- // 2048Game
- //
- // Created by mac on 14-7-16.
- //
- //
-
- #ifndef ___048Game__CardSprite__
- #define ___048Game__CardSprite__
-
- #include "cocos2d.h"
-
- class CardSprite:public cocos2d::Sprite {
-
- public:
- // 初始化遊戲卡片的方法
- static CardSprite *createCardSprite(int numbers, int width, int height, float CardSpriteX, float CardSpriteY);
- virtual bool init();
- CREATE_FUNC(CardSprite);
-
- // 設置數字
- void setNumber(int num);
-
- // 獲取數字
- int getNumber();
- private:
- // 顯示在界面的數字
- int number;
- void enemyInit(int numbers, int width, int height, float CardSpriteX, float CardSpriteY);
-
- // 定義顯示數字的控件
- cocos2d::LabelTTF *labTTFCardNumber;
-
- // 顯示的背景
- cocos2d::LayerColor *layerColorBG;
-
-
- };
-
- #endif /* defined(___048Game__CardSprite__) */
我們可以從遊戲中看到,每張卡片有背景顏色和一個數字,所以我們在頭文件需要聲明它這兩個屬性。
CardSprite.cpp中對頭文件方法的實現:
- //
- // CardSprite.cpp
- // 2048Game
- //
- // Created by wwj on 14-7-16.
- //
- //
-
- #include "CardSprite.h"
-
- USING_NS_CC;
-
- // 初始化遊戲卡片的方法
- CardSprite* CardSprite::createCardSprite(int numbers, int width, int height, float CardSpriteX, float CardSpriteY) {
- // new一個卡片精靈
- CardSprite *enemy = new CardSprite();
- if (enemy && enemy->init()) {
- enemy->autorelease();
- enemy->enemyInit(numbers, width, height, CardSpriteX, CardSpriteY);
- return enemy;
- }
- CC_SAFE_DELETE(enemy);
- return NULL;
- }
-
- // 卡片初始化方法
- bool CardSprite::init() {
- if (!Sprite::init()) {
- return false;
- }
- return true;
- }
-
-
- // 設置數字
- void CardSprite::setNumber(int num) {
- number = num;
-
- // 判斷數字的大小來調整字體的大小
- if (number >= 0) {
- labTTFCardNumber->setFontSize(80);
- }
- if (number >= 16) {
- labTTFCardNumber->setFontSize(60);
- }
- if (number >= 128) {
- labTTFCardNumber->setFontSize(40);
- }
- if (number >= 1024) {
- labTTFCardNumber->setFontSize(20);
- }
-
- // 判斷數組的大小調整顏色
- if (number == 0) {
- layerColorBG->setColor(cocos2d::Color3B(200, 190, 180));
- }
- if (number == 2) {
- layerColorBG->setColor(cocos2d::Color3B(240, 230, 220));
- }
- if (number == 4) {
- layerColorBG->setColor(cocos2d::Color3B(240, 220, 200));
- }
- if (number == 8) {
- layerColorBG->setColor(cocos2d::Color3B(240, 180, 120));
- }
- if (number == 16) {
- layerColorBG->setColor(cocos2d::Color3B(240, 140, 90));
- }
- if (number == 32) {
- layerColorBG->setColor(cocos2d::Color3B(240, 120, 90));
- }
- if (number == 64) {
- layerColorBG->setColor(cocos2d::Color3B(240, 90, 60));
- }
- if (number == 128) {
- layerColorBG->setColor(cocos2d::Color3B(240, 90, 60));
- }
- if (number == 256) {
- layerColorBG->setColor(cocos2d::Color3B(240, 200, 70));
- }
- if (number == 512) {
- layerColorBG->setColor(cocos2d::Color3B(240, 200, 70));
- }
- if (number == 1024) {
- layerColorBG->setColor(cocos2d::Color3B(0, 130, 0));
- }
- if (number == 2048) {
- layerColorBG->setColor(cocos2d::Color3B(0, 130, 0));
- }
-
-
- // 更新顯示的數字
- if (number > 0) {
- labTTFCardNumber->setString(__String::createWithFormat("%i", num)->getCString() );
- } else {
- labTTFCardNumber->setString("");
- }
-
- }
-
- // 獲取數字
- int CardSprite::getNumber() {
- return number;
- }
-
-
- //第1個參數爲數字,第2、3個參數爲卡片的寬高,第4、5個參數爲卡片的位置
- void CardSprite::enemyInit(int numbers, int width, int height, float CardSpriteX, float CardSpriteY) {
- // 初始化數字
- number = numbers;
-
- // 加入遊戲卡片的背景顏色
- layerColorBG = cocos2d::LayerColor::create(cocos2d::Color4B(200, 190, 180, 255), width - 15, height - 15);
- layerColorBG->setPosition(Point(CardSpriteX, CardSpriteY));
-
- // 判斷如果不等於0就顯示,否則爲空
- if (number > 0) {
- // 加入中間字體
- labTTFCardNumber = cocos2d::LabelTTF::create(__String::createWithFormat("%i", number)->getCString(), "HirakakuProN-W6", 100);
- // 顯示卡片數字的位置,這裏顯示在背景的中間
- labTTFCardNumber->setPosition(Point(layerColorBG->getContentSize().width/2, layerColorBG->getContentSize().height/2));
- // 添加卡片數字到背景中
- layerColorBG->addChild(labTTFCardNumber);
- } else {
- // 加入中間字體
- labTTFCardNumber = cocos2d::LabelTTF::create("", "HirakakuProN-w6", 80);
- labTTFCardNumber->setPosition(Point(layerColorBG->getContentSize().width/2, layerColorBG->getContentSize().height/2));
- layerColorBG->addChild(labTTFCardNumber);
- }
- // 將卡片添加到層
- this->addChild(layerColorBG);
-
- }
每張卡片都有相同的寬和高,但數字和位置不同,數字是顯示在背景中間的,所以我們需要5個參數來初始化卡片的位置。
添加卡片到遊戲中
卡片放置在4*4方框中的不同位置,首先我們需要計算出每張卡片的寬高,然後計算每張卡片所在的位置,最後進行顯示。
在HelloWorldScene.h頭文件中聲明創建卡片的方法:
- // 創建卡片
- void createCardSprite(cocos2d::Size size);
在HelloWorldScene.cpp中實現該方法:
- // 創建卡片,size爲屏幕大小
- void HelloWorld::createCardSprite(cocos2d::Size size) {
- // 求出單元格的寬度和高度,28爲左右距離
- int lon = (size.width - 28) / 4;
-
- // 4*4的單元格
- for (int j = 0; j < 4; j++) {
- for (int i = 0; i < 4; i++) {
- // 數字0,寬高相同爲lon,lon+j+20爲卡片X軸位置,如lon+0+20爲第一個卡片的位置,20是每張卡片的間隙,lon+i+20+size.height/6代表的意思是屏幕大小豎方向分了六份,我們這裏只放4個位置
- CardSprite *card = CardSprite::createCardSprite(0, lon, lon, lon * j + 10, lon * i + 10 + size.height / 6);
- addChild(card);
-
- // 添加卡片到二維數組中
- cardArr[j][i] = card;
- }
- }
- }
筆者對每張卡片顯示的位置在代碼中進行了說明,讀者也可以思考一下爲什麼要這麼做。
遊戲中的邏輯實現
2048遊戲最重要的部分就是四個方向的邏輯實現,也是開發這個遊戲的難點所在。
遊戲中隨機卡片的生成
在HelloWorldScene.h頭文件中聲明隨機生成卡片的方法:
- // 自動卡片生成
- void autoCreateCardNumber();
在HelloWorldScene.cpp實現該方法:
- // 自動生成卡片
- void HelloWorld::autoCreateCardNumber() {
- int i = CCRANDOM_0_1() * 4;
- int j = CCRANDOM_0_1() * 4;
-
- // 判斷是否已經存在的位置
- if (cardArr[i][j]->getNumber() > 0) {
- // 已存在,遞歸創建
- autoCreateCardNumber();
- } else {
- // 生成2和4的比例是1:9的概率
- cardArr[i][j]->setNumber(CCRANDOM_0_1() * 10 < 1 ? 4:2);
- }
- }
遊戲結束判斷
在HelloWorldScene.h中聲明判斷遊戲結束的方法:
- // 判斷遊戲是否還能繼續運行下去
- void doCheckGameOver();
在HelloWorldScene.cpp中實現該方法:
- // 遊戲是否還能繼續運行下去
- void HelloWorld::doCheckGameOver() {
- bool isGameOver = true;
-
- for (int y = 0; y < 4; y++) {
- for (int x = 0; x < 4; x++) {
- if (cardArr[x][y]->getNumber() == 0
- || (x>0 && (cardArr[x][y]->getNumber() == cardArr[x-1][y]->getNumber() ))
- || (x<3 && (cardArr[x][y]->getNumber() == cardArr[x+1][y]->getNumber()))
- || (y<0 && (cardArr[x][y]->getNumber() == cardArr[x][y-1]->getNumber()))
- || (x<3 && (cardArr[x][y]->getNumber() == cardArr[x][y+1]->getNumber()))) {
- isGameOver = false;
- }
- }
- }
- if (isGameOver) {
- // 結束遊戲
- Director::getInstance()->replaceScene(TransitionFade::create(1, HelloWorld::createScene()));
- }
-
- }
遊戲分數的添加
2048中需要對分數進行統計並顯示給玩家,我們在HelloWolrdScene.h文件中聲明分數和顯示分數的控件:
- // 整體遊戲的分數
- int score;
- // 定義顯示數據的控件
- cocos2d::LabelTTF *labelTTFCardNumber;
在HelloWorldScene.cpp中對分數初始化爲0,並顯示「分數」的文本:
- score = 0;
-
-
- // 獲得屏幕可視大小
- Size visibleSize = Director::getInstance()->getVisibleSize();
- // 加入遊戲的背景
- auto layerColorBG = cocos2d::LayerColor::create(cocos2d::Color4B(180,170,160, 255));
- this->addChild(layerColorBG);
- // 在上方加入遊戲的分數
- auto labelTTFCardNumberName = LabelTTF::create("分數:","HirakakuProN-W6", 80);
- labelTTFCardNumberName->setPosition(Point(visibleSize.width/5, visibleSize.height-100));
- addChild(labelTTFCardNumberName);
-
-
- labelTTFCardNumber = LabelTTF::create("0", "HirakakuProN-W6", 80);
- labelTTFCardNumber->setPosition(visibleSize.width/2+100, visibleSize.height - 100);
- addChild(labelTTFCardNumber
- );
-
關於分數的計算,在遊戲中的邏輯已經實現,具體讀者可以查看代碼,後面筆者也會提供完整的代碼。
遊戲美化
2048中我們會發現不同的數字會有不同的背景,然後字體大小也會隨數值的變化而變化,實現這個需要通過判斷數值的大小來顯示不同的顏色背景和設置不同字體大小。
這個功能的實現是在卡片類設置數值的時候實現的,代碼邏輯如下:
- // 設置數字
- void CardSprite::setNumber(int num) {
- number = num;
-
- // 判斷數字的大小來調整字體的大小
- if (number >= 0) {
- labTTFCardNumber->setFontSize(80);
- }
- if (number >= 16) {
- labTTFCardNumber->setFontSize(60);
- }
- if (number >= 128) {
- labTTFCardNumber->setFontSize(40);
- }
- if (number >= 1024) {
- labTTFCardNumber->setFontSize(20);
- }
-
- // 判斷數組的大小調整顏色
- if (number == 0) {
- layerColorBG->setColor(cocos2d::Color3B(200, 190, 180));
- }
- if (number == 2) {
- layerColorBG->setColor(cocos2d::Color3B(240, 230, 220));
- }
- if (number == 4) {
- layerColorBG->setColor(cocos2d::Color3B(240, 220, 200));
- }
- if (number == 8) {
- layerColorBG->setColor(cocos2d::Color3B(240, 180, 120));
- }
- if (number == 16) {
- layerColorBG->setColor(cocos2d::Color3B(240, 140, 90));
- }
- if (number == 32) {
- layerColorBG->setColor(cocos2d::Color3B(240, 120, 90));
- }
- if (number == 64) {
- layerColorBG->setColor(cocos2d::Color3B(240, 90, 60));
- }
- if (number == 128) {
- layerColorBG->setColor(cocos2d::Color3B(240, 90, 60));
- }
- if (number == 256) {
- layerColorBG->setColor(cocos2d::Color3B(240, 200, 70));
- }
- if (number == 512) {
- layerColorBG->setColor(cocos2d::Color3B(240, 200, 70));
- }
- if (number == 1024) {
- layerColorBG->setColor(cocos2d::Color3B(0, 130, 0));
- }
- if (number == 2048) {
- layerColorBG->setColor(cocos2d::Color3B(0, 130, 0));
- }
-
-
- // 更新顯示的數字
- if (number > 0) {
- labTTFCardNumber->setString(__String::createWithFormat("%i", num)->getCString() );
- } else {
- labTTFCardNumber->setString("");
- }
-
- }
最後筆者給出所有代碼清單:
AppDelegate.h
AppDelegate.cpp
HelloWorldScene.h
HelloWorldScene.cpp
CardSprite.h
CardSprite.cpp
>>>AppDelegate.h
- #ifndef _APP_DELEGATE_H_
- #define _APP_DELEGATE_H_
-
- #include "cocos2d.h"
-
- /**
- @brief The cocos2d Application.
-
- The reason for implement as private inheritance is to hide some interface call by Director.
- */
- class AppDelegate : private cocos2d::Application
- {
- public:
- AppDelegate();
- virtual ~AppDelegate();
-
- /**
- @brief Implement Director and Scene init code here.
- @return true Initialize success, app continue.
- @return false Initialize failed, app terminate.
- */
- virtual bool applicationDidFinishLaunching();
- */
- virtual bool applicationDidFinishLaunching();
-
- /**
- @brief The function be called when the application enter background
- @param the pointer of the application
- */
- virtual void applicationDidEnterBackground();
-
- /**
- @brief The function be called when the application enter foreground
- @param the pointer of the application
- */
- virtual void applicationWillEnterForeground();
- };
-
- #endif // _APP_DELEGATE_H_