消消乐作为国民级的三消休闲游戏,核心玩法简单易懂:交换相邻元素,凑齐 3 个及以上相同元素即可消除得分。今天我们就用 Cocos2d-x 4.0(C++ 版) 从零实现一个极简版消消乐,掌握 2D 游戏开发的核心技能:精灵管理、触摸交互、动画效果、游戏逻辑判断。
本文适合 Cocos2d-x 新手,全程代码可直接运行,跟着步骤就能做出属于自己的消消乐~
一、开发环境准备
首先搭建 Cocos2d-x 开发环境,这是基础:
- Cocos2d-x 4.0:稳定版引擎(下载地址:Cocos 官网)
- 开发工具:Windows 用 VS2022,Mac 用 Xcode
- 依赖:Python 3.7+(用于创建 Cocos 项目)
- 配置引擎环境变量,确保终端能执行
cocos命令
二、创建 Cocos2d-x 项目
打开终端 / 命令提示符,执行命令创建消消乐项目:
bash
运行
# 命令格式:cocos new 项目名 -p 包名 -l 语言 -d 生成目录
cocos new MatchThreeGame -p com.game.matchthree -l cpp -d ./
创建完成后,用 VS/Xcode 打开proj.win32(Windows)或proj.mac(Mac)目录下的工程文件。
三、游戏核心设计思路
消消乐的核心逻辑非常清晰,我们拆分为 6 个模块:
- 棋盘网格:固定 8×8 的网格(可自定义),每个格子存放一个宝石元素
- 元素精灵:继承 Cocos2d-x 的 Sprite,绑定类型、网格坐标属性
- 触摸交互:点击选中元素,再次点击相邻元素实现交换
- 匹配检测:判断横向 / 纵向是否有 3 个及以上相同元素
- 消除动画:消除匹配元素,播放缩放消失特效
- 下落填充:上方元素下落填补空位,空行生成新元素
四、游戏资源准备
我们准备5 种不同颜色的宝石图片(尺寸统一 80x80px),命名为:
gem1.png、gem2.png、gem3.png、gem4.png、gem5.png
将图片放入项目的 Resources 目录,这是 Cocos2d-x 默认的资源加载路径。
五、核心代码实现
我们直接修改引擎自动生成的 HelloWorldScene,改名为 GameScene 作为游戏主场景。
1. 头文件定义(GameScene.h)
定义棋盘大小、元素属性、核心函数,代码带详细注释:
cpp
运行
#ifndef __GAME_SCENE_H__
#define __GAME_SCENE_H__
#include "cocos2d.h"
USING_NS_CC;
// 棋盘配置:8行8列,每个元素大小80px
#define BOARD_ROWS 8
#define BOARD_COLS 8
#define GEM_SIZE 80.0f
// 宝石精灵:继承Sprite,新增网格坐标、类型属性
class GemSprite : public Sprite
{
public:
int row; // 行坐标
int col; // 列坐标
int type; // 元素类型(1-5)
static GemSprite* create(int type, int row, int col);
};
// 游戏主场景
class GameScene : public Layer
{
public:
static Scene* createScene();
virtual bool init() override;
void onEnter() override;
// 核心函数
void initBoard(); // 初始化棋盘
void createRandomGem(int row, int col); // 生成随机宝石
Vec2 gridToPos(int row, int col); // 网格坐标转屏幕坐标
bool isAdjacent(GemSprite* a, GemSprite* b); // 判断是否相邻
void swapGems(GemSprite* a, GemSprite* b); // 交换元素
void checkMatch(); // 检测匹配元素
void removeMatches(); // 消除匹配元素
void dropGems(); // 元素下落填充
// 触摸事件
bool onTouchBegan(Touch* touch, Event* event);
void onTouchEnded(Touch* touch, Event* event);
CREATE_FUNC(GameScene);
private:
GemSprite* _board[BOARD_ROWS][BOARD_COLS]; // 棋盘二维数组
GemSprite* _selectedGem; // 选中的元素
int _score; // 游戏得分
Label* _scoreLabel; // 得分文本
};
#endif // __GAME_SCENE_H__
2. 实现文件(GameScene.cpp)
这是游戏的核心逻辑,包含交互、动画、消除逻辑:
cpp
运行
#include "GameScene.h"
// 宝石精灵创建
GemSprite* GemSprite::create(int type, int row, int col)
{
GemSprite* gem = new GemSprite();
if (gem && gem->initWithFile(StringUtils::format("gem%d.png", type).c_str()))
{
gem->autorelease();
gem->type = type;
gem->row = row;
gem->col = col;
return gem;
}
CC_SAFE_DELETE(gem);
return nullptr;
}
// 场景创建
Scene* GameScene::createScene()
{
auto scene = Scene::create();
auto layer = GameScene::create();
scene->addChild(layer);
return scene;
}
// 场景初始化
bool GameScene::init()
{
if (!Layer::init())
return false;
// 初始化得分
_score = 0;
_selectedGem = nullptr;
// 背景
auto bg = LayerColor::create(Color4B(240, 240, 240, 255));
this->addChild(bg, -1);
// 得分文本
_scoreLabel = Label::createWithSystemFont("得分: 0", "Arial", 30);
_scoreLabel->setPosition(Vec2(100, 700));
_scoreLabel->setColor(Color3B::BLACK);
this->addChild(_scoreLabel);
// 初始化棋盘
initBoard();
// 绑定触摸事件
auto touchListener = EventListenerTouchOneByOne::create();
touchListener->onTouchBegan = CC_CALLBACK_2(GameScene::onTouchBegan, this);
touchListener->onTouchEnded = CC_CALLBACK_2(GameScene::onTouchEnded, this);
_eventDispatcher->addEventListenerWithSceneGraphPriority(touchListener, this);
return true;
}
// 网格坐标转屏幕坐标(居中显示)
Vec2 GameScene::gridToPos(int row, int col)
{
float x = col * GEM_SIZE + GEM_SIZE/2 + 50;
float y = row * GEM_SIZE + GEM_SIZE/2 + 100;
return Vec2(x, y);
}
// 初始化棋盘:生成随机宝石
void GameScene::initBoard()
{
for (int row = 0; row < BOARD_ROWS; row++)
{
for (int col = 0; col < BOARD_COLS; col++)
{
createRandomGem(row, col);
}
}
}
// 生成随机类型的宝石
void GameScene::createRandomGem(int row, int col)
{
int type = random(1, 5); // 随机1-5类型
auto gem = GemSprite::create(type, row, col);
gem->setPosition(gridToPos(row, col));
this->addChild(gem);
_board[row][col] = gem;
}
// 触摸开始:选中宝石
bool GameScene::onTouchBegan(Touch* touch, Event* event)
{
return true;
}
// 触摸结束:处理交换逻辑
void GameScene::onTouchEnded(Touch* touch, Event* event)
{
Vec2 touchPos = touch->getLocation();
// 遍历棋盘,找到点击的宝石
for (int row = 0; row < BOARD_ROWS; row++)
{
for (int col = 0; col < BOARD_COLS; col++)
{
auto gem = _board[row][col];
if (gem->getBoundingBox().containsPoint(touchPos))
{
// 第一次选中:高亮元素
if (_selectedGem == nullptr)
{
_selectedGem = gem;
_selectedGem->setScale(1.1f); // 选中放大
}
// 第二次选中:交换元素
else
{
// 如果是相邻元素,执行交换
if (isAdjacent(_selectedGem, gem))
{
swapGems(_selectedGem, gem);
checkMatch(); // 检测匹配
}
// 还原选中状态
_selectedGem->setScale(1.0f);
_selectedGem = nullptr;
}
return;
}
}
}
}
// 判断两个宝石是否相邻(上下左右)
bool GameScene::isAdjacent(GemSprite* a, GemSprite* b)
{
int dr = abs(a->row - b->row);
int dc = abs(a->col - b->col);
return (dr == 1 && dc == 0) || (dr == 0 && dc == 1);
}
// 交换两个宝石的位置和数据
void GameScene::swapGems(GemSprite* a, GemSprite* b)
{
// 交换屏幕坐标(动画)
auto moveA = MoveTo::create(0.2f, b->getPosition());
auto moveB = MoveTo::create(0.2f, a->getPosition());
a->runAction(moveA);
b->runAction(moveB);
// 交换棋盘数组数据
std::swap(_board[a->row][a->col], _board[b->row][b->col]);
// 交换网格坐标
std::swap(a->row, b->row);
std::swap(a->col, b->col);
}
// 检测横向/纵向匹配(3个及以上相同元素)
void GameScene::checkMatch()
{
bool hasMatch = false;
std::vector<GemSprite*> matches;
// 横向检测
for (int row = 0; row < BOARD_ROWS; row++)
{
for (int col = 0; col < BOARD_COLS - 2; col++)
{
int type = _board[row][col]->type;
if (type == _board[row][col+1]->type && type == _board[row][col+2]->type)
{
matches.push_back(_board[row][col]);
matches.push_back(_board[row][col+1]);
matches.push_back(_board[row][col+2]);
hasMatch = true;
}
}
}
// 纵向检测
for (int col = 0; col < BOARD_COLS; col++)
{
for (int row = 0; row < BOARD_ROWS - 2; row++)
{
int type = _board[row][col]->type;
if (type == _board[row+1][col]->type && type == _board[row+2][col]->type)
{
matches.push_back(_board[row][col]);
matches.push_back(_board[row+1][col]);
matches.push_back(_board[row+2][col]);
hasMatch = true;
}
}
}
// 有匹配则消除+得分+下落填充
if (hasMatch)
{
for (auto gem : matches)
{
_score += 10; // 每个元素+10分
gem->setTag(1); // 标记为待消除
}
_scoreLabel->setString(StringUtils::format("得分: %d", _score));
removeMatches();
}
}
// 消除匹配的元素(播放缩放动画)
void GameScene::removeMatches()
{
for (int row = 0; row < BOARD_ROWS; row++)
{
for (int col = 0; col < BOARD_COLS; col++)
{
auto gem = _board[row][col];
if (gem->getTag() == 1)
{
// 缩放消失动画
auto scale = ScaleTo::create(0.3f, 0.0f);
auto remove = RemoveSelf::create();
gem->runAction(Sequence::create(scale, remove, nullptr));
_board[row][col] = nullptr; // 置空棋盘
}
}
}
// 延迟执行下落填充(等待动画完成)
this->runAction(Sequence::create(DelayTime::create(0.3f),
CallFunc::create(CC_CALLBACK_0(GameScene::dropGems, this)),
nullptr));
}
// 元素下落填充空位
void GameScene::dropGems()
{
// 从下往上遍历每一列
for (int col = 0; col < BOARD_COLS; col++)
{
for (int row = 0; row < BOARD_ROWS; row++)
{
if (_board[row][col] == nullptr)
{
// 上方元素下落
for (int r = row + 1; r < BOARD_ROWS; r++)
{
if (_board[r][col] != nullptr)
{
// 移动数据
_board[row][col] = _board[r][col];
_board[r][col] = nullptr;
// 更新坐标+播放动画
_board[row][col]->row = row;
_board[row][col]->runAction(MoveTo::create(0.2f, gridToPos(row, col)));
break;
}
}
}
}
}
// 顶部生成新元素
for (int col = 0; col < BOARD_COLS; col++)
{
if (_board[BOARD_ROWS-1][col] == nullptr)
{
createRandomGem(BOARD_ROWS-1, col);
}
}
// 再次检测匹配(连锁消除)
this->runAction(Sequence::create(DelayTime::create(0.3f),
CallFunc::create(CC_CALLBACK_0(GameScene::checkMatch, this)),
nullptr));
}
3. 修改入口函数
打开 main.cpp,将默认场景改为我们的 GameScene:
cpp
运行
#include "GameScene.h"
int main()
{
auto scene = GameScene::createScene();
Director::getInstance()->runWithScene(scene);
return 0;
}
六、运行效果
编译运行项目,你会看到:
- 8×8 的棋盘随机生成 5 种宝石
- 点击宝石会放大高亮,再次点击相邻宝石可交换
- 凑齐 3 个及以上相同宝石会自动消除,得分增加
- 消除后上方宝石下落填充,顶部生成新宝石,支持连锁消除
完美实现消消乐的核心玩法!
七、功能拓展方向
这个极简版消消乐是基础,我们可以轻松拓展进阶功能:
- 特效优化:添加消除粒子特效、交换震动效果
- 关卡系统:增加目标分数、步数限制
- 道具系统:添加炸弹、刷新、消除单行等道具
- 难度升级:增加障碍物、更多元素类型
- 音效音乐:添加点击、消除、得分的音效
八、总结
通过这个消消乐实战,我们掌握了 Cocos2d-x 开发的核心技能:
- 精灵(Sprite)的自定义与管理
- 触摸事件的监听与交互处理
- 动作动画(Move/Scale/Sequence)的使用
- 二维数组实现棋盘网格的逻辑控制
- 三消游戏的核心算法:匹配检测、消除、下落填充
Cocos2d-x 作为老牌 2D 游戏引擎,非常适合休闲游戏开发。这个小项目是入门的绝佳案例,赶紧动手试试吧!
关键点回顾
- 核心:相邻交换 + 三消检测 + 下落填充
- 坐标:网格坐标 ↔ 屏幕坐标转换是基础
- 动画:用 Cocos 动作系统实现流畅的交换、消除、下落效果
- 逻辑:用二维数组管理棋盘元素,数据与视图分离