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

打印 上一主题 下一主题

主题 548|帖子 548|积分 1644

引言

贪吃蛇游戏是一款历史悠久且广受欢迎的经典电子游戏,最早可以追溯到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.csdn.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 游戏界面设计概述

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

  • 游戏区域:这是显示蛇和食品的紧张区域,通常是一个矩形的网格。
  • 开始/暂停按钮:答应玩家开始游戏或在游戏举行中暂停。
  • 退出按钮:玩家可以通过点击退出按钮来竣事当前游戏。
  • 分数显示:显示玩产业前的得分,通常位于游戏区域的上方或角落。
  • 游戏难度选择:可能包罗差别难度级别的选择,影响食品的出现频率或蛇的移动速度。
  • 历史战绩:显示玩家已往游戏的最高分或其他历史记录。
  • 声音效果:游戏中可能会包罗点击按钮的声音反馈和游戏竣事时的特殊音效。
这些界面元素通过Qt的各种组件实现,例如使用QPushButton来创建按钮,使用QPainter和QPixmap来绘制游戏区域的图形,以及使用QTimer来控制游戏的革新率和蛇的移动速度。此外,界面的布局和样式通过Qt的布局管理器和样式表来设计,确保界面的美观和用户友好性。
3. 核心代码分析

3.1 主界面(GameHall)

3.1.1 布局和功能介绍

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


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

3.1.2 代码实现分析

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

  • 构造函数 (GameHall::GameHall(QWidget *parent)): 初始化主界面,设置窗口大小、图标和标题。使用Ui::GameHall定名空间中的类来加载和设置用户界面布局。
  1. GameHall::GameHall(QWidget *parent)
  2.     : QWidget(parent)
  3.     , ui(new Ui::GameHall)
  4. {
  5.     ui->setupUi(this);
  6.     this->setFixedSize(1000, 800); //设置窗口大小
  7.     this->setWindowIcon(QIcon(":res/ico.jpg")); // 设置窗口图标
  8.     this->setWindowTitle("贪吃蛇游戏");
  9.     QPushButton* strBtn = new QPushButton(this);
  10.     strBtn->move(410, 500);
  11.     strBtn->setText("开始游戏");
  12.     strBtn->setToolTip("这是游戏开始按钮,鼠标点击或者回车键进入!");
  13.     strBtn->setToolTipDuration(3000);
  14.     ButtonStyle::setCommonStyle_green(strBtn, 30);
  15.     // 为按钮设置回车快捷键
  16.     strBtn->setShortcut(Qt::Key_Return);
  17.     GameSelect* gameSelect = new GameSelect;
  18.     connect(strBtn, &QPushButton::clicked, [=](){
  19.         QSound::play(":res/clicked.wav");
  20.         this->close();
  21.         gameSelect->setGeometry(this->geometry()); // 第二个窗口和第一个窗口一样大
  22.         gameSelect->show();
  23.     });
复制代码
}

  • 按钮创建与布局:创建一个QPushButton实例,设置其位置、文本、工具提示和样式。按钮的位置使用move方法举行定位,文本设置为“开始游戏”,工具提示提供了额外的使用说明。
  • 按钮样式设置:使用ButtonStyle::setCommonStyle_green函数来设置按钮的样式,例如背景颜色、边框、字体大小等。这为按钮提供了同一的表面和感觉。
  • 信号连接:通过Qt的信号和槽机制,将“开始游戏”按钮的clicked信号连接到一个槽函数,当按钮被点击时实行。在这个槽函数中,播放点击声音,关闭当前的主界面,并展示GameSelect窗口,这是选择游戏模式的界面。
  • 绘图事件处置惩罚 (GameHall::paintEvent(QPaintEvent *event)): 重写paintEvent以自定义绘图逻辑。使用QPainter和QPixmap在窗口上绘制背景图像,创建更加吸引人的视觉效果。
  1. void GameHall::paintEvent(QPaintEvent *event)
  2. {
  3.      Q_UNUSED(event); // 告诉编译器event参数是未使用的
  4.     // 实例化画家
  5.     QPainter painter(this);
  6.     // 实例化绘图设备
  7.     QPixmap pix(":res/game_hall.jpg");
  8.     // 绘画
  9.     painter.drawPixmap(0,0, this->width(), this->height(), pix);
  10. }
复制代码

  • 析构函数 (GameHall::~GameHall()): 整理并删除由GameHall类分配的资源,特别是ui成员,这是用户界面的布局对象。
  1. GameHall::~GameHall()
  2. {
  3.     delete ui;
  4. }
复制代码
3.2 游戏选择界面(GameSelect)

3.2.1 功能介绍

GameSelect 类构成了贪吃蛇游戏的选择界面,答应玩家根据本身的喜好选择差别的游戏难度。这个界面通常包罗以下几个关键功能:


  • 窗口尺寸与图标:设置窗口的大小和图标,与主界面雷同,提供了同等的用户体验。
  • 游戏模式按钮:提供了差别难度级别的按钮,如“简朴模式”、“常规模式”和“困难模式”,每个按钮都对应一个特定的蛇移动速度。
  • 历史战绩按钮:答应玩家查看他们之前的游戏得分记录。
  • 返回按钮:一个返回到主界面的按钮,答应玩家在任何时候返回到游戏的出发点。

3.2.2 代码实现分析

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

  • 构造函数 (GameSelect::GameSelect(QWidget *parent)): 初始化游戏选择界面,设置窗口的尺寸、图标和标题。
  1. GameSelect::GameSelect(QWidget *parent) : QWidget(parent)
  2. {
  3.     this->setFixedSize(1000, 800); // 设置窗口大小
  4.     this->setWindowIcon(QIcon(":res/ico.jpg"));
  5.     this->setWindowTitle("游戏选择界面");
  6.     // 创建并设置返回键的位置
  7.     QPushButton* backBtn = new QPushButton(this);
  8.     backBtn->move(80, 50);
  9.     backBtn->setIcon(QIcon(":res/back.png"));
  10.     backBtn->setToolTip("点击此按钮或按键盘Esc键返回游戏大厅界面");
  11.     backBtn->setShortcut(Qt::Key_Escape);
  12.     backBtn->setIconSize(QSize(50, 50)); // 设置图标大小
  13.     // 设置样式表
  14.     ButtonStyle::setCommonStyle_write(backBtn, 26);
  15.     connect(backBtn, &QPushButton::clicked, [=](){
  16.         this->close();
  17.         QSound::play(":res/clicked.wav");
  18.         GameHall* gameHall = new GameHall;
  19.         gameHall->show();
  20.     });
  21.     GameRoom* gameRoom = new GameRoom;
  22.     QPushButton* simpleBtn = new QPushButton(this);
  23.     simpleBtn->move(420, 300);
  24.     simpleBtn->setText("简单模式");
  25.     simpleBtn->setToolTip("点击此按钮或按数字键1进入简单模式");
  26.     simpleBtn->setShortcut(Qt::Key_1);
  27.     ButtonStyle::setCommonStyle_green(simpleBtn, 26);
  28.     connect(simpleBtn, &QPushButton::clicked, [=](){
  29.         QSound::play(":res/clicked.wav");
  30.         this->close();
  31.         gameRoom->setGeometry(this->geometry());
  32.         gameRoom->show();
  33.         gameRoom->setTimeout(300);
  34.     });
  35.     QPushButton* normalBtn = new QPushButton(this);
  36.     normalBtn->move(420, 380);
  37.     normalBtn->setText("常规模式");
  38.     normalBtn->setToolTip("点击此按钮或按数字键2进入常规模式");
  39.     normalBtn->setShortcut(Qt::Key_2);
  40.     ButtonStyle::setCommonStyle_green(normalBtn, 26);
  41.     connect(normalBtn, &QPushButton::clicked, [=](){
  42.         QSound::play(":res/clicked.wav");
  43.         this->close();
  44.         gameRoom->setGeometry(this->geometry());
  45.         gameRoom->show();
  46.         gameRoom->setTimeout(200);
  47.     });
  48.     QPushButton* hardBtn = new QPushButton(this);
  49.     hardBtn->move(420, 460);
  50.     hardBtn->setText("困难模式");
  51.     hardBtn->setToolTip("点击此按钮或按数字键3进入困难模式");
  52.     hardBtn->setShortcut(Qt::Key_3);
  53.     ButtonStyle::setCommonStyle_green(hardBtn, 26);
  54.     connect(hardBtn, &QPushButton::clicked, [=](){
  55.         QSound::play(":res/clicked.wav");
  56.         this->close();
  57.         gameRoom->setGeometry(this->geometry());
  58.         gameRoom->show();
  59.         gameRoom->setTimeout(100);
  60.     });
  61.     QPushButton* hisBtn = new QPushButton(this);
  62.     hisBtn->move(420, 560);
  63.     hisBtn->setText("历史战绩");
  64.     hisBtn->setToolTip("点击此按钮或按键盘H查看历史记录");
  65.     hisBtn->setShortcut(Qt::Key_H);
  66.     ButtonStyle::setCommonStyle_write(hisBtn, 26);
  67.     connect(hisBtn, &QPushButton::clicked, [=](){
  68.         QWidget* widget = new QWidget;
  69.         widget->setWindowTitle("历史战绩");
  70.         widget->setFixedSize(500, 300);
  71.         QTextEdit* edit = new QTextEdit(widget);
  72.         QFont font("微软雅黑", 24);
  73.         edit->setFont(font);
  74.         edit->setFixedSize(500, 300);
  75.         QFile file("D:/Acode/class111/test_-qt/Qt/greedy_snake/snake/history.txt");
  76.         file.open(QIODevice::ReadOnly);
  77.         QTextStream in(&file);
  78.         int data = in.readLine().toInt();
  79.         edit->append("得分为: ");
  80.         edit->append(QString::number(data));
  81.         widget->show();
  82.     });
  83. }
复制代码

  • 按钮创建:为每种游戏难度创建QPushButton实例,并设置它们的位置、文本、工具提示和快捷键。这些按钮答应玩家通过点击或按下相应的数字键来选择游戏模式。
  • 按钮样式设置:使用ButtonStyle::setCommonStyle_green函数为游戏模式按钮设置样式,使用差别的颜色和字体大小来区分按钮。
  • 信号连接:将每个游戏模式按钮的clicked信号连接到槽函数。在这些槽函数中,播放点击声音,关闭当前的选择界面,并根据所选难度设置蛇的移动速度,然后启动游戏房间界面。
  • 历史战绩功能:为历史战绩按钮设置信号连接,当点击时,读取存储在文件中的最高分,并在一个新窗口中显示。
  • 绘图事件处置惩罚 (GameSelect::paintEvent(QPaintEvent *event)): 重写paintEvent以在界面上绘制背景图像,加强视觉效果。
  1. void GameSelect::paintEvent(QPaintEvent *event)
  2. {
  3.     Q_UNUSED(event);
  4.     QPainter painter(this); // 实例化画图对象
  5.     QPixmap pix(":res/game_select.jpg");
  6.     painter.drawPixmap(0, 0, this->width(), this->height(), pix);
  7. }
复制代码

  • 快捷键设置:为返回按钮和每个游戏模式按钮设置了快捷键,提供了快捷的操作方式。
  • 声音效果:在界面转换和按钮点击时播放声音效果,加强了交互体验。
3.3 游戏房间(GameRoom)

3.3.1 游戏逻辑和核默算法分析

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

  • 初始化蛇的状态:在构造函数中,初始化蛇的位置和状态,蛇的身体由一个QList<QRectF>来表示,其中QRectF是定义蛇身体每个节点的矩形区域。
  • 食品生成:通过createNewFood()函数在游戏区域内随机生成食品,食品也是一个QRectF对象。
  • 蛇的移动:蛇的移动通过moveUp()、moveDown()、moveLeft()和moveRight()函数实现,这些函数根据蛇的当前移动方向来更新蛇身体的位置。
  • 游戏循环:使用QTimer来控制游戏循环和蛇的移动频率。timer的timeout信号触发时,蛇会根据当火线向移动,而且检查是否吃掉食品或撞到本身或墙壁。
  • 游戏失败条件:通过checkFail()函数检查蛇是否撞到本身或墙壁,假如是,则游戏竣事。
  • 绘制游戏元素:在paintEvent()中使用QPainter和QPixmap绘制蛇的身体、食品和游戏背景。

3.3.2 代码实现细节


  • 构造函数 (GameRoom::GameRoom(QWidget *parent)): 初始化游戏房间,设置窗口大小、图标和标题,初始化蛇的位置,创建食品和定时器。
  1. GameRoom::GameRoom(QWidget *parent) : QWidget(parent)
  2. {
  3.     this->setFixedSize(1000, 800); //设置窗口大小
  4.     this->setWindowIcon(QIcon(":res/ico.jpg")); // 设置窗口图标
  5.     this->setWindowTitle("贪吃蛇游戏");
  6.     // 初始化贪吃蛇
  7.     snakeList.push_back(QRectF(this->width() / 2, this->height() / 2, kSnakeNodeWidth, kSnakeNodeHeight));
  8.     moveUp();
  9.     moveUp();
  10.     // 创建食物
  11.     createNewFood();
  12.     timer = new QTimer(this);
  13.     connect(timer, &QTimer::timeout, [=](){
  14.         int cnt = 1; // 默认移动一个
  15.         if (snakeList.front().intersects(foodRect)) // 判断蛇头是否与蛇相交
  16.         {
  17.             createNewFood();
  18.             cnt++; // 有食物相交的话移动2个
  19.         }
  20.         while(cnt--)
  21.         {
  22.             switch (moveDirect) {
  23.             case SnakeDirect::UP:
  24.                 moveUp();
  25.                 break;
  26.             case SnakeDirect::DOWN:
  27.                 moveDown();
  28.                 break;
  29.             case SnakeDirect::LEFT:
  30.                 moveLeft();
  31.                 break;
  32.             case SnakeDirect::RIGHT:
  33.                 moveRight();
  34.                 break;
  35.             default:
  36.                 break;
  37.             }
  38.         }
  39.         snakeList.pop_back(); // 删除最后一个节点
  40.         update();
  41.     });
  42.     // 开始游戏 暂停游戏
  43.     QPushButton* strBtn = new QPushButton(this);
  44.     strBtn->move(840, 120);
  45.     strBtn->setText("开始/暂停");
  46.     strBtn->setToolTip("点击此按钮或按键盘P键,切换游戏开始和暂停");
  47.     strBtn->setShortcut(Qt::Key_P);
  48.     ButtonStyle::setCommonStyle_green(strBtn, 20);
  49.     connect(strBtn, &QPushButton::clicked, [=](){
  50.         if (isGameStart == false)
  51.         {
  52.             isGameStart = true;
  53.             timer->start(moveTimeout);
  54.             sound = new QSound(":res/Trepak.wav");
  55.             sound->play();
  56.             sound->setLoops(-1);
  57.         }
  58.         else
  59.         {
  60.             isGameStart = false;
  61.             timer->stop();
  62.             sound->stop();
  63.         }
  64.     });
  65.     QPushButton* backBtn = new QPushButton(this);
  66.     backBtn->move(855, 200);
  67.     backBtn->setIcon(QIcon(":res/back.png"));
  68.     backBtn->setToolTip("点击此按钮或按键盘Esc键返回游戏大厅界面");
  69.     backBtn->setShortcut(Qt::Key_Escape);
  70.     backBtn->setIconSize(QSize(50, 50)); // 设置图标大小
  71.     // 设置样式表
  72.     ButtonStyle::setCommonStyle_write(backBtn, 26);
  73.     connect(backBtn, &QPushButton::clicked, [=](){
  74.         this->close();
  75.         QSound::play(":res/clicked.wav");
  76.         GameSelect* gameselect = new GameSelect;
  77.         gameselect->show();
  78.         if (isGameStart == true) {
  79.             isGameStart = false;
  80.             timer->stop();
  81.             sound->stop();
  82.         }
  83.     });
  84.     // 方向控制
  85.     QPushButton* up = new QPushButton(this);
  86.     QPushButton* down = new QPushButton(this);
  87.     QPushButton* left = new QPushButton(this);
  88.     QPushButton* right = new QPushButton(this);
  89.     // 为每个按钮创建快捷键
  90.     QShortcut* upShortcut = new QShortcut(QKeySequence(Qt::Key_Up), this);
  91.     QShortcut* downShortcut = new QShortcut(QKeySequence(Qt::Key_Down), this);
  92.     QShortcut* leftShortcut = new QShortcut(QKeySequence(Qt::Key_Left), this);
  93.     QShortcut* rightShortcut = new QShortcut(QKeySequence(Qt::Key_Right), this);
  94.     // 连接快捷键的activated信号到按钮的clicked信号
  95.     connect(upShortcut, SIGNAL(activated()), up, SLOT(animateClick()));
  96.     connect(downShortcut, SIGNAL(activated()), down, SLOT(animateClick()));
  97.     connect(leftShortcut, SIGNAL(activated()), left, SLOT(animateClick()));
  98.     connect(rightShortcut, SIGNAL(activated()), right, SLOT(animateClick()));
  99.     up->move(875, 405);
  100.     down->move(875,455);
  101.     left->move(810,455);
  102.     right->move(930,455);
  103.     up->setText("↑");
  104.     ButtonStyle::setCommonStyle_green(up,20);
  105.     down->setText("↓");
  106.     ButtonStyle::setCommonStyle_green(down,20);
  107.     left->setText("←");
  108.     ButtonStyle::setCommonStyle_green(left,20);
  109.     right->setText("→");
  110.     ButtonStyle::setCommonStyle_green(right,20);
  111.     connect(up, &QPushButton::clicked, [=](){
  112.         if (moveDirect != SnakeDirect::DOWN)
  113.         {
  114.             moveDirect = SnakeDirect::UP;
  115.         }
  116.     });
  117.     connect(down, &QPushButton::clicked,[=](){
  118.         if (moveDirect != SnakeDirect::UP)
  119.         {
  120.             moveDirect = SnakeDirect::DOWN;
  121.         }
  122.     });
  123.     connect(left, &QPushButton::clicked, [=](){
  124.         if (moveDirect != SnakeDirect::RIGHT)
  125.         {
  126.             moveDirect = SnakeDirect::LEFT;
  127.         }
  128.     });
  129.     connect(right, &QPushButton::clicked, [=](){
  130.         if (moveDirect != SnakeDirect::LEFT)
  131.         {
  132.             moveDirect = SnakeDirect::RIGHT;
  133.         }
  134.     });
  135.     // 退出游戏
  136.     QPushButton* ExitBtn = new QPushButton(this);
  137.     ExitBtn->move(860, 700);
  138.     ExitBtn->setText("退出");
  139.     ButtonStyle::setCommonStyle_green(ExitBtn, 20);
  140.     QMessageBox* messageBox = new QMessageBox(this);
  141.     QPushButton* ok = new QPushButton("ok");
  142.     QPushButton* cancel = new QPushButton("cancel");
  143.     messageBox->addButton(ok, QMessageBox::AcceptRole);
  144.     messageBox->addButton(cancel, QMessageBox::RejectRole);
  145.     messageBox->setWindowTitle("警告");
  146.     messageBox->setText("是否确认退出!");
  147.     messageBox->setIcon(QMessageBox::Warning);
  148.     connect(ExitBtn, &QPushButton::clicked, [=](){
  149.         messageBox->show();
  150.         messageBox->exec(); // 事件轮询
  151.         QSound::play(":res/clicked.wav");
  152.         if(messageBox->clickedButton() == ok)
  153.         {
  154.             this->close();
  155.         }
  156.         else
  157.         {
  158.             messageBox->close();
  159.         }
  160.     });
  161. }
复制代码

  • 蛇的移动
    移动函数通过计算蛇头的新位置并更新snakeList来实现蛇的移动逻辑。
    移动后,蛇的尾部节点被移除,模拟蛇的移动效果。
  1. void GameRoom::moveUp()
  2. {
  3.     QPointF leftTop; // 左上角坐标
  4.     QPointF rightBottom; // 右下角坐标
  5.     auto snakeNode = snakeList.front(); //头
  6.     int headX = snakeNode.x();
  7.     int headY = snakeNode.y();
  8.     if (headY < 0) // 穿墙了
  9.     {
  10.         leftTop = QPointF(headX, this->height() - kSnakeNodeHeight);
  11.     }
  12.     else
  13.     {
  14.         leftTop = QPointF(headX, headY - kSnakeNodeHeight);
  15.     }
  16.     rightBottom = leftTop + QPointF(kSnakeNodeWidth, kSnakeNodeHeight);
  17.     snakeList.push_front(QRectF(leftTop, rightBottom));
  18. }
复制代码

  • 食品生成
    createNewFood()函数在随机位置生成食品,确保食品不会与蛇的身体重叠。
  1. void GameRoom::createNewFood()
  2. {
  3.     foodRect = QRectF(qrand() % (800/ kSnakeNodeWidth) * kSnakeNodeWidth,
  4.                       qrand() % (this->height() / kSnakeNodeHeight) * kSnakeNodeHeight,
  5.                       kSnakeNodeWidth,
  6.                       kSnakeNodeHeight);
  7. }
复制代码

  • 定时器和游戏循环
    通过QTimer的timeout信号,周期性地触发蛇的移动。
    假如蛇吃掉食品,增长蛇的长度并生成新的食品。
  1. // 创建食物
  2. createNewFood();
  3. timer = new QTimer(this);
  4. connect(timer, &QTimer::timeout, [=](){
  5.     int cnt = 1; // 默认移动一个
  6.     if (snakeList.front().intersects(foodRect)) // 判断蛇头是否与蛇相交
  7.     {
  8.         createNewFood();
  9.         cnt++; // 有食物相交的话移动2个
  10.     }
  11.     while(cnt--)
  12.     {
  13.         switch (moveDirect) {
  14.         case SnakeDirect::UP:
  15.             moveUp();
  16.             break;
  17.         case SnakeDirect::DOWN:
  18.             moveDown();
  19.             break;
  20.         case SnakeDirect::LEFT:
  21.             moveLeft();
  22.             break;
  23.         case SnakeDirect::RIGHT:
  24.             moveRight();
  25.             break;
  26.         default:
  27.             break;
  28.         }
  29.     }
  30.     snakeList.pop_back(); // 删除最后一个节点
  31.     update();
  32. });
复制代码

  • 游戏失败条件
    checkFail()函数通过比较蛇的每个节点来检测是否有自我碰撞或撞墙的环境。
  1. bool GameRoom::checkFail()
  2. {
  3.     // 遍历蛇的身体节点,检查是否有重叠
  4.     for (int i = 0; i < snakeList.size(); ++i)
  5.     {
  6.         for (int j = i + 1; j < snakeList.size(); ++j)
  7.         {
  8.             if (snakeList.at(i) == snakeList.at(j))
  9.             {
  10.                 // 发现重叠,游戏失败
  11.                 return true;
  12.             }
  13.         }
  14.     }
  15.     // 没有发现重叠,游戏继续
  16.     return false;
  17. }
复制代码

  • 绘制逻辑 (GameRoom::paintEvent(QPaintEvent *event)):
    使用QPainter绘制游戏背景、蛇的身体、食品和分数。
    根据蛇的当前移动方向选择相应的蛇头图像。
  1. void GameRoom::paintEvent(QPaintEvent *event)
  2. {
  3.     Q_UNUSED(event);
  4.     QPainter painter(this); // 实例化画家对象
  5.     QPixmap pix;
  6.     pix.load(":res/game_room.jpg");
  7.     painter.drawPixmap(0, 0, 800, 800, pix);
  8.     pix.load(":res/bg1.jpeg");
  9.     painter.drawPixmap(800, 0, 200, 800, pix);
  10.     // 绘制蛇:蛇头 + 蛇身体 + 蛇尾巴
  11.     // 绘制蛇头:上 下 左 右
  12.     if(moveDirect == SnakeDirect::UP)
  13.     {
  14.         pix.load(":res/up.png");
  15.     }
  16.     else if (moveDirect == SnakeDirect::DOWN)
  17.     {
  18.         pix.load(":res/down.png");
  19.     }
  20.     else if (moveDirect == SnakeDirect::LEFT)
  21.     {
  22.         pix.load(":res/left.png");
  23.     }
  24.     else if (moveDirect == SnakeDirect::RIGHT)
  25.     {
  26.         pix.load(":res/right.png");
  27.     }
  28.     auto snakeHead = snakeList.front();
  29.     painter.drawPixmap(snakeHead.x(), snakeHead.y(), snakeHead.width(), snakeHead.height(), pix);
  30.     // 绘制蛇身
  31.     pix.load(":res/Bd.png");
  32.     for (int i = 1; i < snakeList.size()-1; ++i)
  33.     {
  34.         auto node = snakeList.at(i);
  35.         painter.drawPixmap(node.x(), node.y(), node.width(), node.height(), pix);
  36.     }
  37.     // 绘制蛇尾巴
  38.     auto snakeTail = snakeList.back();
  39.     painter.drawPixmap(snakeTail.x(), snakeTail.y(), snakeTail.width(), snakeTail.height(), pix);
  40.     // 绘制食物
  41.     pix.load(":res/food.png");
  42.     painter.drawPixmap(foodRect.x(), foodRect.y(), foodRect.width(), foodRect.height(), pix);
  43.     // 绘制分数
  44.     pix.load(":res/sorce_bg.png");
  45.     painter.drawPixmap(this->width()*0.85, this->height()*0.06, 90, 40, pix);
  46.     QPen pen;
  47.     pen.setColor(Qt::black);
  48.     painter.setPen(pen);
  49.     QFont font("微软雅黑");
  50.     font.setPixelSize(24); // 设置字体大小为20像素
  51.     painter.setFont(font);
  52.     painter.drawText(this->width()*0.9, this->height()*0.1, QString("%1").arg(snakeList.size()));
  53.     // 往文件中写分数
  54.     int c = snakeList.size();
  55.     QFile file("D:/Acode/class111/test_-qt/Qt/greedy_snake/snake/history.txt");
  56.     if (file.open(QIODevice::WriteOnly | QIODevice::Text))
  57.     {
  58.         QTextStream out(&file);
  59.         out << c;
  60.         qDebug() << c;
  61.         file.close();
  62.     } else {
  63.          qDebug() << "文件打开失败";
  64.     }
  65.     // 绘制游戏失败效果
  66.     if (checkFail())
  67.     {
  68.         pen.setColor(Qt::red);
  69.         painter.setPen(pen);
  70.         QFont font("微软雅黑", 50);
  71.         painter.drawText(this->width()*0.45, this->height()*0.5, QString("GAME OVER!"));
  72.         timer->stop();
  73.         QSound::play(":res/gameover.wav");
  74.     }
  75. }
复制代码

  • 分数和游戏状态显示
    在游戏界面上绘制当前分数,通常基于蛇的长度。
    假如游戏失败,显示“GAME OVER!”并制止游戏循环。
  1.     // 往文件中写分数
  2.     int c = snakeList.size();
  3.     QFile file("D:/Acode/class111/test_-qt/Qt/greedy_snake/snake/history.txt");
  4.     if (file.open(QIODevice::WriteOnly | QIODevice::Text))
  5.     {
  6.         QTextStream out(&file);
  7.         out << c;
  8.         qDebug() << c;
  9.         file.close();
  10.     } else {
  11.          qDebug() << "文件打开失败";
  12.     }
  13.     // 绘制游戏失败效果
  14.     if (checkFail())
  15.     {
  16.         pen.setColor(Qt::red);
  17.         painter.setPen(pen);
  18.         QFont font("微软雅黑", 50);
  19.         painter.drawText(this->width()*0.45, this->height()*0.5, QString("GAME OVER!"));
  20.         timer->stop();
  21.         QSound::play(":res/gameover.wav");
  22.     }
  23. }
复制代码
3.4 按钮样式(ButtonStyle)

3.4.1 代码实现


  • 类定义 (buttonstyle.h): 声明了两个静态方法,用于设置差别风格的按钮样式。
  1. class ButtonStyle {
  2. public:
  3.     static void setCommonStyle_green(QPushButton *button, size_t font_size);
  4.     static void setCommonStyle_write(QPushButton *button, size_t font_size);
  5. };
复制代码

  • 样式设置 (buttonstyle.cpp): 实现了上述声明的方法,使用 Qt 的样式表(styleSheet)来定义按钮的视觉样式。
  1. void ButtonStyle::setCommonStyle_green(QPushButton *button, size_t font_size) {
  2.     QString styleSheet = R"(
  3.         QPushButton {
  4.             background-color: #4CAF50;
  5.             border: 2px solid #4CAF50;
  6.             border-radius: 10px;
  7.             font: bold %1px '微软雅黑';
  8.             color: white;
  9.             padding: 8px;
  10.             padding-left: 20px;
  11.             padding-right: 20px;
  12.         }
  13.         QPushButton:hover {
  14.             background-color: #45a049;
  15.         }
  16.         QPushButton:pressed {
  17.             background-color: #3e8e41;
  18.         }
  19.     )";
  20.     button->setStyleSheet(styleSheet.arg(font_size));
  21. }
  22. void ButtonStyle::setCommonStyle_write(QPushButton *button, size_t font_size) {
  23.     QString styleSheet = R"(
  24.         QPushButton {
  25.             font: bold %1px '微软雅黑';
  26.             border: 2px solid #a3a3a3;
  27.             border-radius: 16px;
  28.             background-color: rgba(255, 255, 255, 0);
  29.             padding: 8px;
  30.             padding-left: 20px;
  31.             padding-right: 20px;
  32.         }
  33.         QPushButton:hover {
  34.             background-color: rgba(180, 180, 180, 0.3);
  35.         }
  36.         QPushButton:pressed {
  37.             background-color: rgba(180, 180, 180, 0.4);
  38.         }
  39.     )";
  40.     button->setStyleSheet(styleSheet.arg(font_size));
  41. }
复制代码
3.4.2 实现效果


  • 绿色按钮样式 (setCommonStyle_green): 这种方法设置了一个绿色主题的按钮样式,具有以下特点:
    背景颜色为绿色(#4CAF50)。
    边框为绿色,宽度为2px。
    字体为粗体,使用微软雅黑字体,字体大小由传入的 font_size 参数决定。
    按钮文本颜色为白色。
    按钮具有圆角和内边距。
    当鼠标悬停在按钮上时,背景颜色会变暗(#45a049),按下时颜色会更暗(#3e8e41)。

  • 白色按钮样式 (setCommonStyle_write): 这种方法设置了一个白色半透明主题的按钮样式,特点包罗:
    字体和大小设置与绿色按钮雷同。
    边框为灰色,宽度为2px。
    背景颜色为白色,且具有透明度(rgba(255, 255, 255, 0))。
    按钮具有更大的圆角和内边距。
    鼠标悬停时,背景颜色会变为半透明的浅灰色(rgba(180, 180, 180, 0.3)),按下时透明度更高(rgba(180, 180, 180, 0.4))。

    通过使用 ButtonStyle 类,贪吃蛇游戏中的所有按钮都可以轻松地应用同一和专业的表面,加强了用户界面的同等性和美观性。
4. 图形和音效

4.1 图形资源

在贪吃蛇游戏中,图形资源紧张包罗:

  • 游戏背景:这是游戏的紧张背景图像,为玩家提供视觉上的上下文环境。
  • 蛇的身体:蛇的身体通常由一系列矩形或圆形的图形组成,这些图形在游戏区域内根据蛇的移动而更新位置。
  • 食品:食品的图形通常是一个吸引玩家注意力的小图标,用来表示蛇需要收集的目标。
  • 按钮和图标:游戏界面中的按钮,如“开始游戏”、“暂停游戏”、“退出游戏”等,以及它们的图标,都需要图形资源。
4.2 音效资源

音效资源加强了游戏的互动性和沉醉感,包罗:

  • 点击声:当玩家点击按钮或举行选择时播放的声音效果。
  • 游戏音乐:背景音乐,为游戏提供连续的音频体验。
  • 游戏竣事:当玩家的游戏失败时播放的特殊音效
4.3 在Qt中加载和使用资源

在Qt中,图形和音效资源可以通过以下方式加载和使用:

  • 资源文件(.qrc):Qt支持使用资源文件来管理图像、音乐等资源。资源文件是一个XML文件,其中列出了所有资源的路径和访问方式。资源文件可以被编译到应用程序中,使得资源可以与应用程序一起打包和摆设。
    在Qt Creator中创建资源文件,并使用<file>标签指定资源的路径。
    使用:/res/image.png这样的语法来访问资源文件中的图像。
  • QPixmap:用于加载和显示图像资源。例如,使用QPixmap(":res/game_background.jpg")来加载游戏背景图像。
  • QPainter和QWidget:使用QPainter对象在QWidget或其子类上绘制图像。在paintEvent函数中,可以调用drawPixmap方法来绘制图像资源。
  • QSound:用于播放音效资源。例如,使用QSound(":res/click.wav")来加载点击声音效果,并调用play方法播放。
  • QMediaPlayer(可选):假如需要更高级的音频控制,可以使用QMediaPlayer类来加载和播放背景音乐。
  • 信号和槽:将图形和音效资源的加载与游戏逻辑相联合,例如,在按钮的clicked信号连接到播放音效的槽函数上。
  • 性能考虑:为了提高性能,图像和音效资源应该预先加载并缓存,制止在游戏运行时频繁加载资源。
示例代码:

  1. // 加载和使用图像资源
  2. QPixmap backgroundPixmap(":res/game_background.jpg");
  3. QPainter painter(this);
  4. painter.drawPixmap(0, 0, width(), height(), backgroundPixmap);
  5. // 加载和播放音效资源
  6. QSound clickSound(":res/click.wav");
  7. clickSound.play();
  8. // 使用QMediaPlayer播放背景音乐
  9. QMediaPlayer *musicPlayer = new QMediaPlayer;
  10. musicPlayer->setMedia(QUrl("qrc:/res/game_music.mp3"));
  11. musicPlayer->play();
复制代码
5. 调试和优化

5.1 调试技巧


  • 使用Qt Creator的Debugger: 使用Qt Creator内置的调试器举行断点设置、单步实行、监督变量和查看调用栈。
  • 日志输出: 在代码中添加日志输出语句,用于记录程序实行流程和变量状态,可以使用qDebug()函数。
  • 单位测试: 编写单位测试来验证各个函数和模块的精确性,确保代码更改不会引入新的错误。
  • 图形化调试: 对于图形和界面相关的问题,使用Qt Creator的图形化调试工具,如像素完善模式和DOM Inspector。
  • 性能分析: 使用性能分析工具,如Valgrind或Qt Creator的性能分析器,来检测内存泄漏和性能瓶颈。
  • 简化复杂函数: 将复杂的函数分解为更小的、更易于管理的函数,有助于定位问题。
5.2 性能优化建议


  • 淘汰重绘区域: 仅在必要时调用update()或repaint(),而且尽量缩小更新的范围。
  • 优化绘制逻辑: 在paintEvent中淘汰不必要的计算和绘制操作,例如,通过缓存绘制效果来制止重复绘制。
  • 使用更高效的数据结构: 根据需要选择合适的数据结构,以淘汰查找、插入和删除操作的时间复杂度。
  • 淘汰对象创建: 制止在频繁调用的函数中创建和销毁对象,因为对象的创建和销毁成本较高。
  • 公道使用定时器: 确保定时器的隔断设置公道,制止过快触发而导致的CPU占用过高。
  • 资源加载和缓存: 预先加载游戏中使用的所有资源,并在内存中缓存,制止在游戏运行时频繁读取磁盘。
5.3 代码重构和改进方法


  • 代码检察: 定期举行代码检察,以发现潜在的设计问题和改进代码质量。
  • 提取重复代码: 将重复的代码块重构为独立的函数或类,以淘汰代码冗余。
  • 使用设计模式: 根据需要应用设计模式,如观察者模式、单例模式或工厂模式,来提高代码的可维护性和扩展性。
  • 接口和抽象类: 定义清晰的接口和抽象类,以促进代码的低耦合和高内聚。
  • 依赖注入: 使用依赖注入来淘汰类之间的依赖关系,提高代码的机动性和可测试性。
  • 重构工具: 使用Qt Creator或IDE的重构工具来安全地举行重定名、移动和更改代码结构。
  • 代码格式化: 保持代码风格的同等性,使用Qt Creator的代码格式化功能或.editorconfig文件来自动格式化代码。
  • 文档和注释: 确保代码有充分的文档和注释,这有助于他人明白和维护代码
6. 扩展功能和未来方向

6.1 扩展功能


  • 高分榜:
    集成一个高分榜功能,记录玩家的得分,并答应玩家在游戏竣事后上传本身的得分。
    提供本地和在线的高分榜,显示最高分和历史最佳效果。
  • 差别主题:
    答应玩家选择差别的游戏主题,如宇宙、森林、海洋等,每个主题都有独特的图形和音效。
    通过主题包扩展游戏,用户可以下载和安装新的主题包。
  • 自定义皮肤:
    答应玩家为蛇选择差别的皮肤或颜色,提供个性化的游戏体验。
  • 加强的音效和音乐:
    提供更丰富的音效和背景音乐选项,玩家可以根据喜好选择差别的音乐风格。
  • 效果体系:
    实现一个效果体系,为完成特定任务或到达某些里程碑的玩家提供奖励。
  • 教程和资助:
    提供游戏教程和资助体系,资助新玩家快速上手。
6.2 未来方向


  • 网络对战:
    开发网络对战功能,答应玩家在线竞争或合作。
    实现匹配体系,根据玩家的得分或技能水平举行匹配。
  • 社交功能:
    集成社交媒体分享功能,答应玩家分享本身的得分和效果。
  • 跨平台游戏:
    确保游戏可以在差别的装备和平台上运行,如PC、移动装备和游戏机。
  • 云存储:
    使用云服务存储玩家的得分和进度,使玩家可以在差别装备上继续游戏。
  • 人工智能对手:
    开发具有人工智能的对手,为玩家提供更具挑战性的游戏体验。
  • 游戏内购买:
    提供游戏内购买选项,如特殊皮肤、主题包或其他假造商品。
  • 多语言支持:
    增长对多种语言的支持,扩大游戏的受众范围。
  • 加强实际(AR)和假造实际(VR):
    探索将游戏扩展到AR或VR平台的可能性,提供沉醉式游戏体验。
6.3 技能实现



  • 高分榜: 使用数据库或后端服务存储得分,实现得分的上传和检索。
  • 网络对战: 使用网络编程技能实现客户端之间的通信,可能需要一个中心服务器来协调游戏会话。
  • 云存储: 集成云服务API,如华为云、阿里云或腾讯云,实现数据的同步和存储。
  • 人工智能: 应用机器学习算法,创建具有差别计谋的AI对手。
7. 总结

使用Qt开发贪吃蛇游戏是一个实践和展示Qt强盛功能的过程,同时也是深入了解C++在现代应用开发中应用代价的时机。以下是通过本项目获得的一些关键经验和认识:


  • Qt的跨平台本领:Qt作为一个跨平台的框架,答应开发者编写一次代码,然后在Windows、macOS、Linux、iOS和Android等多个平台上运行。这极大地提升了开发效率和软件的可移植性。
  • 丰富的GUI组件:Qt提供了一套丰富的GUI组件,包罗按钮、标签、文本框等,这些组件在构建用户界面时非常有用。它们不但表面美观,而且具有很好的用户体验。
  • 事件驱动的编程模型:Qt的事件处置惩罚机制简化了用户交互的处置惩罚。通过信号和槽机制,Qt应用程序能够响应各种用户操作和体系事件,这在开发贪吃蛇游戏中尤为关键。
  • 绘图和动画支持:Qt的绘图体系为游戏提供了绘制图形和动画的本领。使用QPainter和QPixmap,可以轻松实现游戏的视觉体现。
  • 性能优化:Qt应用程序的性能可以通过各种方式举行优化,包罗淘汰不必要的重绘、使用更高效的数据结构和算法,以及使用Qt的资源体系预加载和缓存资源。
  • 音频和多媒体集成:Qt的QSound和QMediaPlayer类简化了游戏中音频效果和背景音乐的集成。
  • 代码的可维护性和扩展性:使用Qt和C++编写的代码结构清晰,易于维护和扩展。面向对象的编程范式使得代码更加模块化。
  • 学习曲线:固然Qt和C++具有强盛的功能,但它们也有一个学习曲线。通过实际项目,如贪吃蛇游戏的开发,可以加深对Qt框架和C++语言特性的明白。
  • 社区和文档支持:Qt拥有一个活跃的社区和过细的文档,为开发者提供了丰富的学习资源和问题解答。
  • 游戏开发的深度和广度:通过开发贪吃蛇游戏,我们可以看到Qt和C++在游戏开发中的应用潜力,从简朴的2D游戏到复杂的3D图形和网络对战游戏。
总之,使用Qt开发贪吃蛇游戏不但是一种技能上的实践,也是对Qt和C++在游戏开发中应用潜力的探索。通过这个项目,开发者可以学习到宝贵的技能,并为未来的软件开发工作打下坚固的基础。
末端

随着本文的竣事,我盼望读者不但对使用Qt开发贪吃蛇游戏有了深入的了解,而且也被激励去实验本身的项目。开发应用程序,尤其是游戏,是一段充满挑战和学习时机的路程。鼓励动手实践,探索Qt文档,到场社区,开发个性化项目,并分享您的经验。同时,也要持续学习,接受反馈,并迭代改进。软件开发是一个不断进步的过程,享受编程和创造的过程,享受您在开发中取得的每一个进步和乐成。记住,每一个巨大的开发者都是从编写第一个“Hello World”程序开始的。所以,不要害怕犯错,不要害怕重新开始。每个项目都是成长和学习的时机。现在,开始您的Qt开发之旅,创造令人高兴的新作品,并与世界分享您的创造力吧!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

怀念夏天

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表