前言
该文章是记录用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_NAMESPACE
class GamePage : public QWidget
{
Q_OBJECT
public:
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_OBJECT
public:
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_OBJECT
public:
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