Qt编写贪吃蛇小游戏完整项目

news/2024/9/17 19:43:56/ 标签: qt, c++, git, gitee

文章目录

  • 前言
  • 一、Qt环境准备
  • 二、编写思路
  • 三、编写代码
    • 1、开始游戏界面代码
      • 1.1、绘制界面
      • 1.2、界面基本配置
    • 2、选择难度界面代码
    • 3、游戏房间界面制作
      • 3.1、界面基础配置
      • 3.2、提前配置类中的成员变量
        • 3.2.1、QRectF
      • 3.3、检测游戏是否结束的方法
      • 3.4、蛇移动的实现
        • 3.4.1、蛇向上移动
        • 3.4.2、蛇向下移动
        • 3.4.3、蛇向左移动
        • 3.4.4、蛇向右移动
      • 3.5、渲染出贪吃蛇
      • 3.6、初始化贪吃蛇、绘制食物
      • 3.7、实现游戏开始与暂停(设置定时器)
      • 3.8、实现游戏控制蛇移动
      • 3.9、退出游戏按钮
      • 3.10、渲染积分、绘制结束游戏
      • 3.11、写入、获取分数
  • 四、完整代码块
    • 1、游戏主页面
      • 1.1、gamepage.h
      • 1.2、gamepage.cpp
    • 2、游戏难度选择
      • 2.1、gameselect.h
      • 2.2、gameselect.cpp
    • 3、游戏房间界面
      • 3.1、gameroom.h
      • 3.2、gameroom.cpp
  • 总结


前言

  该文章是记录用Qt编写贪吃蛇小游戏项目的步骤也方法注意事项,且本次使用Markdown编辑器编辑文章。


一、Qt环境准备

我们编写项目环境是必不可少的,未来我会准备一章编写教大家如何去配置Qt环境的。

二、编写思路

我们可以先编写一个给玩家开始游戏选择的界面,例如:
在这里插入图片描述
选择开始游戏之后,我们界面可以变为选择难度,例如:
在这里插入图片描述
选择难度之后我们就可以跳转到游戏界面了,游戏界面如下:
在这里插入图片描述
接下来我们开始编写代码。

三、编写代码

1、开始游戏界面代码

1.1、绘制界面

  在编写界面时我们要先创建好类,我们暂且先称为GamePage吧,在Qt中插入图片的方法有多种,但是一般我们不会在QWidget中的构造函数中编写,而是要放到重写的paintEvent方法中,其方法的作用在于界面发生任何改变时、窗口最小化时,就会主动调用repaint()或者update()方法使图片不会看不见。
  我们Qt中提供了绘画的API接口使用QPainter类去绘画,绘制照片时需使用QPixmap,QPixmap主要是用来在屏幕上显示图形,且Qt对该类在屏幕上进行了特别的优化。
代码如下:

注意:painter的drawPixmap方法使用(绘制的位置x、y坐标,绘制长宽width、height,最后选项是用哪个pixmap类

void GamePage::paintEvent(QPaintEvent *event)
{(void)event;QPainter painter(this);QPixmap pix(":/src source/interface.jpg");painter.drawPixmap(0,0,this->width(),this->height(),pix);
}

  可能会有人会疑问,为什么这个路径是这样的?其实这里是使用了Qt中qrc文件, qrc ⽂件是⼀种XML格式的资源配置⽂件,我们运用此文件可以通过将资源⽂件添加到项⽬中来⽅便地访问和管理这些资源。方法如下:
在这里插入图片描述
这里无脑下一步,接下来添加路径,路径自行修改,添加文件即可:
在这里插入图片描述
在这里插入图片描述

1.2、界面基本配置

准备了绘制方法后,我们就可以设置界面窗口的基础设置了。
我们可以先设置好窗口的固定大小,使用 setFixedSize 方法去调整。
修改窗口图标与标题、添加按钮(修改好位置、设置字体格式与大小、利用QSS对按钮样式进行修改)、最后用connect对按钮信号与槽进行连接,我们可以使用你lambda表达式进行编写。
代码如下:

GamePage::GamePage(QWidget *parent): QWidget(parent), ui(new Ui::GamePage)
{ui->setupUi(this);//设置窗口固定大小this->setFixedSize(1000,700);//设置图标this->setWindowIcon(QIcon(":/src source/snake.png"));this->setWindowTitle("贪吃蛇游戏");//添加按钮QPushButton *start = new QPushButton(this);//移动按钮位置start->setObjectName("start");start->move(400,500);start->setText("开始游戏");//设置字体和大小start->setFont(QFont("宋体",25));//利用QSS去修改按钮样式this->setStyleSheet("#start{""border: 0px;""border-radius: 30px;""background-color: #dadbde;""padding:20px;}""#start:hover{""border: 2px solid #8f8f91}""#start:pressed {""background-color: #f6f7fa;};");connect(start,&QPushButton::clicked,[=]{GameSelect *gameSelect = new GameSelect();gameSelect->setGeometry(this->geometry());this->close();QSound::play(":/src source/tip_music.wav");gameSelect->show();});
}

代码中我们添加了触发按钮就会触发提示音效,使用QSound类中的play方法,当我们点击了按钮开始游戏,GamePage类界面就可以关闭了,我们就创建好GameSelect类并且显示出来。

2、选择难度界面代码

  我们在编写GameSelect界面时,思路大致与GamePage界面相似,也是先重写PaintEvent方法,绘制背景画面,在构造方法中我们要准备4个按钮给玩家选择,分别是简单难度、普通难度、困难难度以及上局分数(上局分数该按钮方法我们后面在实现),我们这里在提供一个返回按钮,让玩家可以返回到上一个GamePage界面中。

设置返回按钮…

//设置一个返回的按钮QPushButton *backBtn = new QPushButton(this);backBtn->setObjectName("backBrn");backBtn->setIcon(QIcon(":/src source/return.png"));backBtn->setStyleSheet("#backBrn{border: 1px solid #8f8f91; padding: 10px;background-color:#e1ffff;}""#backBrn:hover{border: 2px solid #ff0000;}");backBtn->move(this->geometry().width()-200,this->geometry().height()-180);connect(backBtn,&QPushButton::clicked,[=]{this->close();GamePage *gamePage = new GamePage;gamePage->show();QSound::play(":/src source/tip_music.wav");});

GameSelect界面代码如下:

GameSelect::GameSelect(QWidget *parent) :QWidget(parent),ui(new Ui::GameSelect)
{ui->setupUi(this);//设置窗口固定大小this->setFixedSize(1000,700);//设置图标this->setWindowIcon(QIcon(":/src source/snake.png"));this->setWindowTitle("游戏关卡选择");//设置一个返回的按钮QPushButton *backBtn = new QPushButton(this);backBtn->setObjectName("backBrn");backBtn->setIcon(QIcon(":/src source/return.png"));backBtn->setStyleSheet("#backBrn{border: 1px solid #8f8f91; padding: 10px;background-color:#e1ffff;}""#backBrn:hover{border: 2px solid #ff0000;}");backBtn->move(this->geometry().width()-200,this->geometry().height()-180);connect(backBtn,&QPushButton::clicked,[=]{this->close();GamePage *gamePage = new GamePage;gamePage->show();QSound::play(":/src source/tip_music.wav");});//创建游戏房间界面GameRoom *gRoom = new GameRoom();//设置按钮的属性QFont font("宋体",25);QString style("QPushButton{""border: 0px;""border-radius: 30px;""background-color: #dadbde;""padding:20px;}""QPushButton:hover{""border: 2px solid #8f8f91}""QPushButton:pressed {""background-color: #f6f7fa;};");QPushButton *simpleBut = new QPushButton(this);simpleBut->move(390,120);simpleBut->setText("简单模式");simpleBut->setFont(font);simpleBut->setStyleSheet(style);connect(simpleBut,&QPushButton::clicked,[=]{this->close();gRoom->setGeometry(this->geometry());QSound::play(":/src source/tip_music.wav");gRoom->show();});QPushButton *normalBut = new QPushButton(this);normalBut->move(390,240);normalBut->setText("普通模式");normalBut->setFont(font);normalBut->setStyleSheet(style);connect(normalBut,&QPushButton::clicked,[=]{this->close();gRoom->setGeometry(this->geometry());QSound::play(":/src source/tip_music.wav");gRoom->show();});QPushButton *hardBut = new QPushButton(this);hardBut->move(390,360);hardBut->setText("困难模式");hardBut->setFont(font);hardBut->setStyleSheet(style);connect(hardBut,&QPushButton::clicked,[=]{this->close();gRoom->setGeometry(this->geometry());QSound::play(":/src source/tip_music.wav");gRoom->show();});QPushButton *hisBut = new QPushButton(this);hisBut->move(390,480);hisBut->setText("历史战绩");hisBut->setFont(font);hisBut->setStyleSheet(style);
}void GameSelect::paintEvent(QPaintEvent *event)
{(void)event;QPainter painter(this);QPixmap pix(":/src source/game_select.png");painter.drawPixmap(0,0,this->width(),this->height(),pix);
}

大致编写与GamePage界面相似,按钮也使用QSS对外观进行美观制作。

3、游戏房间界面制作

3.1、界面基础配置

  设置界面配置大致与上两个界面相同这里不再赘述,至于想怎么编写都是可以的。

3.2、提前配置类中的成员变量

  在准备编写成员变量时,我们可以先提前准备好代表蛇移动方向的枚举,以便后续代码的编写与维护。

//表示蛇移动的方向
enum class SnakeDirect{UP = 0,DOWN,LEFT,RIGHT
};

  在编写类时可以先提前准备好蛇的高度与宽度以及蛇的默认移动速度,这些提前配置好,后续我们编写的时候就会方便很多,提前创建好表示贪吃蛇的链表,链表我们使用QRectF去存储,以及用QRectF类型去表示食物的节点,这里QRectF是什么类型呢?

3.2.1、QRectF

QRectF 类是Qt框架中的一个功能强大的工具,专门用于处理和操作浮点精度的矩形区域。

我们回到刚刚的话题,接下来我们要准备表示蛇移动方向的变量,我们可以暂时初始化为 SnakeDirect::UP 也就是代表向上,还要准备定时器QTimer成员变量以及代表游戏是否开始的成员变量。

onst int kSnakeNodeWidth = 20; //表示蛇身体节点的宽度const int kSnakeNodeHeight = 20;//表示蛇身体节点的高度const int kDefaultTimeout = 200;//表示蛇默认移动速度QList<QRectF> snakeList;//表示贪吃蛇链表QRectF foodRect;//表示食物节点SnakeDirect moveDirect = SnakeDirect::UP;//表示蛇默认移动方向QTimer *timer;//定时器bool isGameStart = false;//表示是否开始游戏

3.3、检测游戏是否结束的方法

检测游戏是否结束可以直接判断链表中是否存在元素相同,当出现相同时代表蛇头对蛇身体发生了碰撞,这样利用代码即可判断是否结束游戏。

bool GameRoom::checkFail()
{for(int i = 0; i < snakeList.size(); i++){for(int  j = i + 1; j < snakeList.size(); j++){if(snakeList.at(i) == snakeList.at(j)){return true;}}}return false;
}

3.4、蛇移动的实现

3.4.1、蛇向上移动

  当需要对蛇进行移动时,我们需要考虑当蛇穿过边缘时,蛇需要从对面的边框出现,此时我们需要用两个变量去记录矩形框左上角以及右下角的位置,
先记录蛇头的坐标位置,leftTop = QPointF(headX,this->height() - kSnakeNodeHeight); 我们可以用以上代码去控制当蛇穿过上边缘时,就会从下边框出现,其他情况正常设计即可,右下角位置就是左上角加上蛇的长与宽即可,准备好之后直接头插进蛇的链表中。

代码如下:

void GameRoom::moveUp()
{QPointF leftTop;    //左上角坐标QPointF rightBottom;//右下角坐标auto snakeNode = snakeList.front();//蛇头int headX = snakeNode.x();int headY = snakeNode.y();if(headY < 0){leftTop = QPointF(headX,this->height() - kSnakeNodeHeight);}else{leftTop = QPointF(headX,headY - kSnakeNodeHeight);}rightBottom = leftTop + QPointF(kSnakeNodeWidth,kSnakeNodeHeight);snakeList.push_front(QRectF(leftTop,rightBottom));
}
3.4.2、蛇向下移动

  蛇向下移动与向上移动大致相识,但是我们可以使用QPointF中的一个方法 bottomLeft() 此方法是可以获取矩形左下角的坐标,这样蛇正常向下移动时,我们可以获取矩形框的左下角坐标,将左下角的坐标赋值给左上角变量中,这样可以快捷编写代码。

void GameRoom::moveDown()
{QPointF leftTop;    //左上角坐标QPointF rightBottom;//右下角坐标auto snakeNode = snakeList.front();//蛇头int headX = snakeNode.x();int headY = snakeNode.y();if(headY > this->height()){leftTop = QPointF(headX,0);}else{leftTop = snakeNode.bottomLeft();//获取矩形左下角的坐标}rightBottom = leftTop + QPointF(kSnakeNodeWidth,kSnakeNodeHeight);snakeList.push_front(QRectF(leftTop,rightBottom));
}
3.4.3、蛇向左移动

方法大致与上面两个编写方法一致,就是要注意穿过墙的时候要控制好。

void GameRoom::moveLeft()
{QPointF leftTop;    //左上角坐标QPointF rightBottom;//右下角坐标auto snakeNode = snakeList.front();//蛇头int headX = snakeNode.x();int headY = snakeNode.y();if(headX < 0){leftTop = QPointF(700 - kSnakeNodeWidth,headY);}else{leftTop = QPointF(headX - kSnakeNodeWidth,headY);}rightBottom = leftTop + QPointF(kSnakeNodeWidth,kSnakeNodeHeight);snakeList.push_front(QRectF(leftTop,rightBottom));
}
3.4.4、蛇向右移动

这里可以利用 topRight() 获取右上角坐标

void GameRoom::moveRight()
{QPointF leftTop;    //左上角坐标QPointF rightBottom;//右下角坐标auto snakeNode = snakeList.front();//蛇头int headX = snakeNode.x();int headY = snakeNode.y();if(headX > 700){leftTop = QPointF(0,headY);}else{leftTop = snakeNode.topRight();//获取矩形右上角坐标}rightBottom = leftTop + QPointF(kSnakeNodeWidth,kSnakeNodeHeight);snakeList.push_front(QRectF(leftTop,rightBottom));
}

3.5、渲染出贪吃蛇

  绘制贪吃蛇我们需要分三个部分,蛇头、蛇身、蛇尾,绘制也是使用Qpainter以及QPixmap配合使用,这边不在赘述,蛇头即是之前提前编写的蛇链表(snakeList),获取蛇头的坐标位置,直接绘制, 但需要注意蛇头方向,先前准备了moveDirect表示蛇移动的方向,需要判断蛇的方向去绘制蛇头的方向 ,蛇身就需要使用循环把剩下的节点绘制出来,剩下蛇尾也是直接获取节点坐标绘制。

  //绘制蛇 蛇头 + 蛇身体 + 蛇尾巴//绘制蛇头:上 下 左 右if(moveDirect == SnakeDirect::UP){pix.load(":/src source/head_up.png");}else if(moveDirect == SnakeDirect::DOWN){pix.load(":/src source/head_down.png");}else if(moveDirect == SnakeDirect::LEFT){pix.load(":/src source/head_left.png");}else if(moveDirect == SnakeDirect::RIGHT){pix.load(":/src source/head_right.png");}auto snakeHead = snakeList.front();painter.drawPixmap(snakeHead.x(),snakeHead.y(),snakeHead.width(),snakeHead.height(),pix);//绘制蛇身pix.load(":/src source/body.png");for (int i = 1; i < snakeList.size() - 1; i++) {auto node = snakeList.at(i);painter.drawPixmap(node.x(),node.y(),node.width(),node.height(),pix);}//绘制蛇尾auto snakeTail = snakeList.back();painter.drawPixmap(snakeTail.x(),snakeTail.y(),snakeTail.width(),snakeTail.height(),pix);

3.6、初始化贪吃蛇、绘制食物

  初始化贪吃蛇我们需要在构造函数中做好准备,对链表做尾插个节点即可,此时运行程序发现就一个蛇头,那么我们可以提供多两个蛇向上移动,添加多两个节点,蛇就变成许多了。
代码如下:

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

  绘制食物可以使用 qrand 随机数让食物随机生成,不过需要注意控制好,不要让食物跑出界面之外,所以要控制好。

void GameRoom::createNewFood()
{foodRect = QRectF(qrand() % (750 / kSnakeNodeWidth) * kSnakeNodeWidth,qrand() % (this->height() / kSnakeNodeHeight) * kSnakeNodeHeight,kSnakeNodeWidth, kSnakeNodeHeight);
}

3.7、实现游戏开始与暂停(设置定时器)

  当需要蛇持续进行移动,我们就需要利用定时器让界面移动(QTimer),这里使用connect让信号与槽连接起来,在QRectF中存在一种方法 intersects 该方法作用在于判断两个矩形框是否发生相交,当我们蛇与食物发生碰撞时,让蛇链表尾插一个节点,并且让蛇持续往蛇头方向移动,还需要使用 update() 刷新链表数据,以防吃了食物蛇数据没有发生改变。
  在添加两个按钮去控制开始或者停止,开始前设置好定时器触发时间,用方法staart()启动即可,也可以准备好背景音乐QSound,Qsound中存在一个方法 setLoops 当为真时会循环播放音乐。
代码如下:

 //使用定时器timer = new QTimer(this);connect(timer,&QTimer::timeout,[=]{int cnt = 1;if(snakeList.front().intersects(foodRect)){//intersects方法作用是判断两个矩形框是否有相交createNewFood();cnt++;QSound::play(":/src source/succ.wav");}while (cnt--) {switch (moveDirect) {case SnakeDirect::UP:moveUp();break;case SnakeDirect::DOWN:moveDown();break;case SnakeDirect::LEFT:moveLeft();break;case SnakeDirect::RIGHT:moveRight();break;}}snakeList.pop_back();update();//用于刷新链表});QPushButton *start = new QPushButton("开始",this);QPushButton *stop = new QPushButton("停止",this);start->setObjectName("start");stop->setObjectName("stop");QFont font("宋体",10);start->setFont(font);stop->setFont(font);start->move(820,180);stop->move(820,240);connect(start,&QPushButton::clicked,[=]{if(!isGameStart){isGameStart = true;timer->start(moveTimeout);//设置定时器触发时间sound = new QSound(":/src source/bgm.wav",this);sound->play();sound->setLoops(-1);}});connect(stop,&QPushButton::clicked,[=]{if(isGameStart){isGameStart = false;timer->stop();sound->stop();}});

3.8、实现游戏控制蛇移动

  这里添加蛇移动的按钮,要配置好按钮的基础设置,也可以配置好照片,这里我设置了按钮快捷键setShortcut(),setShortcut里传的参数是QKeySequence,需注意。
代码如下:

//方向控制QPushButton *up = new QPushButton(QIcon(":/src source/up.png"),"上",this);QPushButton *down = new QPushButton(QIcon(":/src source/down.png"),"下",this);QPushButton *left = new QPushButton(QIcon(":/src source/left.png"),"左",this);QPushButton *right = new QPushButton(QIcon(":/src source/right.png"),"右",this);up->move(840,400);down->move(840,480);left->move(780,440);right->move(880,440);connect(up,&QPushButton::clicked,[=]{if(moveDirect != SnakeDirect::DOWN)moveDirect = SnakeDirect::UP;});connect(down,&QPushButton::clicked,[=]{if(moveDirect != SnakeDirect::UP    )moveDirect = SnakeDirect::DOWN;});connect(left,&QPushButton::clicked,[=]{if(moveDirect != SnakeDirect::RIGHT)moveDirect = SnakeDirect::LEFT;});connect(right,&QPushButton::clicked,[=]{if(moveDirect != SnakeDirect::LEFT)moveDirect = SnakeDirect::RIGHT;});up->setShortcut(QKeySequence("Up"));down->setShortcut(QKeySequence("Down"));left->setShortcut(QKeySequence("Left"));right->setShortcut(QKeySequence("Right"));start->setShortcut((QKeySequence("Space")));

3.9、退出游戏按钮

  创建退出游戏按钮并不复杂,这边主要讲的是触发按钮后弹出的界面,Qt中有QWidget还有QDialog类,Qt也提供了一些QDialog控件给程序员使用,这边我们使用QMessageBox,当触发按钮显示提示窗口,提示是否退出,所以要体用两个按钮给提示窗口了,这里需要注意按钮哪种枚举值,用于表示消息框中的接受按钮的角色,QMessageBox::AcceptRole 通常用于设置消息框的默认接受按钮(通常是“确定”或“是”按钮)。后面我们点击按钮时可使用QMessageBox中的clickedButton方法去判断即可。

//退出游戏QPushButton *exitBtn = new QPushButton("退出",this);exitBtn->setFont(QFont("宋体",20));exitBtn->move(840,600);QMessageBox *messageBox = new QMessageBox(this);QPushButton *ok = new QPushButton("ok");QPushButton *cancel = new QPushButton("cancel");messageBox->setWindowTitle("退出游戏");messageBox->setText("确认退出游戏吗?");messageBox->addButton(ok,QMessageBox::AcceptRole);messageBox->addButton(cancel,QMessageBox::RejectRole);connect(exitBtn,&QPushButton::clicked,[=]{timer->stop();sound->stop();messageBox->exec();QSound::play(":/src source/tip_music.wav");if(messageBox->clickedButton() == ok){GameSelect *gSelect = new GameSelect;this->close();gSelect->show();}else{messageBox->close();timer->start();sound->play();}});
}

3.10、渲染积分、绘制结束游戏

绘制积分与绘制结束游戏这边不在过多赘述,判断结束我们运用上编写的checkFail()即可,其他代码编写即可。

    //绘制积分pix.load(":/src source/integral.png");painter.drawPixmap(800,60,50,40,pix);QPen pen;pen.setColor(Qt::black);painter.setPen(pen);QFont font("方正舒体",35);painter.setFont(font);painter.drawText(875,95,QString("%1").arg(snakeList.size()));//绘制游戏失败效果if(checkFail()){pen.setColor(Qt::red);painter.setPen(pen);painter.setFont(QFont("方正舒体",50));painter.drawText(this->width()*0.25,this->height()*0.5,QString("GAME OVER!"));timer->stop();sound->deleteLater();sound = new QSound(":/src source/fail.wav");sound->setLoops(0);sound->play();}

3.11、写入、获取分数

当得到的分数需要进行永久的保存,要么写入硬盘中要么写在数据库中,我们暂且先写在文件中,写入文件我们使用QFile库,在利用QTextStream对文件输入输出数据,编写格式可以观看代码编写。
代码如下:

    //往文件写分数int c = snakeList.size();//初始创建文件QFile file("./1.txt");// 打开文件以写入模式,记录分数if(file.open(QIODevice::WriteOnly | QIODevice::Text)){QTextStream out (&file);int num = c;out << num;file.close();}//获取分数QFile file("./1.txt");file.open(QIODevice::ReadOnly);QTextStream in(&file);int date = in.readLine().toInt();edit->setText("上次分数为:");edit->append(QString::number(date) + QString("分"));

四、完整代码块

1、游戏主页面

1.1、gamepage.h

#ifndef GAMEPAGE_H
#define GAMEPAGE_H#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui { class GamePage; }
QT_END_NAMESPACEclass GamePage : public QWidget
{Q_OBJECTpublic:GamePage(QWidget *parent = nullptr);~GamePage();void paintEvent(QPaintEvent *event);private:Ui::GamePage *ui;
};
#endif // GAMEPAGE_H

1.2、gamepage.cpp

#include "gamepage.h"
#include "ui_gamepage.h"
#include <gameselect.h>
#include <QPainter>
#include <QIcon>
#include <QPushButton>
#include <QSound>GamePage::GamePage(QWidget *parent): QWidget(parent), ui(new Ui::GamePage)
{ui->setupUi(this);//设置窗口固定大小this->setFixedSize(1000,700);//设置图标this->setWindowIcon(QIcon(":/src source/snake.png"));this->setWindowTitle("贪吃蛇游戏");//添加按钮QPushButton *start = new QPushButton(this);//移动按钮位置start->setObjectName("start");start->move(400,500);start->setText("开始游戏");//设置字体和大小start->setFont(QFont("宋体",25));//利用QSS去修改按钮样式this->setStyleSheet("#start{""border: 0px;""border-radius: 30px;""background-color: #dadbde;""padding:20px;}""#start:hover{""border: 2px solid #8f8f91}""#start:pressed {""background-color: #f6f7fa;};");connect(start,&QPushButton::clicked,[=]{GameSelect *gameSelect = new GameSelect();gameSelect->setGeometry(this->geometry());this->close();QSound::play(":/src source/tip_music.wav");gameSelect->show();});}GamePage::~GamePage()
{delete ui;
}void GamePage::paintEvent(QPaintEvent *event)
{(void)event;QPainter painter(this);QPixmap pix(":/src source/interface.jpg");painter.drawPixmap(0,0,this->width(),this->height(),pix);
}

2、游戏难度选择

2.1、gameselect.h

#ifndef GAMESELECT_H
#define GAMESELECT_H#include <QWidget>namespace Ui {
class GameSelect;
}class GameSelect : public QWidget
{Q_OBJECTpublic:explicit GameSelect(QWidget *parent = nullptr);~GameSelect();void paintEvent(QPaintEvent *event);private:Ui::GameSelect *ui;};#endif // GAMESELECT_H

2.2、gameselect.cpp

#include "gameselect.h"
#include "ui_gameselect.h"
#include "gamepage.h"
#include "gameroom.h"
#include <QIcon>
#include <QPushButton>
#include <QSound>
#include <QPainter>
#include <QTextEdit>
#include <QFile>
#include <QTextStream>GameSelect::GameSelect(QWidget *parent) :QWidget(parent),ui(new Ui::GameSelect)
{ui->setupUi(this);//设置窗口固定大小this->setFixedSize(1000,700);//设置图标this->setWindowIcon(QIcon(":/src source/snake.png"));this->setWindowTitle("游戏关卡选择");//设置一个返回的按钮QPushButton *backBtn = new QPushButton(this);backBtn->setObjectName("backBrn");backBtn->setIcon(QIcon(":/src source/return.png"));backBtn->setStyleSheet("#backBrn{border: 1px solid #8f8f91; padding: 10px;background-color:#e1ffff;}""#backBrn:hover{border: 2px solid #ff0000;}");backBtn->move(this->geometry().width()-200,this->geometry().height()-180);connect(backBtn,&QPushButton::clicked,[=]{this->close();GamePage *gamePage = new GamePage;gamePage->show();QSound::play(":/src source/tip_music.wav");});//创建游戏房间界面GameRoom *gRoom = new GameRoom();//设置按钮的属性QFont font("宋体",25);QString style("QPushButton{""border: 0px;""border-radius: 30px;""background-color: #dadbde;""padding:20px;}""QPushButton:hover{""border: 2px solid #8f8f91}""QPushButton:pressed {""background-color: #f6f7fa;};");QPushButton *simpleBut = new QPushButton(this);simpleBut->move(390,120);simpleBut->setText("简单模式");simpleBut->setFont(font);simpleBut->setStyleSheet(style);connect(simpleBut,&QPushButton::clicked,[=]{this->close();gRoom->setGeometry(this->geometry());QSound::play(":/src source/tip_music.wav");gRoom->show();gRoom->setTimeout(300);});QPushButton *normalBut = new QPushButton(this);normalBut->move(390,240);normalBut->setText("普通模式");normalBut->setFont(font);normalBut->setStyleSheet(style);connect(normalBut,&QPushButton::clicked,[=]{this->close();gRoom->setGeometry(this->geometry());QSound::play(":/src source/tip_music.wav");gRoom->show();gRoom->setTimeout(200);});QPushButton *hardBut = new QPushButton(this);hardBut->move(390,360);hardBut->setText("困难模式");hardBut->setFont(font);hardBut->setStyleSheet(style);connect(hardBut,&QPushButton::clicked,[=]{this->close();gRoom->setGeometry(this->geometry());QSound::play(":/src source/tip_music.wav");gRoom->show();gRoom->setTimeout(100);});QPushButton *hisBut = new QPushButton(this);hisBut->move(390,480);hisBut->setText("历史战绩");hisBut->setFont(font);hisBut->setStyleSheet(style);connect(hisBut,&QPushButton::clicked,[=]{QWidget *widget = new QWidget();widget->setWindowTitle("历史战绩");widget->setFixedSize(400,240);QTextEdit *edit = new QTextEdit(widget);edit->setFont(font);edit->setFixedSize(400,240);QFile file("./1.txt");file.open(QIODevice::ReadOnly);QTextStream in(&file);int date = in.readLine().toInt();edit->setText("上次分数为:");edit->append(QString::number(date) + QString("分"));widget->show();file.close();});
}GameSelect::~GameSelect()
{delete ui;
}void GameSelect::paintEvent(QPaintEvent *event)
{(void)event;QPainter painter(this);QPixmap pix(":/src source/game_select.png");painter.drawPixmap(0,0,this->width(),this->height(),pix);
}

3、游戏房间界面

3.1、gameroom.h

#ifndef GAMEROOM_H
#define GAMEROOM_H#include <QWidget>
#include <QSound>//表示蛇移动的方向
enum class SnakeDirect{UP = 0,DOWN,LEFT,RIGHT
};namespace Ui {
class GameRoom;
}class GameRoom : public QWidget
{Q_OBJECTpublic:explicit GameRoom(QWidget *parent = nullptr);~GameRoom();void paintEvent(QPaintEvent *event);//蛇方向移动的方法void moveUp();void moveDown();void moveLeft();void moveRight();//检测游戏是否结束bool checkFail();//创建食物void createNewFood();//设置蛇的移动时间void setTimeout(int timeout){moveTimeout = timeout;}private:Ui::GameRoom *ui;const int kSnakeNodeWidth = 20; //表示蛇身体节点的宽度const int kSnakeNodeHeight = 20;//表示蛇身体节点的高度const int kDefaultTimeout = 200;//表示蛇默认移动速度QList<QRectF> snakeList;//表示贪吃蛇链表QRectF foodRect;//表示食物节点SnakeDirect moveDirect = SnakeDirect::UP;//表示蛇默认移动方向QTimer *timer;//定时器bool isGameStart = false;//表示是否开始游戏//游戏背景音乐QSound *sound;//设置移动速度变量int moveTimeout = kDefaultTimeout;
};#endif // GAMEROOM_H

3.2、gameroom.cpp

#include "gameroom.h"
#include "ui_gameroom.h"
#include "gameselect.h"
#include <QIcon>
#include <QPainter>
#include <QDebug>
#include <QTimer>
#include <QPushButton>
#include <QMessageBox>
#include <QLabel>
#include <QFile>
#include <QDebug>GameRoom::GameRoom(QWidget *parent) :QWidget(parent),ui(new Ui::GameRoom)
{ui->setupUi(this);this->setFixedSize(1000,700);this->setWindowIcon(QIcon(":/src source/snake.png"));this->setWindowTitle("游戏房间");//初始化声音sound = new QSound(":/src source/bgm.wav",this);//初始化贪吃蛇snakeList.push_back(QRectF(this->width() * 0.5,this->height() * 0.5,kSnakeNodeWidth,kSnakeNodeHeight));moveUp();moveUp();//创建食物createNewFood();//使用定时器timer = new QTimer(this);connect(timer,&QTimer::timeout,[=]{int cnt = 1;if(snakeList.front().intersects(foodRect)){//intersects方法作用是判断两个矩形框是否有相交createNewFood();cnt++;QSound::play(":/src source/succ.wav");}while (cnt--) {switch (moveDirect) {case SnakeDirect::UP:moveUp();break;case SnakeDirect::DOWN:moveDown();break;case SnakeDirect::LEFT:moveLeft();break;case SnakeDirect::RIGHT:moveRight();break;}}snakeList.pop_back();update();//用于刷新链表});QPushButton *start = new QPushButton("开始",this);QPushButton *stop = new QPushButton("停止",this);start->setObjectName("start");stop->setObjectName("stop");QFont font("宋体",10);start->setFont(font);stop->setFont(font);start->move(820,180);stop->move(820,240);connect(start,&QPushButton::clicked,[=]{if(!isGameStart){isGameStart = true;timer->start(moveTimeout);//设置定时器触发时间sound = new QSound(":/src source/bgm.wav",this);sound->play();sound->setLoops(-1);}});connect(stop,&QPushButton::clicked,[=]{if(isGameStart){isGameStart = false;timer->stop();sound->stop();}});//方向控制QPushButton *up = new QPushButton(QIcon(":/src source/up.png"),"上",this);QPushButton *down = new QPushButton(QIcon(":/src source/down.png"),"下",this);QPushButton *left = new QPushButton(QIcon(":/src source/left.png"),"左",this);QPushButton *right = new QPushButton(QIcon(":/src source/right.png"),"右",this);up->move(840,400);down->move(840,480);left->move(780,440);right->move(880,440);connect(up,&QPushButton::clicked,[=]{if(moveDirect != SnakeDirect::DOWN)moveDirect = SnakeDirect::UP;});connect(down,&QPushButton::clicked,[=]{if(moveDirect != SnakeDirect::UP    )moveDirect = SnakeDirect::DOWN;});connect(left,&QPushButton::clicked,[=]{if(moveDirect != SnakeDirect::RIGHT)moveDirect = SnakeDirect::LEFT;});connect(right,&QPushButton::clicked,[=]{if(moveDirect != SnakeDirect::LEFT)moveDirect = SnakeDirect::RIGHT;});up->setShortcut(QKeySequence("Up"));down->setShortcut(QKeySequence("Down"));left->setShortcut(QKeySequence("Left"));right->setShortcut(QKeySequence("Right"));start->setShortcut((QKeySequence("Space")));//退出游戏QPushButton *exitBtn = new QPushButton("退出",this);exitBtn->setFont(QFont("宋体",20));exitBtn->move(840,600);QMessageBox *messageBox = new QMessageBox(this);QPushButton *ok = new QPushButton("ok");QPushButton *cancel = new QPushButton("cancel");messageBox->setWindowTitle("退出游戏");messageBox->setText("确认退出游戏吗?");messageBox->addButton(ok,QMessageBox::AcceptRole);messageBox->addButton(cancel,QMessageBox::RejectRole);connect(exitBtn,&QPushButton::clicked,[=]{timer->stop();sound->stop();messageBox->show();messageBox->exec();QSound::play(":/src source/tip_music.wav");if(messageBox->clickedButton() == ok){GameSelect *gSelect = new GameSelect;this->close();gSelect->show();}else{messageBox->close();timer->start();sound->play();}});
}GameRoom::~GameRoom()
{delete ui;
}void GameRoom::paintEvent(QPaintEvent *event)
{(void)event;//绘制游戏背景QPainter painter(this);QPixmap pix;pix.load(":/src source/game_img.jpg");painter.drawPixmap(0,0,750,700,pix);pix.load(":/src source/game_back.jpg");painter.drawPixmap(750,0,300,700,pix);//绘制蛇 蛇头 + 蛇身体 + 蛇尾巴//绘制蛇头:上 下 左 右if(moveDirect == SnakeDirect::UP){pix.load(":/src source/head_up.png");}else if(moveDirect == SnakeDirect::DOWN){pix.load(":/src source/head_down.png");}else if(moveDirect == SnakeDirect::LEFT){pix.load(":/src source/head_left.png");}else if(moveDirect == SnakeDirect::RIGHT){pix.load(":/src source/head_right.png");}auto snakeHead = snakeList.front();painter.drawPixmap(snakeHead.x(),snakeHead.y(),snakeHead.width(),snakeHead.height(),pix);//绘制蛇身pix.load(":/src source/body.png");for (int i = 1; i < snakeList.size() - 1; i++) {auto node = snakeList.at(i);painter.drawPixmap(node.x(),node.y(),node.width(),node.height(),pix);}//绘制蛇尾auto snakeTail = snakeList.back();painter.drawPixmap(snakeTail.x(),snakeTail.y(),snakeTail.width(),snakeTail.height(),pix);//绘制食物pix.load(":/src source/meal.png");painter.drawPixmap(foodRect.x(),foodRect.y(),foodRect.width(),foodRect.height(),pix);//绘制积分pix.load(":/src source/integral.png");painter.drawPixmap(800,60,50,40,pix);QPen pen;pen.setColor(Qt::black);painter.setPen(pen);QFont font("方正舒体",35);painter.setFont(font);painter.drawText(875,95,QString("%1").arg(snakeList.size()));//往文件写分数int c = snakeList.size();//初始创建文件QFile file("./1.txt");// 打开文件以写入模式,记录分数if(file.open(QIODevice::WriteOnly | QIODevice::Text)){QTextStream out (&file);int num = c;out << num;file.close();}//绘制游戏失败效果if(checkFail()){pen.setColor(Qt::red);painter.setPen(pen);painter.setFont(QFont("方正舒体",50));painter.drawText(this->width()*0.25,this->height()*0.5,QString("GAME OVER!"));timer->stop();sound->deleteLater();sound = new QSound(":/src source/fail.wav");sound->setLoops(0);sound->play();}}void GameRoom::moveUp()
{QPointF leftTop;    //左上角坐标QPointF rightBottom;//右下角坐标auto snakeNode = snakeList.front();//蛇头int headX = snakeNode.x();int headY = snakeNode.y();if(headY < 0){leftTop = QPointF(headX,this->height() - kSnakeNodeHeight);}else{leftTop = QPointF(headX,headY - kSnakeNodeHeight);}rightBottom = leftTop + QPointF(kSnakeNodeWidth,kSnakeNodeHeight);snakeList.push_front(QRectF(leftTop,rightBottom));}void GameRoom::moveDown()
{QPointF leftTop;    //左上角坐标QPointF rightBottom;//右下角坐标auto snakeNode = snakeList.front();//蛇头int headX = snakeNode.x();int headY = snakeNode.y();if(headY > this->height()){leftTop = QPointF(headX,0);}else{leftTop = snakeNode.bottomLeft();//获取矩形左下角的坐标}rightBottom = leftTop + QPointF(kSnakeNodeWidth,kSnakeNodeHeight);snakeList.push_front(QRectF(leftTop,rightBottom));
}void GameRoom::moveLeft()
{QPointF leftTop;    //左上角坐标QPointF rightBottom;//右下角坐标auto snakeNode = snakeList.front();//蛇头int headX = snakeNode.x();int headY = snakeNode.y();if(headX < 0){leftTop = QPointF(700 - kSnakeNodeWidth,headY);}else{leftTop = QPointF(headX - kSnakeNodeWidth,headY);}rightBottom = leftTop + QPointF(kSnakeNodeWidth,kSnakeNodeHeight);snakeList.push_front(QRectF(leftTop,rightBottom));
}void GameRoom::moveRight()
{QPointF leftTop;    //左上角坐标QPointF rightBottom;//右下角坐标auto snakeNode = snakeList.front();//蛇头int headX = snakeNode.x();int headY = snakeNode.y();if(headX > 700){leftTop = QPointF(0,headY);}else{leftTop = snakeNode.topRight();//获取矩形右上角坐标}rightBottom = leftTop + QPointF(kSnakeNodeWidth,kSnakeNodeHeight);snakeList.push_front(QRectF(leftTop,rightBottom));
}bool GameRoom::checkFail()
{for(int i = 0; i < snakeList.size(); i++){for(int  j = i + 1; j < snakeList.size(); j++){if(snakeList.at(i) == snakeList.at(j)){return true;}}}return false;
}void GameRoom::createNewFood()
{foodRect = QRectF(qrand() % (750 / kSnakeNodeWidth) * kSnakeNodeWidth,qrand() % (this->height() / kSnakeNodeHeight) * kSnakeNodeHeight,kSnakeNodeWidth, kSnakeNodeHeight);
}

总结

  这是贪吃蛇全部啦,如想看源码可去gitee去获取喔。https://gitee.com/edwin2edd/greedy_snake_project


http://www.ppmy.cn/news/1516468.html

相关文章

【赵渝强老师】执行MySQL的冷备份与冷恢复

冷备份是指发生在数据库已经正常关闭的情况下进行的备份。由于此时数据库已经关闭&#xff0c;通过冷备份可以将数据库的关键性文件拷贝到另外存储位置。冷备份因为只是拷贝文件&#xff0c;因此备份的速度非常快。在执行恢复时&#xff0c;只需将文件再拷贝回去就可以很容易恢…

CPU利用率和CPU负载的区别

CPU利用率和负载虽然相关,但确是两个不同的概念。 CPU利用率 CPU利用率表示CPU实际工作时间与总时间的比率,通常以百分比表示。范围是0% 到 100%&#xff0c;CPU利用率的含义是表示CPU在给定时间内实际执行指令的时间比例&#xff0c;举个例子: 70% 的CPU利用率意味着在某个时…

TCP、UDP

端口号: 端口号: 16位数值(unsigned short ) //0~65535 (65536个数) //标示一个进程 TCP和 UDP 的端口号是独立的 端口号: (1) 作用:唯一的标识一个进程 每一个应用程序进程有一个端口号&#xff0c; 通讯时区分数据包属于哪个应…

硬件面试经典 100 题(81~90)题

81、请问下图电路中二极管 D1、D2 有什么作用&#xff1f; 在 Vi 输入电压接近于零时&#xff0c;D1、D2 给三极管 T1、T2 提供偏置电压&#xff0c;使 T1、T2 维持导通&#xff0c;以消除交越失真。 陈氏解释 这道题参见&#xff1a;硬件面试经典 100 题&#xff08;51~70 题…

Vue3 后台管理系统项目 前端部分

这里写目录标题 1 创建Vue3项目1.1 相关链接1.2 Vue Router1.3 Element1.4 scss1.5 mitt1.6 axios1.7 echarts1.8 配置vite.config.js 2 CSS部分2.1 样式穿透2.2 :style &#xff1a;在样式中使用插值语法 3. ElementUI3.1 rules&#xff1a; 数据验证3.2 修改element.style中的…

信号分解|基于北方苍鹰优化变分模态分解的时序信号分解Matlab程序NGO-VMD

信号分解|基于北方苍鹰优化变分模态分解的时序信号分解Matlab程序NGO-VMD 文章目录 一、基本原理二、实验结果三、核心代码四、代码获取五、总结 信号分解|基于北方苍鹰优化变分模态分解的时序信号分解Matlab程序NGO-VMD 一、基本原理 NGO-VMD结合了北方苍鹰优化算法&#xff…

移动端爬虫学习记录

免责声明 本文旨在探讨移动端爬虫技术的应用和挑战&#xff0c;仅供教育和研究用途。请确保在合法合规的框架内使用爬虫技术&#xff0c;遵循相关法律法规和网站的使用条款。作者不对因使用本文内容而产生的任何法律或安全问题承担责任。 1、初识移动端爬虫 学习移动端爬虫的原…

7. Java 中 HashMap 的扩容机制是怎样的?

​​​​​​HashMap 是基于哈希表的数据结构&#xff0c;其容量是动态调整的。当存储的元素数量增加时&#xff0c;为了保持较好的性能&#xff0c;HashMap 需要进行扩容。HashMap 的扩容机制是为了减少哈希碰撞&#xff0c;提高查询效率。 1. 初始容量和负载因子 初始容量&a…

Android 退出app方式(回忆录)

一、点击返回键或者设备back键调用finish private void back(){finish(); }或Overridepublic void onBackPressed() {super.onBackPressed();}二、结束进程 android.os.Process.killProcess(android.os.Process.myPid()); 三、方法二exit结束java虚拟机 System.exit(0); 四…

uniapp中路由的基本使用方法、参数传递方式以及路由拦截与权限控制

一、概述 在uniapp开发中&#xff0c;路由是非常重要的一个方面&#xff0c;它可以实现页面之间的跳转和传递参数。本文将介绍uniapp中路由的使用技巧&#xff0c;并给出具体的代码示例。 二、uniapp路由的基本使用 在uniapp中&#xff0c;路由的基本使用可以通过uni.navigate…

ai变声:视频怎么变音?分享6个语音变声器,视频变声不再难!

想过如何让自己的直播内容更吸引人吗&#xff1f;你是否希望通过变声器来打造独特的声音效果&#xff1f;或者&#xff0c;如何用创意声音提升观众的互动体验呢&#xff1f;随着直播行业的不断发展&#xff0c;每位主播都在努力寻找吸引观众的独特方式&#xff0c;而变声器正是…

【Test 001】Qt 开发基础体系 QMap 类和 QHash 类以及 QVector 类

文章目录 1.QMap 详解1.1 QMap 的介绍1.2 QMap 的具体用法如下1.3 QmultiMap类 2.QHash 详解3. QMap 和 QHash 的对比4. QVector 详解 1.QMap 详解 1.1 QMap 的介绍 &#x1f427;① QMap<key,T>提供一个从类型为Key的键到类型为T的值的映射。通常&#xff0c;QMap存储的…

新手小白Ubuntu18.04超详细安装教程

1、Ubuntu18.04系统下载地址 Ubuntu18.04下载地址 直接下载桌面版 2、Ubuntu18.04安装 &#xff08;1&#xff09;打开VMware虚拟机 文件—>新建虚拟机—>选择典型 &#xff08;2&#xff09;选择稍后安装系统 &#xff08;3&#xff09;选择linux系统&#xff0c;…

学习记录:js算法(十五):三数之和

文章目录 三数之和我的思路网上思路 总结 三数之和 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意&…

win/mac视频剪辑软件Premiere Pro 2024下载安装

目录 一、简介 &#xff08;一&#xff09;高级调色功能 &#xff08;二&#xff09;字幕制作 &#xff08;三&#xff09;与其他 Adobe 软件的协同工作 下载 二、安装 &#xff08;一&#xff09;安装前的准备工作 &#xff08;二&#xff09;安装过程中的常见问题及解…

前端面试宝典【CSS篇】【8】

在前端开发的世界里,每一次面试都是一次机遇,也是一次挑战。 你是否曾因技术深度不够而错失良机? 或是面对最新的技术趋势感到迷茫? 我们的【前端面试宝典】正是为此而来。 由拥有多年一线实战经验的资深工程师亲自授课,结合最新的行业动态与实战案例,旨在全面提升你的技…

【算法进阶2-动态规划】斐波那契数列(递归调用、动态规划)、钢条切割问题(自定而下实现、自底向上、切割方案)

1 斐波那契数 2 钢条切割问题 2.1 最优解情况 2.2 钢条切割问题之自定而下实现 2.3 钢条切割问题之自底向上实现 2.4 钢条切割问题-重构解-切割方案 1 斐波那契数 # 1 子问题的重复计算 def fibonacci(n: int) -> int:"""使用递归方式计算第 n 个斐波那契数…

美国高防服务器测评

美国高防服务器通常具有出色的硬件配置和网络性能&#xff0c;以及强大的DDoS防御能力。rak小编为您整理发布美国高防服务器测评。 美国高防服务器因其地理位置和网络基础设施的优势&#xff0c;通常被认为在防御分布式拒绝服务(DDoS)攻击方面具有较高的能力。面对日益增长的网…

基于R语言遥感随机森林建模与空间预测

随机森林作为一种集成学习方法&#xff0c;在处理复杂数据分析任务中特别是遥感数据分析中表现出色。通过构建大量的决策树并引入随机性&#xff0c;随机森林在降低模型方差和过拟合风险方面具有显著优势。在训练过程中&#xff0c;使用Bootstrap抽样生成不同的训练集&#xff…

力扣: 两两交换链表中的节点

文章目录 需求代码代码解释结尾 需求 给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;…