贪吃蛇小游戏
- 一.项目介绍
- **[贪吃蛇项目地址](https://gitee.com/strandingzy/QT/tree/zyy/snake)**
- 二.项目实现
-
- [2.1 游戏大厅](#2.1 游戏大厅)
- 2.2关卡选择界面
- [2.3 游戏房间](#2.3 游戏房间)
-
- [2.3.1 封装贪吃蛇数据结构](#2.3.1 封装贪吃蛇数据结构)
- [2.3.2 初始化游戏房间界面](#2.3.2 初始化游戏房间界面)
- [2.3.3 蛇的移动](#2.3.3 蛇的移动)
- [2.3.4 初始化贪吃蛇生体和食物节点](#2.3.4 初始化贪吃蛇生体和食物节点)
- [2.3.5 实现定时器的超时槽函数](#2.3.5 实现定时器的超时槽函数)
- [2.3.6 实现各个方向的移动](#2.3.6 实现各个方向的移动)
- [2.3.7 重写绘图事件函数进行渲染](#2.3.7 重写绘图事件函数进行渲染)
- [2.3.8 检查是否自己会撞到自己](#2.3.8 检查是否自己会撞到自己)
- [2.3.9 设置游戏开始和游戏暂停按钮](#2.3.9 设置游戏开始和游戏暂停按钮)
- [2.3.10 设置游戏开始和游戏暂停按钮](#2.3.10 设置游戏开始和游戏暂停按钮)
- [2.3.11 获取历史战绩](#2.3.11 获取历史战绩)
- 代码的总体文件
- gamehall.h
- gameroom.h
- gameselect.h
- gamehall.cpp
- gameroom.cpp
- gameselect.cpp
一.项目介绍
贪吃蛇游戏是⼀款休闲益智类游戏。它通过控制蛇头方向吃食物,从而使得蛇变得越来越长。在本游戏中设置了上下左右四个⽅向键来控制蛇的移动方向。⻝物的产⽣是随机⽣成的,当蛇每吃⼀次⻝物就会增加⼀节身体,同时游戏积分也会相应的加⼀。在本游戏的设计中,蛇的⾝体会越吃越长,身体越长对应的难度就越⼤,因为⼀旦蛇头和⾝体相交游戏就会结束。
贪吃蛇项目地址
界面一:游戏大厅
当用户点击 "开始游戏" 按钮之后,就会进⼊到关卡选择界面。
界面二:关卡选择界面
在关卡选择界面上设置了三个游戏模式按钮,分别是:简单模式、正常模式、困难模式;⼀个 "历史战绩" 按钮;⼀个返回游戏大厅界⾯的按钮。
界面三:游戏界面
在游戏界⾯,如果想要开始游戏,⾸先点击 "开始" 按钮,此时蛇就会移动并且还有背景⾳效。如果想要暂停游戏,那么点击 "暂停" 按钮即可。当我们在游戏时,可以通过右边控制区域的上下左右⽅向键来控制蛇的移动。当蛇每吃⼀次⻝物时,伴随有吃⻝物的⾳效,蛇⾝会增加⼀节⻓度,并且分数
积分也会相应的加⼀。最后在控制区域的右下⻆布局了⼀个 "退出" 按钮,当点击 "退出" 按钮时,就会有⼀个弹窗提⽰,如下
如果我们点击 "Ok" ,则会退出到游戏关卡选择界⾯,此时我们可以重新选择游戏模式。如果点击"Cancel" ,则不会退出游戏,我们还是处在当前游戏房间界⾯。
二.项目实现
2.1 游戏大厅
游戏大厅界面比较简单, 只有⼀个背景图和⼀个按钮
- 背景图片的渲染,通过QT的绘图事件完成
cpp
void GameHall::paintEvent(QPaintEvent *event)
{
//实例化画家对象
QPainter painter(this);
//实例化绘图设备
QPixmap pix(":res/game_hall.png");
//绘画
painter.drawPixmap(0,0,this->width(),this->height(),pix);
}
- 按钮的响应通过QT的信号槽机制完成
c
GameHall::GameHall(QWidget *parent)
: QWidget(parent)
, ui(new Ui::GameHall)
{
ui->setupUi(this);
this->setFixedSize(1500,1000);//设置窗口大小
this->setWindowTitle(QString("贪吃蛇游戏"));
this->setWindowIcon(QIcon(":res/ico.png"));//设置窗口图标
//
QFont font("华文行楷",20);
QPushButton * strbutton = new QPushButton(this);
strbutton->setText("开始游戏");
strbutton->setFont(font);
strbutton->move(650,700);
strbutton->setShortcut(QKeySequence(Qt::Key_Space));
strbutton->setStyleSheet("QPushButton{border:0px}");//给按钮设置样式 去除边框
GameSelect * gameSelect = new GameSelect;
connect(strbutton,&QPushButton::clicked,[=](){
this->close();//当前窗口关闭
gameSelect->setGeometry(this->geometry());//设置第二个窗口于当前窗口一致
gameSelect->show();//新的窗口打开
QSound::play(":res/clicked.wav");
});
}
- 这里的Qfound类,使用需要在
.pro
的文件中加上QT += core gui multimedia
- 这里使用
strbutton->setShortcut(QKeySequence(Qt::Key_Space));
给按钮添加了快捷键- 使用
strbutton->setStyleSheet("QPushButton{border:0px}");
去除按钮边框样式- 通过槽函数跳转到新的窗口 ,在槽函数中使用了
Lambda
表达式
通过QSound
创建音频对象,使点击按钮有声音
2.2关卡选择界面
关卡选择界⾯包含⼀个背景图和五个按钮,背景图的绘制和游戏⼤厅背景图绘制⼀样,同样使用的是Qt中的绘图事件,下面依次介绍。
- 背景图片的渲染,通过QT的绘图事件完成
c
void GameHall::paintEvent(QPaintEvent *event)
{
//实例化画家对象
QPainter painter(this);
//实例化绘图设备
QPixmap pix(":res/game_hall.png");
//绘画
painter.drawPixmap(0,0,this->width(),this->height(),pix);
}
- 按钮的响应通过QT的信号和槽机制完成
cpp
#include "gameselect.h"
#include"gameroom.h"
#include<QIcon>
#include<QPushButton>
#include"gamehall.h"
#include<QSound>
#include<QPainter>
GameSelect::GameSelect(QWidget *parent) : QWidget(parent)
{
this->setFixedSize(1500,1000);//设置窗口大小
this->setWindowIcon(QIcon(":res/ico.png"));
this->setWindowTitle("游戏关卡选择");
QPushButton* backBtn = new QPushButton(this);//返回按钮
backBtn->move(1400,900);
backBtn->setIcon(QIcon(":res/back.png"));
backBtn->setShortcut(QKeySequence(Qt::Key_Escape));
connect(backBtn,&QPushButton::clicked,[=](){
this->close();
GameHall * gameHall = new GameHall;
gameHall->show();
QSound::play(":res/clicked.wav");
});
QFont font("华文行楷",20);
gameroom*gameRoom = new gameroom;
QPushButton* simpleBtn = new QPushButton(this);
simpleBtn->move(650,300);
simpleBtn->setFont(font);
simpleBtn->setText("简单模式");
connect(simpleBtn,&QPushButton::clicked,[=](){
this->close();
gameRoom->setGeometry(this->geometry());
gameRoom->show();
});
QPushButton*normalBtn = new QPushButton(this);
normalBtn->move(650,400);
normalBtn->setFont(font);
normalBtn->setText("正常模式");
connect(normalBtn,&QPushButton::clicked,[=](){
this->close();
gameRoom->setGeometry(this->geometry());
gameRoom->show();
});
QPushButton*hardBtn = new QPushButton(this);
hardBtn->move(650,500);
hardBtn->setFont(font);
hardBtn->setText("困难模式");
connect(hardBtn,&QPushButton::clicked,[=]{
this->close();
gameRoom->setGeometry(this->geometry());
gameRoom->show();
});
QPushButton*hisBtn = new QPushButton(this);
hisBtn->move(650,600);
hisBtn->setFont(font);
hisBtn->setText("历史战绩");
}
void GameSelect::paintEvent(QPaintEvent *event)
{
QPainter painter(this);//实例化画家对象
QPixmap pix(":res/game_select.png");
painter.drawPixmap(0,0,this->width(),this->height(),pix);
}
connect(hardBtn,&QPushButton::clicked,[=]{ this->close(); gameRoom->setGeometry(this->geometry()); gameRoom->show(); });
通过关联信号槽,使得可以选择游戏模式,首先把当前窗口关闭,然后显示新的窗口,在设置与当前窗口的大小一样
2.3 游戏房间
游戏房间界面包含下面几个部分:
- 背景的绘制
- 蛇的绘制,蛇的移动,判断蛇是否会撞到自己
- 积分的累加和绘制
怎么让蛇动起来?
- 我们可以⽤⼀个链表表示贪吃蛇,⼀个小方块表示蛇的⼀个节点, 我们设置蛇的默认⻓度为3;
- 向上移动的逻辑就是在蛇的上方加⼊⼀个小方块, 然后把最后⼀个小⽅块删除即可
- 需要用到定时器Qtimer 每100 - 200ms 重新渲染
怎么判断蛇有没有吃到食物?
- 判断蛇头和食物的坐标是否相交,
怎么控制蛇的移动?
- 借助QT的实践机制实现, 重写keyPressEvent即可, 在函数中监控想要的键盘事件即可
- 通过绘制四个按钮,使⽤信号和槽的机制控制蛇的上、下、左、右移动方向
2.3.1 封装贪吃蛇数据结构
c
#ifndef GAMEROOM_H
#define GAMEROOM_H
#include <QWidget>
#include<QSound>
enum class SnakeDirect
{
UP=0,
DOWN,
LEFT,
RIGHT
};
class gameroom : public QWidget
{
Q_OBJECT
public:
explicit gameroom(QWidget *parent = nullptr);
//重写绘图事件
void paintEvent(QPaintEvent *event);
void moveUp();
void moveDown();
void moveLeft();
void moveRight();
bool checkFail();
void createNewFood();
void setTimeout(int timeout){moveTime=timeout;}
private:
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 moveTime = KDefaultTimeout;
};
#endif // GAMEROOM_H
2.3.2 初始化游戏房间界面
设置窗口大小、标题、图标等
cpp
gameroom::gameroom(QWidget *parent) : QWidget(parent)
{
this->setFixedSize(1500,1000);
this->setWindowIcon(QIcon(":res/ico.png"));
this->setWindowTitle("游戏房间");
}
2.3.3 蛇的移动
蛇的移动方向为:上、下、左、右。通过在游戏房间中布局四个按钮来控制蛇的移动方向。
· 注意: 这里贪吃蛇不允许直接掉头, 比如当前是向上的, 不能直接修改为向下。
cpp
QPushButton*up= new QPushButton(this);
QPushButton*down= new QPushButton(this);
QPushButton*left= new QPushButton(this);
QPushButton*right= new QPushButton(this);
up->move(1320,750);
down->move(1320,900);
left->move(1220,825);
right->move(1420,825);
up->setText("↑");
// up->setShortcut(QKeySequence(Qt::Key_W));
down->setText("↓");
// down->setShortcut(QKeySequence(Qt::Key_S));
left->setText("←");
// left->setShortcut(QKeySequence(Qt::Key_A));
right->setText("→");
// left->setShortcut(QKeySequence(Qt::Key_D));
up->setStyleSheet("QPushButton{border:0px}");
down->setStyleSheet("QPushButton{border:0px}");
left->setStyleSheet("QPushButton{border:0px}");
right->setStyleSheet("QPushButton{border:0px}");
QFont ft("宋体",36);
up->setFont(ft);
down->setFont(ft);
right->setFont(ft);
left->setFont(ft);
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::LEFT)
{
moveDirect = SnakeDirect::RIGHT;
}
});
connect(right,&QPushButton::clicked,[=](){
if(moveDirect!= SnakeDirect::RIGHT)
{
moveDirect = SnakeDirect::LEFT;
}
});
2.3.4 初始化贪吃蛇生体和食物节点
c
GameRoom::GameRoom(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{ snakeList.push_back(QRectF(this->width()*0.5,this->height()*0.5,KSnakeNodeWidth,KSnakeNodeHeight));
moveUp();
moveUp();
moveUp();
//创建食物
createNewFood();
}
moveUp() 的功能是将蛇向上移动⼀次, 即在上⽅新增⼀个节点, 但不删除尾部节点。
createNewFood() ⽅法的功能是随机创建⼀个⻝物节点:
cpp
void gameroom::createNewFood()
{
foodRect = QRectF(qrand()%(1500/KSnakeNodeWidth)*KSnakeNodeWidth,
qrand()%(this->height()/KSnakeNodeHeight)*KSnakeNodeHeight,
KSnakeNodeWidth,KSnakeNodeHeight);
}
2.3.5 实现定时器的超时槽函数
定时器的是为了实现每隔⼀段时间能处理移动的逻辑并且更新绘图事件。
- ⾸先, 需要判断蛇头和⻝物节点坐标是否相交
- 如果相交, 需要创建新的⻝物节点, 并且需要更新蛇的⻓度, 所以 cnt 需要 +1 ;
- 如果不相交, 那么直接处理蛇的移动即可。
- 根据蛇移动方向 moveDirect 来处理蛇的移动, 处理方法是在前方加⼀个, 并且删除后方节点;
- 重新触发绘图事件, 更新渲染
cpp
timer = new QTimer(this);
connect(timer,&QTimer::timeout,[=]{
int cnt =1 ;
if(snakeList.front().intersects(foodRect))
{
//intersects判断会不会相交
createNewFood();
cnt++;
QSound::play(":res/eatfood.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();
});
2.3.6 实现各个方向的移动
各个方向的移动主要在于更新矩形节点的坐标, 要注意的是⼀定要处理边界的情况, 当边界不够存储⼀个新的节点, 我们需要处理穿墙逻辑。
- 实现向上移动
c
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));//更新到链表中
}
- 实现向下移动
c
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));
}
- 实现向左移动
c
void gameroom::moveLeft()
{
QPointF leftTop;//左上角坐标
QPointF rightBottom;//右下角坐标
auto snakeNode = snakeList.front();
int headx = snakeNode.x();
int heady = snakeNode.y();
if(headx < 0)
{
leftTop = QPointF(1500 - KSnakeNodeWidth,heady);
}
else {
leftTop = QPointF(headx - KSnakeNodeWidth,heady);
}
rightBottom = leftTop + QPointF(KSnakeNodeWidth,KSnakeNodeHeight);
snakeList.push_front(QRectF(leftTop,rightBottom));
}
- 实现向右移动
c
void gameroom::moveRight()
{
QPointF leftTop;//左上角坐标
QPointF rightBottom;//右下角坐标
auto snakeNode = snakeList.front();
int headx = snakeNode.x();
int heady = snakeNode.y();
if(heady + KSnakeNodeWidth> 1200)
{
leftTop = QPointF(0,heady);
}
else {
leftTop = snakeNode.bottomRight();
}
rightBottom = leftTop + QPointF(KSnakeNodeWidth,KSnakeNodeHeight);
snakeList.push_front(QRectF(leftTop,rightBottom));
}
2.3.7 重写绘图事件函数进行渲染
重写基类的 paintEvent() 方法进行渲染:
- 渲染背景图
- 渲染蛇头
- 渲染蛇身体
- 渲染蛇尾巴
- 渲染右边游戏控制区域
- 渲染⻝物节点
- 渲染当前分数
- 游戏结束渲染 game over
c
void gameroom::paintEvent(QPaintEvent *event)
{
QPainter painter(this);//实例画家对象
QPixmap pix;
pix.load(":res/game_room.png");
painter.drawPixmap(0,0,1200,1400,pix);
pix.load(":res/bg1.png");
painter.drawPixmap(1200,0,300,1100,pix);
//绘制蛇
//头
if(moveDirect == SnakeDirect::UP)
{
pix.load(":res/up.png");
}
else if (moveDirect == SnakeDirect::DOWN) {
pix.load(":res/down.png");
}
else if (moveDirect == SnakeDirect::LEFT) {
pix.load(":res/left.png");
}
else if(moveDirect == SnakeDirect::RIGHT){
pix.load(":res/right.png");
}
auto snakHead = snakeList.front();
painter.drawPixmap(snakHead.x(),snakHead.y(),snakHead.width(),snakHead.height(),pix);
//身体
pix.load(":res/Bd.png");
for (int i= 1;i < snakeList.size()-1;i++) {
auto node = snakeList.at(i); //at 获取每一个节点
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(":res/food.png");
painter.drawPixmap(foodRect.x(),foodRect.y(),foodRect.width(),foodRect.height(),pix);
//分数
pix.load(":res/sorce_bg.png");
painter.drawPixmap(this->width()*0.85,this->height()*0.1,90,40,pix);
QPen pen;
pen.setColor(Qt::black);
QFont font("方正舒体",22);
painter.setFont(font);
painter.setPen(pen);
painter.drawText(this->width()*0.9,this->height()*0.06,QString("%1").arg(snakeList.size()));
//往文件中写分数
int c = snakeList.size();
QFile file("C:/Users/zyy/Desktop/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);
QFont font("方正舒体",22);
painter.setFont(font);
painter.drawText(this->width()*0.45,this->height()*0.5,QString("GAME OVER!"));
timer->stop();
QSound::play(":res/gameover.wav");
sound->stop();
}
}
2.3.8 检查是否自己会撞到自己
c
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;
}
2.3.9 设置游戏开始和游戏暂停按钮
c
QPushButton * strBtn = new QPushButton(this);
QPushButton * stopBtn = new QPushButton(this);
strBtn->move(1270,100);
stopBtn->move(1270,170);
strBtn->setText("开始");
strBtn->setShortcut(QKeySequence(Qt::Key_C+Qt::CTRL));
stopBtn->setText("暂停");
stopBtn->setShortcut(QKeySequence(Qt::Key_V+Qt::CTRL));
QFont font("楷体",20);
strBtn->setFont(font);
stopBtn->setFont(font);
connect(strBtn,&QPushButton::clicked,[=]{
sound = new QSound(":res/Trepak.wav");
sound->play();
sound->setLoops(-1);//一直循环播放
isGameStart = true;
timer->start(moveTime);
});
connect(stopBtn,&QPushButton::clicked,[=]{
isGameStart = false;
timer->stop();
sound->stop();
});
2.3.10 设置游戏开始和游戏暂停按钮
当我们点击退出游戏按钮时,当前游戏房间窗⼝不会⽴即退出,而是会弹窗提示,提示我们是否要退出游戏,效果如下图示:
这个弹窗提示我们是通过 Qt 中的消息盒子来实现的,具体实现过程如下:
c
//退出游戏
QPushButton*ExitBtn = new QPushButton(this);
ExitBtn->setText("退出游戏");
ExitBtn->move(1240,400);
ExitBtn->setFont(font);
QMessageBox *msg = new QMessageBox(this);
QPushButton *ok = new QPushButton("ok");
QPushButton *canel = new QPushButton("canel");
msg->addButton(ok,QMessageBox::AcceptRole);
msg->addButton(canel,QMessageBox::RejectRole);
msg->setWindowTitle("退出游戏");
msg->setText("确认退出游戏吗");
connect(ExitBtn,&QPushButton::clicked,[=](){
msg->show();
msg->exec();//时间轮询
QSound::play("res/clicked.wav");
GameSelect *select = new GameSelect;
if(msg->clickedButton()==ok)
{
this->close();
select->show();
}
else {
msg->close();
}
});
}
2.3.11 获取历史战绩
对于历史战绩的获取我们是通过 Qt 中的读写文件操作来实现的。具体实现过程如下:
- 写文件: 向文件中写入蛇的长度
c
int c = snakeList.size();
QFile file("C:/Users/zyy/Desktop/1.txt");
if(file.open(QIODevice::WriteOnly| QIODevice::Text))
{
QTextStream out(&file);
int num = c;
out<<num ;
file.close();
}
- 读文件:读取写入文件中蛇的长度
c
int c = snakeList.size();
QFile file("C:/Users/zyy/Desktop/1.txt");
if(file.open(QIODevice::WriteOnly| QIODevice::Text))
{
QTextStream out(&file);
int num = c;
out<<num ;
file.close();
}
代码的总体文件
gamehall.h
c
#ifndef GAMEHALL_H
#define GAMEHALL_H
#include <QWidget>
QT_BEGIN_NAMESPACE
namespace Ui { class GameHall; }
QT_END_NAMESPACE
class GameHall : public QWidget
{
Q_OBJECT
public:
GameHall(QWidget *parent = nullptr);
~GameHall();
//从写绘图事件
void paintEvent(QPaintEvent *event);
private:
Ui::GameHall *ui;
};
#endif // GAMEHALL_H
gameroom.h
cpp
#ifndef GAMEROOM_H
#define GAMEROOM_H
#include <QWidget>
#include<QSound>
enum class SnakeDirect
{
UP=0,
DOWN,
LEFT,
RIGHT
};
class gameroom : public QWidget
{
Q_OBJECT
public:
explicit gameroom(QWidget *parent = nullptr);
//重写绘图事件
void paintEvent(QPaintEvent *event);
void moveUp();
void moveDown();
void moveLeft();
void moveRight();
bool checkFail();
void createNewFood();
void setTimeout(int timeout){moveTime=timeout;}
private:
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 moveTime = KDefaultTimeout;
};
#endif // GAMEROOM_H
gameselect.h
c
#ifndef GAMESELECT_H
#define GAMESELECT_H
#include <QWidget>
class GameSelect : public QWidget
{
Q_OBJECT
public:
explicit GameSelect(QWidget *parent = nullptr);
//绘图事件
void paintEvent(QPaintEvent *event);
signals:
};
#endif // GAMESELECT_H
gamehall.cpp
cpp
#include "gamehall.h"
#include "ui_gamehall.h"
#include<QPainter>
#include<QIcon>
#include<QFont>
#include<QPushButton>
#include"gameselect.h"
#include<QSound>
GameHall::GameHall(QWidget *parent)
: QWidget(parent)
, ui(new Ui::GameHall)
{
ui->setupUi(this);
this->setFixedSize(1500,1000);//设置窗口大小
this->setWindowTitle(QString("贪吃蛇游戏"));
this->setWindowIcon(QIcon(":res/ico.png"));//设置窗口图标
//
QFont font("华文行楷",20);
QPushButton * strbutton = new QPushButton(this);
strbutton->setText("开始游戏");
strbutton->setFont(font);
strbutton->move(650,700);
strbutton->setShortcut(QKeySequence(Qt::Key_Space));
strbutton->setStyleSheet("QPushButton{border:0px}");//给按钮设置样式 去除边框
GameSelect * gameSelect = new GameSelect;
connect(strbutton,&QPushButton::clicked,[=](){
this->close();//当前窗口关闭
gameSelect->setGeometry(this->geometry());//设置第二个窗口于当前窗口一致
gameSelect->show();//新的窗口打开
QSound::play(":res/clicked.wav");
});
}
GameHall::~GameHall()
{
delete ui;
}
void GameHall::paintEvent(QPaintEvent *event)
{
//实例化画家对象
QPainter painter(this);
//实例化绘图设备
QPixmap pix(":res/game_hall.png");
//绘画
painter.drawPixmap(0,0,this->width(),this->height(),pix);
}
gameroom.cpp
cpp
#include "gameroom.h"
#include<QPainter>
#include<QPixmap>
#include<QIcon>
#include<QTimer>
#include<QPushButton>
#include<QMessageBox>
#include<QFile>
#include<QTextStream>
#include"gameselect.h"
gameroom::gameroom(QWidget *parent) : QWidget(parent)
{
this->setFixedSize(1500,1000);
this->setWindowIcon(QIcon(":res/ico.png"));
this->setWindowTitle("游戏房间");
//初始化贪吃蛇
snakeList.push_back(QRectF(this->width()*0.5,this->height()*0.5,KSnakeNodeWidth,KSnakeNodeHeight));
moveUp();
moveUp();
moveUp();
//创建食物
createNewFood();
timer = new QTimer(this);
connect(timer,&QTimer::timeout,[=]{
int cnt =1 ;
if(snakeList.front().intersects(foodRect))
{
//intersects判断会不会相交
createNewFood();
cnt++;
QSound::play(":res/eatfood.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 * strBtn = new QPushButton(this);
QPushButton * stopBtn = new QPushButton(this);
strBtn->move(1270,100);
stopBtn->move(1270,170);
strBtn->setText("开始");
strBtn->setShortcut(QKeySequence(Qt::Key_C+Qt::CTRL));
stopBtn->setText("暂停");
stopBtn->setShortcut(QKeySequence(Qt::Key_V+Qt::CTRL));
QFont font("楷体",20);
strBtn->setFont(font);
stopBtn->setFont(font);
connect(strBtn,&QPushButton::clicked,[=]{
sound = new QSound(":res/Trepak.wav");
sound->play();
sound->setLoops(-1);//一直循环播放
isGameStart = true;
timer->start(moveTime);
});
connect(stopBtn,&QPushButton::clicked,[=]{
isGameStart = false;
timer->stop();
sound->stop();
});
//方向控制
QPushButton*up= new QPushButton(this);
QPushButton*down= new QPushButton(this);
QPushButton*left= new QPushButton(this);
QPushButton*right= new QPushButton(this);
up->move(1320,750);
down->move(1320,900);
left->move(1220,825);
right->move(1420,825);
up->setText("↑");
up->setShortcut(QKeySequence(Qt::Key_W));
down->setText("↓");
down->setShortcut(QKeySequence(Qt::Key_S));
left->setText("←");
left->setShortcut(QKeySequence(Qt::Key_A));
right->setText("→");
left->setShortcut(QKeySequence(Qt::Key_D));
up->setStyleSheet("QPushButton{border:0px}");
down->setStyleSheet("QPushButton{border:0px}");
left->setStyleSheet("QPushButton{border:0px}");
right->setStyleSheet("QPushButton{border:0px}");
QFont ft("宋体",36);
up->setFont(ft);
down->setFont(ft);
right->setFont(ft);
left->setFont(ft);
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;
}
});
//退出游戏
QPushButton*ExitBtn = new QPushButton(this);
ExitBtn->setText("退出游戏");
ExitBtn->move(1240,400);
ExitBtn->setFont(font);
QMessageBox *msg = new QMessageBox(this);
QPushButton *ok = new QPushButton("ok");
QPushButton *canel = new QPushButton("canel");
msg->addButton(ok,QMessageBox::AcceptRole);
msg->addButton(canel,QMessageBox::RejectRole);
msg->setWindowTitle("退出游戏");
msg->setText("确认退出游戏吗");
connect(ExitBtn,&QPushButton::clicked,[=](){
msg->show();
msg->exec();//时间轮询
QSound::play("res/clicked.wav");
GameSelect *select = new GameSelect;
if(msg->clickedButton()==ok)
{
this->close();
select->show();
}
else {
msg->close();
}
});
}
void gameroom::paintEvent(QPaintEvent *event)
{
QPainter painter(this);//实例画家对象
QPixmap pix;
pix.load(":res/game_room.png");
painter.drawPixmap(0,0,1200,1400,pix);
pix.load(":res/bg1.png");
painter.drawPixmap(1200,0,300,1100,pix);
//绘制蛇
//头
if(moveDirect == SnakeDirect::UP)
{
pix.load(":res/up.png");
}
else if (moveDirect == SnakeDirect::DOWN) {
pix.load(":res/down.png");
}
else if (moveDirect == SnakeDirect::LEFT) {
pix.load(":res/left.png");
}
else if(moveDirect == SnakeDirect::RIGHT){
pix.load(":res/right.png");
}
auto snakHead = snakeList.front();
painter.drawPixmap(snakHead.x(),snakHead.y(),snakHead.width(),snakHead.height(),pix);
//身体
pix.load(":res/Bd.png");
for (int i= 1;i < snakeList.size()-1;i++) {
auto node = snakeList.at(i); //at 获取每一个节点
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(":res/food.png");
painter.drawPixmap(foodRect.x(),foodRect.y(),foodRect.width(),foodRect.height(),pix);
//分数
pix.load(":res/sorce_bg.png");
painter.drawPixmap(this->width()*0.85,this->height()*0.1,90,40,pix);
QPen pen;
pen.setColor(Qt::black);
QFont font("方正舒体",22);
painter.setFont(font);
painter.setPen(pen);
painter.drawText(this->width()*0.9,this->height()*0.06,QString("%1").arg(snakeList.size()));
//往文件中写分数
int c = snakeList.size();
QFile file("C:/Users/zyy/Desktop/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);
QFont font("方正舒体",22);
painter.setFont(font);
painter.drawText(this->width()*0.45,this->height()*0.5,QString("GAME OVER!"));
timer->stop();
QSound::play(":res/gameover.wav");
sound->stop();
}
}
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(1500 - 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(heady + KSnakeNodeWidth> 1200)
{
leftTop = QPointF(0,heady);
}
else {
leftTop = snakeNode.bottomRight();
}
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()%(1500/KSnakeNodeWidth)*KSnakeNodeWidth,
qrand()%(this->height()/KSnakeNodeHeight)*KSnakeNodeHeight,
KSnakeNodeWidth,KSnakeNodeHeight);
}
gameselect.cpp
cpp
#include "gameselect.h"
#include"gameroom.h"
#include<QIcon>
#include<QPushButton>
#include"gamehall.h"
#include<QSound>
#include<QPainter>
#include<QTextEdit>
#include<QFile>
#include<QTextStream>
GameSelect::GameSelect(QWidget *parent) : QWidget(parent)
{
this->setFixedSize(1500,1000);//设置窗口大小
this->setWindowIcon(QIcon(":res/ico.png"));
this->setWindowTitle("游戏关卡选择");
QPushButton* backBtn = new QPushButton(this);//返回按钮
backBtn->move(1400,900);
backBtn->setIcon(QIcon(":res/back.png"));
backBtn->setShortcut(QKeySequence(Qt::Key_Escape));
connect(backBtn,&QPushButton::clicked,[=](){
this->close();
GameHall * gameHall = new GameHall;
gameHall->show();
QSound::play(":res/clicked.wav");
});
QFont font("华文行楷",20);
gameroom*gameRoom = new gameroom;
QPushButton* simpleBtn = new QPushButton(this);
simpleBtn->move(650,300);
simpleBtn->setFont(font);
simpleBtn->setText("简单模式");
connect(simpleBtn,&QPushButton::clicked,[=](){
this->close();
gameRoom->setGeometry(this->geometry());
gameRoom->show();
gameRoom->setTimeout(300);
});
QPushButton*normalBtn = new QPushButton(this);
normalBtn->move(650,400);
normalBtn->setFont(font);
normalBtn->setText("正常模式");
connect(normalBtn,&QPushButton::clicked,[=](){
this->close();
gameRoom->setGeometry(this->geometry());
gameRoom->show();
gameRoom->setTimeout(200);
});
QPushButton*hardBtn = new QPushButton(this);
hardBtn->move(650,500);
hardBtn->setFont(font);
hardBtn->setText("困难模式");
connect(hardBtn,&QPushButton::clicked,[=]{
this->close();
gameRoom->setGeometry(this->geometry());
gameRoom->show();
gameRoom->setTimeout(300);
});
QPushButton*hisBtn = new QPushButton(this);
hisBtn->move(650,600);
hisBtn->setFont(font);
hisBtn->setText("历史战绩");
connect(hisBtn,&QPushButton::clicked,[=](){
QWidget *widget = new QWidget;
widget->setWindowTitle("历史战绩");
widget->setFixedSize(500,300);
QTextEdit *edit = new QTextEdit(widget);
edit->setFont(font);
edit->setFixedSize(500,300);
QFile file("C:/Users/zyy/Desktop/1.txt");
file.open(QIODevice::ReadOnly);//读取内容
QTextStream in(&file);
int data = in.readLine().toInt();//读取一行的数据
edit->append("得分为:");
edit->append(QString::number(data));//写入文本中
widget->show();
});
}
void GameSelect::paintEvent(QPaintEvent *event)
{
QPainter painter(this);//实例化画家对象
QPixmap pix(":res/game_select.png");
painter.drawPixmap(0,0,this->width(),this->height(),pix);
}