Cocos2d-x 入门实战|从零开发经典消消乐游戏

15次阅读
没有评论

消消乐作为国民级的三消休闲游戏,核心玩法简单易懂:交换相邻元素,凑齐 3 个及以上相同元素即可消除得分。今天我们就用 Cocos2d-x 4.0(C++ 版) 从零实现一个极简版消消乐,掌握 2D 游戏开发的核心技能:精灵管理、触摸交互、动画效果、游戏逻辑判断。

本文适合 Cocos2d-x 新手,全程代码可直接运行,跟着步骤就能做出属于自己的消消乐~

一、开发环境准备

首先搭建 Cocos2d-x 开发环境,这是基础:

  1. Cocos2d-x 4.0:稳定版引擎(下载地址:Cocos 官网
  2. 开发工具:Windows 用 VS2022,Mac 用 Xcode
  3. 依赖:Python 3.7+(用于创建 Cocos 项目)
  4. 配置引擎环境变量,确保终端能执行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 个模块:

  1. 棋盘网格:固定 8×8 的网格(可自定义),每个格子存放一个宝石元素
  2. 元素精灵:继承 Cocos2d-x 的 Sprite,绑定类型、网格坐标属性
  3. 触摸交互:点击选中元素,再次点击相邻元素实现交换
  4. 匹配检测:判断横向 / 纵向是否有 3 个及以上相同元素
  5. 消除动画:消除匹配元素,播放缩放消失特效
  6. 下落填充:上方元素下落填补空位,空行生成新元素

四、游戏资源准备

我们准备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;
}

六、运行效果

编译运行项目,你会看到:

  1. 8×8 的棋盘随机生成 5 种宝石
  2. 点击宝石会放大高亮,再次点击相邻宝石可交换
  3. 凑齐 3 个及以上相同宝石会自动消除,得分增加
  4. 消除后上方宝石下落填充,顶部生成新宝石,支持连锁消除

完美实现消消乐的核心玩法!

七、功能拓展方向

这个极简版消消乐是基础,我们可以轻松拓展进阶功能:

  1. 特效优化:添加消除粒子特效、交换震动效果
  2. 关卡系统:增加目标分数、步数限制
  3. 道具系统:添加炸弹、刷新、消除单行等道具
  4. 难度升级:增加障碍物、更多元素类型
  5. 音效音乐:添加点击、消除、得分的音效

八、总结

通过这个消消乐实战,我们掌握了 Cocos2d-x 开发的核心技能:

  • 精灵(Sprite)的自定义与管理
  • 触摸事件的监听与交互处理
  • 动作动画(Move/Scale/Sequence)的使用
  • 二维数组实现棋盘网格的逻辑控制
  • 三消游戏的核心算法:匹配检测、消除、下落填充

Cocos2d-x 作为老牌 2D 游戏引擎,非常适合休闲游戏开发。这个小项目是入门的绝佳案例,赶紧动手试试吧!


关键点回顾

  1. 核心:相邻交换 + 三消检测 + 下落填充
  2. 坐标:网格坐标 ↔ 屏幕坐标转换是基础
  3. 动画:用 Cocos 动作系统实现流畅的交换、消除、下落效果
  4. 逻辑:用二维数组管理棋盘元素,数据与视图分离
正文完
可以使用微信扫码关注公众号(ID:xzluomor)
post-qrcode
 0
评论(没有评论)