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_