Skip to content

cocos2d-x制作高仿2048游戏 #46

Open
@tangxueer

Description

@tangxueer

游戏引擎:cocos2d-x v3.8.1

主要语言:C++

运行平台:win,ios,android

开发流程:

(一) 收集图片素材

(二) Cocos Studio制作静态UI

(此步可被代码代替,但使用可视化工具更加便捷)

主场景:

先添加图片资源,创建基础容器 root,在 root 子树上添加一系列静态元素,这里除了菜单按钮需要用户交互外其他都是图片类型(Cocos Studio中图片与精灵区别不大)。修改相应元素的名称,以便在写代码时做对应操作。

菜单层:

因为菜单是在主场景的基础上跳出来的,所以作为一个层来处理。同样添加所需的元素。

(三) Cocos2d-x 编写游戏逻辑

(1) 类的定义(工厂模式)
先创建游戏所需场景MainScene,所需层MenuLayer的 .cpp 与 .h 文件,又由于2048游戏主要体现在对卡片的操作上,且比较复杂,所以这里再创建 CardSprite 的 .cpp 与 .h 文件。

主场景:

class MainScene : public cocos2d::Layer
{
public:
static cocos2d::Scene* createScene();
virtual bool init();
CREATE_FUNC(MainScene);
static void ClearData(); //清除上次游戏数据,用于重新开始

private:
void LoadBackground(); //加载背景音乐
void MenuTouch(cocos2d::Ref *pSender, Widget::TouchEventType type); //点击弹出菜单事件
void SetScore(); //设置分数
Size GetVisibleSize(); //获取可视化大小
void SetTouchListener(); //调用手势识别的事件监听器,添加事件监听
void createCardSprite(cocos2d::Size size); //创建卡片精灵
virtual bool onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event); //触摸事件开始
virtual void onTouchEnded(cocos2d::Touch *touch, cocos2d::Event *unused_event); //触摸事件结束
bool doLeft(); //向左
bool doRight(); //向右
bool doUp(); //向上
bool doDown(); //向下
void autoCreateCardNumber(); //自动生成随机数初始卡片
void doCheckGameOver(); //判断游戏是否结束

Layout* root;
ImageView* imgIcon;
ImageView* imgScorelast;
ImageView* imgScoremax;
Button* btnMenu;
int firstX, firstY, endX, endY;
CardSprite *cardArr[4][4]; //二维数组存放16张卡片格
int lastScore; //最新分数
int maxScore; //历史最高分数
LabelTTF *labelTTFCardNumber;
LabelTTF *labelTTFlastScore;
LabelTTF *labelTTFmaxScore;

};

菜单层:

class MenuLayer : public cocos2d::Layer
{
public:
virtual bool init();
CREATE_FUNC(MenuLayer);
static void playEffect(const std::string &effectName, bool force); //播放音效

private:
void SetBackground(); //获取studio ui场景
void SetButton(); //设置菜单选项按钮
void ContinueTouch(cocos2d::Ref pSender, Widget::TouchEventType type); //继续按钮点击事件
void RestartTouch(cocos2d::Ref *pSender, Widget::TouchEventType type); //重新开始按钮点击事件
void Close(Node
pSender); //关闭菜单层
void loadSoundMenu(); //加载声音菜单
void soundMenuCallback(Ref *sender); //声音菜单回调函数

Layout* root;
ImageView* imgBack;
ImageView* imgMenu;
Button* btnContinue;
Button* btnRestart;
CheckBox* checkBox;

};

卡片精灵:

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:
void enemyInit(int numbers, int width, int height, float CardSpriteX, float CardSpriteY); //结合卡片与数字
int number;
cocos2d::LabelTTF *labTTFCardNumber;
cocos2d::LayerColor *layerColorBG;
};

(2) 方法定义
Cocos2d-x是个功能强大的集成库,其提供的方法可满足大部分游戏的需求,写代码时配合官方文档食用风味更佳。

获取 Cocos Studio 的UI页面

void MainScene::LoadBackground()
{
auto rootNode = CSLoader::createNode("MainScene.csb");
root = (Layout_)rootNode->getChildByName("root");
imgIcon = (ImageView_)Helper::seekWidgetByName(root, "imgIcon");
imgScorelast = (ImageView_)Helper::seekWidgetByName(root, "imgScorelast");
imgScoremax = (ImageView_)Helper::seekWidgetByName(root, "imgScoremax");
btnMenu = (Button*)Helper::seekWidgetByName(root, "btnMenu");
//加载控件
btnMenu->addTouchEventListener(CC_CALLBACK_2(MainScene::MenuTouch, this));
//菜单按钮点击事件
addChild(rootNode);
}

2048游戏的特色在于识别上下左右手势,对卡片进行移动或合并操作。(这里有点要注意,当初困扰了我一天,一定要把Cocos Studio中除按钮外的控件取消交互性,否则会和手势识别冲突。)

//调用手势识别的事件监听器,添加事件监听
void MainScene::SetTouchListener()
{
auto touchListener = EventListenerTouchOneByOne::create();
touchListener->onTouchBegan = CC_CALLBACK_2(MainScene::onTouchBegan, this);
touchListener->onTouchEnded = CC_CALLBACK_2(MainScene::onTouchEnded, this);

_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);
}

//触摸事件开始
bool MainScene::onTouchBegan(cocos2d::Touch *touch, cocos2d::Event *unused_event) {
Point touchP0 = touch->getLocation(); //触摸点
firstX = touchP0.x;
firstY = touchP0.y;
return true;
}

//触摸结束触发
void MainScene::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()) {
MenuLayer::playEffect("Sound/move.wav", false);
autoCreateCardNumber();
doCheckGameOver();
}//右,上,下省略
}
}
}

有卡片的游戏中初始化卡片的套路

CardSprite* CardSprite::createCardSprite(int numbers, int width, int height, float CardSpriteX, float CardSpriteY)
{
// 初始化卡片精灵
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 MainScene::doLeft() {
//log("doLeft");
bool isdo = false;
// 最外层循环为4*4迭代
for (int y = 0; y < 4; y++) {
for (int x = 0; x < 4; x++) {
//判断卡片是合并还是清空
for (int x1 = x + 1; x1 < 4; x1++) {
if (cardArr[x1][y]->getNumber() > 0) {//有数字
if (cardArr[x][y]->getNumber() <= 0) { //为空
//设置为右边卡片的数值
cardArr[x][y]->setNumber(cardArr[x1][y]->getNumber());
cardArr[x1][y]->setNumber(0);
x--;
isdo = true;
}
else if (cardArr[x][y]->getNumber() == cardArr[x1][y]->getNumber()) {
//当前卡片的值与其比较卡片的值相等,设置为其的2倍
cardArr[x][y]->setNumber(cardArr[x][y]->getNumber() * 2);
cardArr[x1][y]->setNumber(0);
MenuLayer::playEffect("Sound/merge.wav", false);
cardArr[x][y]->runAction(Sequence::create(ScaleTo::create(0.05, 1.1), ScaleTo::create(0.05, 1.0), NULL));
//设置分数
lastScore += cardArr[x][y]->getNumber();
if (lastScore > maxScore)
{
maxScore = lastScore;
}
labelTTFlastScore->setString(__String::createWithFormat("%i", lastScore)->getCString());
labelTTFmaxScore->setString(__String::createWithFormat("%i", maxScore)->getCString());
isdo = true;
}
break; //跳出,否则无法运行
}
}
}
}
return isdo;
}

菜单层中的继续按钮与重新开始按钮的动效不同,前者是缩小到消失,后者是清空变黑

//继续按钮
void MenuLayer::ContinueTouch(cocos2d::Ref *pSender, Widget::TouchEventType type)
{
switch (type)
{
case Widget::TouchEventType::ENDED:
MenuLayer::playEffect("Sound/move.wav",false); //点击的音效
auto seq = Sequence::create(ScaleTo::create(0.2, 0), CallFuncN::create(CC_CALLBACK_1(MenuLayer::Close, this)), NULL);
//弹窗关闭动画 0.2s逐渐缩小到消失
imgBack->runAction(seq);
break;
}
}

//关闭菜单层
void MenuLayer::Close(Node* pSender)
{
this->removeFromParentAndCleanup(true);//把该层从父空间清空
}

//重新开始按钮
void MenuLayer::RestartTouch(cocos2d::Ref *pSender, Widget::TouchEventType type)
{
switch (type)
{
case Widget::TouchEventType::ENDED:
MenuLayer::playEffect("Sound/move.wav",false); //点击的音效
MainScene::ClearData();
Director::getInstance()->replaceScene(TransitionFade::create(0.5f, MainScene::createScene()));
//清空变黑,场景替换
break;
}
}

制作一个高仿的2048游戏是十分简单基础的,还有动画,音效等设置的代码就不贴了。这里格式有些乱,我的github上有源码,网上也有参考教程,有兴趣的同学可以进一步了解。

最后,cocos2d-x 推荐学习路线:
1.麦子学院,极客学院 cocos 入门教学视频系列
2 .cocos 官方文档,教程
3.小游戏项目实战
4. lua 脚本
5.极客学院物理引擎,数据操作,绘图 API 等进阶视频
6. cocos2d-x 底层源码
7.大项目实战
......

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions