【Qt】探索Qt框架:开发经典贪吃蛇游戏的全过程与实践

时间:2024-07-16 07:11:24

文章目录

  • 引言
  • 项目链接:
  • 1. Qt框架的使用简介
  • 2. 贪吃蛇游戏设计
    • 2.1 游戏规则和玩法介绍
    • 2.2 游戏界面设计概述
  • 3. 核心代码解析
    • 3.1 主界面(GameHall)
      • 3.1.1 布局和功能介绍
      • 3.1.2 代码实现分析
    • 3.2 游戏选择界面(GameSelect)
      • 3.2.1 功能介绍
      • 3.2.2 代码实现分析
    • 3.3 游戏房间(GameRoom)
      • 3.3.1 游戏逻辑和核心算法解析
      • 3.3.2 代码实现细节
    • 3.4 按钮样式(ButtonStyle)
      • 3.4.1 代码实现
      • 3.4.2 实现效果
  • 4. 图形和音效
    • 4.1 图形资源
    • 4.2 音效资源
    • 4.3 在Qt中加载和使用资源
      • 示例代码:
  • 5. 调试和优化
    • 5.1 调试技巧
    • 5.2 性能优化建议
    • 5.3 代码重构和改进方法
  • 6. 扩展功能和未来方向
    • 6.1 扩展功能
    • 6.2 未来方向
    • 6.3 技术实现
  • 7. 总结
  • 结尾

引言

贪吃蛇游戏是一款历史悠久且广受欢迎的经典电子游戏,最早可以追溯到1976年的"Snake Game"。它在1997年作为诺基亚手机的内置游戏而变得家喻户晓,其简单的操作和上瘾的游戏性迅速赢得了全球玩家的喜爱。贪吃蛇的流行度不仅体现在其在手机平台上的普及,还扩展到了个人电脑、游戏机等其他平台,成为跨时代的电子游戏代表作。

使用Qt框架开发贪吃蛇游戏具有多方面的优势。Qt是一个功能强大且跨平台的应用程序开发框架,它提供了丰富的GUI控件和工具,使得用户界面的设计变得直观和高效。Qt的事件驱动架构和网络功能为实现响应式控制和多人在线游戏提供了强大支持。此外,Qt的开源特性和活跃的社区支持,为开发者提供了大量的资源和文档,降低了开发难度和成本。Qt的性能优化和集成开发环境也极大提升了开发效率,使得使用Qt开发贪吃蛇游戏成为一个理想的选择。

项目链接:

源代码:gitee:https://gitee.com/q-haodong/test_-qt/tree/master/Qt/greedy_snake

效果演示视频https://live.****.net/v/409583?spm=1001.2014.3001.5501

Qt贪吃蛇游戏

1. Qt框架的使用简介

在Qt贪吃蛇代码中,Qt的基本组件和模块主要包括:

  • QWidget: 作为大多数Qt控件的基类,QWidget代表了一个窗口或窗口内部的一个区域,可以包含其他控件和进行绘图。
  • QMainWindow: 表示主窗口,是大多数应用程序的顶层窗口。
  • QDialog: 用于创建对话框,用于用户交互。
  • QPushButton: 用于创建按钮控件,用户可以点击触发事件。在代码中,按钮用于开始游戏、选择游戏难度、返回主界面等操作。
  • QPainter: 用于绘制图形界面的2D图形类。在paintEvent函数中使用,用于绘制游戏界面、蛇的身体、食物等。
  • QIcon: 用于创建和处理图标,设置应用程序或控件的图标。
  • QTimer: 用于创建定时器,可以周期性地触发事件,如游戏中蛇的移动。
  • QSound: 用于播放声音效果,增强用户体验。
  • QFont: 用于定义字体的外观,如字体大小、风格等。
  • QMessageBox: 用于显示警告框或其他消息,与用户进行交互。
  • QFileQTextStream: 用于文件操作,如读取或写入数据。在游戏代码中,用于记录游戏分数。
  • QShortcut: 允许定义快捷键,使用户可以使用键盘快捷方式执行操作,如使用箭头键控制蛇的方向。
  • QPainterQPixmap: 一起使用来绘制图像。QPixmap是一个图像对象,QPainter用于绘制这个图像。
  • QListQRectF: QList是一个容器,用于存储列表数据;QRectF代表一个矩形区域,用于定义蛇的身体节点。
  • QApplication: 每个Qt应用程序的主对象,负责处理应用程序的控制流程。
  • 信号和槽机制: Qt的信号和槽是其核心特性之一,用于对象间的通信。例如,按钮的clicked信号可以连接到自定义槽函数,当按钮被点击时执行特定的操作。

2. 贪吃蛇游戏设计

2.1 游戏规则和玩法介绍

贪吃蛇是一款经典的电子游戏,其基本规则和玩法如下:

  • 目标:玩家控制一条蛇在游戏区域内移动,目标是吃掉随机出现的食物。
  • 控制:通常使用键盘的箭头键来控制蛇头的移动方向,上、下、左、右。
  • 成长:每吃掉一个食物,蛇的长度就会增加一节。
  • 障碍:蛇不能撞到自己的身体,否则游戏结束。
  • 得分:玩家通过吃掉食物获得分数,通常是根据食物的数量来累计得分。

2.2 游戏界面设计概述

贪吃蛇游戏的界面设计通常包括以下几个部分:

  1. 游戏区域:这是显示蛇和食物的主要区域,通常是一个矩形的网格。
  2. 开始/暂停按钮:允许玩家开始游戏或在游戏进行中暂停。
  3. 退出按钮:玩家可以通过点击退出按钮来结束当前游戏。
  4. 分数显示:显示玩家当前的得分,通常位于游戏区域的上方或角落。
  5. 游戏难度选择:可能包括不同难度级别的选择,影响食物的出现频率或蛇的移动速度。
  6. 历史战绩:显示玩家过去游戏的最高分或其他历史记录。
  7. 声音效果:游戏中可能会包含点击按钮的声音反馈和游戏结束时的特殊音效。

这些界面元素通过Qt的各种组件实现,例如使用QPushButton来创建按钮,使用QPainterQPixmap来绘制游戏区域的图形,以及使用QTimer来控制游戏的刷新率和蛇的移动速度。此外,界面的布局和样式通过Qt的布局管理器和样式表来设计,确保界面的美观和用户友好性。

3. 核心代码解析

3.1 主界面(GameHall)

3.1.1 布局和功能介绍

GameHall 类代表贪吃蛇游戏的主界面,它为玩家提供了进入游戏的起点。以下是主界面的主要布局和功能:

  • 窗口尺寸:主界面设置为固定大小,例如1000x800像素,提供一个足够大的显示区域。
  • 窗口图标:设置了一个窗口图标,增强了应用程序的专业外观。
  • 窗口标题:显示窗口的标题,例如“贪吃蛇游戏”,告知用户应用程序的名称。
  • 开始游戏按钮:界面上有一个明显的“开始游戏”按钮,这是玩家进入游戏的主要入口。按钮具有工具提示,说明其功能和快捷操作方式。
  • 绘图区域:通过覆盖paintEvent函数,主界面可以包含自定义的绘图逻辑,用于显示背景图像或游戏厅的静态画面。
    在这里插入图片描述

3.1.2 代码实现分析

在代码层面,GameHall 类的实现涉及以下几个关键部分:

  1. 构造函数 (GameHall::GameHall(QWidget *parent)): 初始化主界面,设置窗口大小、图标和标题。使用Ui::GameHall命名空间中的类来加载和设置用户界面布局。
GameHall::GameHall(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::GameHall)
{
    ui->setupUi(this);

    this->setFixedSize(1000, 800); //设置窗口大小
    this->setWindowIcon(QIcon(":res/ico.jpg")); // 设置窗口图标
    this->setWindowTitle("贪吃蛇游戏");

    QPushButton* strBtn = new QPushButton(this);
    strBtn->move(410, 500);
    strBtn->setText("开始游戏");
    strBtn->setToolTip("这是游戏开始按钮,鼠标点击或者回车键进入!");
    strBtn->setToolTipDuration(3000);
    ButtonStyle::setCommonStyle_green(strBtn, 30);

    // 为按钮设置回车快捷键
    strBtn->setShortcut(Qt::Key_Return);

    GameSelect* gameSelect = new GameSelect;
    connect(strBtn, &QPushButton::clicked, [=](){
        QSound::play(":res/clicked.wav");
        this->close();
        gameSelect->setGeometry(this->geometry()); // 第二个窗口和第一个窗口一样大
        gameSelect->show();
    });

}

  1. 按钮创建与布局:创建一个QPushButton实例,设置其位置、文本、工具提示和样式。按钮的位置使用move方法进行定位,文本设置为“开始游戏”,工具提示提供了额外的使用说明。

  2. 按钮样式设置:使用ButtonStyle::setCommonStyle_green函数来设置按钮的样式,例如背景颜色、边框、字体大小等。这为按钮提供了统一的外观和感觉。

  3. 信号连接:通过Qt的信号和槽机制,将“开始游戏”按钮的clicked信号连接到一个槽函数,当按钮被点击时执行。在这个槽函数中,播放点击声音,关闭当前的主界面,并展示GameSelect窗口,这是选择游戏模式的界面。

  4. 绘图事件处理 (GameHall::paintEvent(QPaintEvent *event)): 重写paintEvent以自定义绘图逻辑。使用QPainterQPixmap在窗口上绘制背景图像,创建更加吸引人的视觉效果。

void GameHall::paintEvent(QPaintEvent *event)
{
     Q_UNUSED(event); // 告诉编译器event参数是未使用的
    // 实例化画家
    QPainter painter(this);

    // 实例化绘图设备
    QPixmap pix(":res/game_hall.jpg");

    // 绘画
    painter.drawPixmap(0,0, this->width(), this->height(), pix);
}
  1. 析构函数 (GameHall::~GameHall()): 清理并删除由GameHall类分配的资源,特别是ui成员,这是用户界面的布局对象。
GameHall::~GameHall()
{
    delete ui;
}

3.2 游戏选择界面(GameSelect)

3.2.1 功能介绍

GameSelect 类构成了贪吃蛇游戏的选择界面,允许玩家根据自己的喜好选择不同的游戏难度。这个界面通常包含以下几个关键功能:

  • 窗口尺寸与图标:设置窗口的大小和图标,与主界面类似,提供了一致的用户体验。
  • 游戏模式按钮:提供了不同难度级别的按钮,如“简单模式”、“常规模式”和“困难模式”,每个按钮都对应一个特定的蛇移动速度。
  • 历史战绩按钮:允许玩家查看他们之前的游戏得分记录。
  • 返回按钮:一个返回到主界面的按钮,允许玩家在任何时候返回到游戏的起点。
    在这里插入图片描述

3.2.2 代码实现分析

在代码层面,GameSelect 类的实现涉及以下关键部分:

  1. 构造函数 (GameSelect::GameSelect(QWidget *parent)): 初始化游戏选择界面,设置窗口的尺寸、图标和标题。
GameSelect::GameSelect(QWidget *parent) : QWidget(parent)
{
    this->setFixedSize(1000, 800); // 设置窗口大小
    this->setWindowIcon(QIcon(":res/ico.jpg"));
    this->setWindowTitle("游戏选择界面");


    // 创建并设置返回键的位置
    QPushButton* backBtn = new QPushButton(this);
    backBtn->move(80, 50);
    backBtn->setIcon(QIcon(":res/back.png"));
    backBtn->setToolTip("点击此按钮或按键盘Esc键返回游戏大厅界面");
    backBtn->setShortcut(Qt::Key_Escape);
    backBtn->setIconSize(QSize(50, 50)); // 设置图标大小

    // 设置样式表
    ButtonStyle::setCommonStyle_write(backBtn, 26);

    connect(backBtn, &QPushButton::clicked, [=](){
        this->close();
        QSound::play(":res/clicked.wav");
        GameHall* gameHall = new GameHall;
        gameHall->show();
    });


    GameRoom* gameRoom = new GameRoom;

    QPushButton* simpleBtn = new QPushButton(this);
    simpleBtn->move(420, 300);
    simpleBtn->setText("简单模式");
    simpleBtn->setToolTip("点击此按钮或按数字键1进入简单模式");
    simpleBtn->setShortcut(Qt::Key_1);
    ButtonStyle::setCommonStyle_green(simpleBtn, 26);
    connect(simpleBtn, &QPushButton::clicked, [=](){
        QSound::play(":res/clicked.wav");
        this->close();
        gameRoom->setGeometry(this->geometry());
        gameRoom->show();

        gameRoom->setTimeout(300);
    });


    QPushButton* normalBtn = new QPushButton(this);
    normalBtn->move(420, 380);
    normalBtn->setText("常规模式");
    normalBtn->setToolTip("点击此按钮或按数字键2进入常规模式");
    normalBtn->setShortcut(Qt::Key_2);
    ButtonStyle::setCommonStyle_green(normalBtn, 26);
    connect(normalBtn, &QPushButton::clicked, [=](){
        QSound::play(":res/clicked.wav");
        this->close();
        gameRoom->setGeometry(this->geometry());
        gameRoom->show();

        gameRoom->setTimeout(200);
    });


    QPushButton* hardBtn = new QPushButton(this);
    hardBtn->move(420, 460);
    hardBtn->setText("困难模式");
    hardBtn->setToolTip("点击此按钮或按数字键3进入困难模式");
    hardBtn->setShortcut(Qt::Key_3);
    ButtonStyle::setCommonStyle_green(hardBtn, 26);
    connect(hardBtn, &QPushButton::clicked, [=](){
        QSound::play(":res/clicked.wav");
        this->close();
        gameRoom->setGeometry(this->geometry());
        gameRoom->show();

        gameRoom->setTimeout(100);
    });

    QPushButton* hisBtn = new QPushButton(this);
    hisBtn->move(420, 560);
    hisBtn->setText("历史战绩");
    hisBtn->setToolTip("点击此按钮或按键盘H查看历史记录");
    hisBtn->setShortcut(Qt::Key_H);
    ButtonStyle::setCommonStyle_write(hisBtn, 26);

    connect(hisBtn, &QPushButton::clicked, [=](){
        QWidget* widget = new QWidget;
        widget->setWindowTitle("历史战绩");
        widget->setFixedSize(500, 300);

        QTextEdit* edit = new QTextEdit(widget);
        QFont font("微软雅黑", 24);
        edit->setFont(font);
        edit->setFixedSize(500, 300);

        QFile file("D:/Acode/class111/test_-qt/Qt/greedy_snake/snake/history.txt");
        file.open(QIODevice::ReadOnly);

        QTextStream in(&file);
        int data = in.readLine().toInt();

        edit->append("得分为: ");
        edit->append(QString::number(data));
        widget->show();
    });

}
  1. 按钮创建:为每种游戏难度创建QPushButton实例,并设置它们的位置、文本、工具提示和快捷键。这些按钮允许玩家通过点击或按下相应的数字键来选择游戏模式。

  2. 按钮样式设置:使用ButtonStyle::setCommonStyle_green函数为游戏模式按钮设置样式,使用不同的颜色和字体大小来区分按钮。

  3. 信号连接:将每个游戏模式按钮的clicked信号连接到槽函数。在这些槽函数中,播放点击声音,关闭当前的选择界面,并根据所选难度设置蛇的移动速度,然后启动游戏房间界面。

  4. 历史战绩功能:为历史战绩按钮设置信号连接,当点击时,读取存储在文件中的最高分,并在一个新窗口中显示。

  5. 绘图事件处理 (GameSelect::paintEvent(QPaintEvent *event)): 重写paintEvent以在界面上绘制背景图像,增强视觉效果。

void GameSelect::paintEvent(QPaintEvent *event)
{
    Q_UNUSED(event);

    QPainter painter(this); // 实例化画图对象

    QPixmap pix(":res/game_select.jpg");
    painter.drawPixmap(0, 0, this->width(), this->height(), pix);
}
  1. 快捷键设置:为返回按钮和每个游戏模式按钮设置了快捷键,提供了快捷的操作方式。

  2. 声音效果:在界面转换和按钮点击时播放声音效果,增强了交互体验。

3.3 游戏房间(GameRoom)

3.3.1 游戏逻辑和核心算法解析

GameRoom 类是实现贪吃蛇游戏逻辑的核心,包括蛇的移动、食物生成和游戏失败条件的检测。

  1. 初始化蛇的状态:在构造函数中,初始化蛇的位置和状态,蛇的身体由一个QList<QRectF>来表示,其中QRectF是定义蛇身体每个节点的矩形区域。

  2. 食物生成:通过createNewFood()函数在游戏区域内随机生成食物,食物也是一个QRectF对象。

  3. 蛇的移动:蛇的移动通过moveUp()moveDown()moveLeft()moveRight()函数实现,这些函数根据蛇的当前移动方向来更新蛇身体的位置。

  4. 游戏循环:使用QTimer来控制游戏循环和蛇的移动频率。timertimeout信号触发时,蛇会根据当前方向移动,并且检查是否吃掉食物或撞到自己或墙壁。

  5. 游戏失败条件:通过checkFail()函数检查蛇是否撞到自己或墙壁,如果是,则游戏结束。

  6. 绘制游戏元素:在paintEvent()中使用QPainterQPixmap绘制蛇的身体、食物和游戏背景。
    在这里插入图片描述

3.3.2 代码实现细节

  1. 构造函数 (GameRoom::GameRoom(QWidget *parent)): 初始化游戏房间,设置窗口大小、图标和标题,初始化蛇的位置,创建食物和定时器。
GameRoom::GameRoom(QWidget *parent) : QWidget(parent)
{
    this->setFixedSize(1000, 800); //设置窗口大小
    this->setWindowIcon(QIcon(":res/ico.jpg")); // 设置窗口图标
    this->setWindowTitle("贪吃蛇游戏");

    // 初始化贪吃蛇
    snakeList.push_back(QRectF(this->width() / 2, this->height() / 2, kSnakeNodeWidth, kSnakeNodeHeight));

    moveUp();
    moveUp();

    // 创建食物
    createNewFood();

    timer = new QTimer(this);

    connect(timer, &QTimer::timeout, [=](){
        int cnt = 1; // 默认移动一个
        if (snakeList.front().intersects(foodRect)) // 判断蛇头是否与蛇相交
        {
            createNewFood();
            cnt++; // 有食物相交的话移动2个
        }

        while(cnt--)
        {
            switch (moveDirect) {
            case SnakeDirect::UP:
                moveUp();
                break;
            case SnakeDirect::DOWN:
                moveDown();
                break;
            case SnakeDirect::LEFT:
                moveLeft();
                break;
            case SnakeDirect::RIGHT:
                moveRight();
                break;
            default:
                break;
            }
        }

        snakeList.pop_back(); // 删除最后一个节点
        update();
    });

    // 开始游戏 暂停游戏
    QPushButton* strBtn = new QPushButton(this);
    strBtn->move(840, 120);
    strBtn->setText("开始/暂停");
    strBtn->setToolTip("点击此按钮或按键盘P键,切换游戏开始和暂停");
    strBtn->setShortcut(Qt::Key_P);
    ButtonStyle::setCommonStyle_green(strBtn, 20);

    connect(strBtn, &QPushButton::clicked, [=](){
        if (isGameStart == false)
        {
            isGameStart = true;
            timer->start(moveTimeout);
            sound = new QSound(":res/Trepak.wav");
            sound->play();
            sound->setLoops(-1);