一、项目介绍
本项目是一个使用Qt框架开发的经典贪吃蛇游戏,旨在通过简单易懂的游戏机制和精美的用户界面,为玩家提供娱乐和编程学习的机会。
二、主要功能
2.1 游戏界面
游戏主要是由三个界面构成,分别是游戏大厅、难度选择和游戏内界面,因此需要建立三个.cpp文件,分别对应的是gamehall.cpp、gameselect.cpp和gameroom.cpp。
2.1.1 gamehall.cpp
在游戏大厅界面,需要将图片设置到背景板上,并且创建一个“开始游戏”按钮
GameHall::GameHall(QWidget *parent)
: QWidget(parent)
, ui(new Ui::GameHall)
{
ui->setupUi(this);
// 设置窗口大小、图标、名字
this->setFixedSize(1080,720);
this->setWindowIcon(QIcon(":Resource/icon.png"));
this->setWindowTitle("贪吃蛇大作战");
// 设置开始按钮
QFont font("华文行楷",18);
QPushButton* pushButton_start = new QPushButton(this);
pushButton_start->setText("开始游戏");
pushButton_start->setFont(font);
pushButton_start->move(520,400);
pushButton_start->setGeometry(440,520,160,90);
pushButton_start->setStyleSheet("QPushButton{border:0px;}");
// 点击“开始游戏”按钮进入难度选择界面
GameSelect* gameSelect = new GameSelect;
connect(pushButton_start,&QPushButton::clicked,[=](){
this->close();
gameSelect->setGeometry(this->geometry());
gameSelect->show();
QSound::play(":Resource/clicked.wav");
});
}
GameHall::~GameHall()
{
delete ui;
}
void GameHall::paintEvent(QPaintEvent *event)
{
(void) *event;
// 实例化画家
QPainter painter(this);
// 实例化绘图设备
QPixmap pix(":Resource/game_hall.png");
// 绘图
painter.drawPixmap(0,0,this->width(),this->height(),pix);
}
2.1.2 gameselect.cpp
在游戏选择界面中,同样是需要设置背景,创建按钮等操作。
其中点击“简单模式”、“正常模式”、“困难模式”可以直接进入游戏房间内,而查阅“历史战绩”时会弹出一个新窗口显示战绩。
GameSelect::GameSelect(QWidget *parent) : QWidget(parent)
{
// 设置“难度选择”界面的图标、名字
this->setFixedSize(1080,720);
this->setWindowIcon(QIcon(":Resource/icon.png"));
this->setWindowTitle("难度选择");
QFont font("华文行楷",24);
GameRoom* gameRoom = new GameRoom;
// 设置选择难度按钮和历史战绩按钮
QPushButton* pushButton_easy = new QPushButton(this);
pushButton_easy->move(460,160);
pushButton_easy->setGeometry(460,160,150,80);
pushButton_easy->setText("简单模式");
pushButton_easy->setFont(font);
pushButton_easy->setStyleSheet("QPushButton{border:0px;color:white}");
QPushButton* pushButton_normal = new QPushButton(this);
pushButton_normal->move(460,280);
pushButton_normal->setGeometry(460,280,150,80);
pushButton_normal->setText("正常模式");
pushButton_normal->setFont(font);
pushButton_normal->setStyleSheet("QPushButton{border:0px;color:white}");
QPushButton* pushButton_hard = new QPushButton(this);
pushButton_hard->move(460,400);
pushButton_hard->setGeometry(460,400,150,80);
pushButton_hard->setText("困难模式");
pushButton_hard->setFont(font);
pushButton_hard->setStyleSheet("QPushButton{border:0px;color:white}");
QPushButton* pushButton_record = new QPushButton(this);
pushButton_record->move(460,520);
pushButton_record->setGeometry(460,520,150,80);
pushButton_record->setText("历史战绩");
pushButton_record->setFont(font);
pushButton_record->setStyleSheet("QPushButton{border:0px;color:white}");
// 点击不同困难模式按钮进入游戏房间
connect(pushButton_easy,&QPushButton::clicked,[=](){
this->close();
gameRoom->setGeometry(this->geometry());
gameRoom->show();
QSound::play(":Resource/clicked.wav");
gameRoom->setTimeout(300);
});
connect(pushButton_normal,&QPushButton::clicked,[=](){
this->close();
gameRoom->setGeometry(this->geometry());
gameRoom->show();
QSound::play(":Resource/clicked.wav");
gameRoom->setTimeout(200);
});
connect(pushButton_hard,&QPushButton::clicked,[=](){
this->close();
gameRoom->setGeometry(this->geometry());
gameRoom->show();
QSound::play(":Resource/clicked.wav");
gameRoom->setTimeout(100);
});
// 设置历史战绩窗口
connect(pushButton_record,&QPushButton::clicked,[=](){
QWidget* widget = new QWidget;
widget->setWindowTitle("历史战绩");
widget->setWindowIcon(QIcon(":Resource/icon.png"));
widget->setFixedSize(500,300);
QSound::play(":Resource/clicked.wav");
QTextEdit* edit = new QTextEdit(widget);
edit->setFont(font);
edit->setFixedSize(500,300);
QFile file("D:/bite/C-program/project/Snake/gamedata.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)
{
(void) *event;
QPainter painter(this);
QPixmap pix(":Resource/game_select.png");
painter.drawPixmap(0,0,this->width(),this->height(),pix);
}
同时在这个界面中,我们需要创建一个“回退”按钮,点击可回退到游戏大厅界面
// 设置回退按钮
QPushButton* pushButton_back = new QPushButton(this);
pushButton_back->move(1000,640);
pushButton_back->setGeometry(1000,640,60,60);
pushButton_back->setIcon(QIcon(":Resource/back.png"));
// 点击回退按钮回到上一页
connect(pushButton_back,&QPushButton::clicked,[=](){
this->close();
GameHall* gameHall = new GameHall;
gameHall->show();
QSound::play(":Resource/clicked.wav");
});
2.1.3 gameroom.cpp
在游戏房间界面中,我们可以看到许多元素,其中不仅有“开始”、“暂停”、“退出”三个按钮,还有控制小蛇移动的方向键按钮,还有计分板等等元素。
我们首先要做的是设计背景以及创建各个按钮。
// 设置游戏房间大小、图标、名字
this->setFixedSize(1080,720);
this->setWindowIcon(QIcon(":Resource/icon.png"));
this->setWindowTitle("游戏房间");
// 开始游戏、暂停游戏
QFont font("楷体",20);
QPushButton* pushButton_start = new QPushButton(this);
pushButton_start->move(890,460);
pushButton_start->setGeometry(890,460,100,60);
pushButton_start->setText("开始");
pushButton_start->setFont(font);
connect(pushButton_start,&QPushButton::clicked,[=](){
isGameStart = true;
timer->start(moveTimeout);
sound = new QSound(":Resource/Trepak.wav");
sound->play();
sound->setLoops(-1);
});
QPushButton* pushButton_stop = new QPushButton(this);
pushButton_stop->move(890,540);
pushButton_stop->setGeometry(890,540,100,60);
pushButton_stop->setText("暂停");
pushButton_stop->setFont(font);
connect(pushButton_stop,&QPushButton::clicked,[=](){
isGameStart = false;
timer->stop();
sound->stop();
});
// 设置方向键的位置、大小、图标和快捷键
QPushButton* pushButton_up = new QPushButton(this);
pushButton_up->move(900,220);
pushButton_up->setGeometry(900,220,80,60);
pushButton_up->setIcon(QIcon(":Resource/up1.png"));
connect(pushButton_up,&QPushButton::clicked,[=](){
if(moveDirect != SnakeDirect::DOWN)
moveDirect = SnakeDirect::UP;
});
pushButton_up->setShortcut(QKeySequence(Qt::Key_W));
QPushButton* pushButton_down = new QPushButton(this);
pushButton_down->move(900,340);
pushButton_down->setGeometry(900,340,80,60);
pushButton_down->setIcon(QIcon(":Resource/down1.png"));
connect(pushButton_down,&QPushButton::clicked,[=](){
if(moveDirect != SnakeDirect::UP)
moveDirect = SnakeDirect::DOWN;
});
pushButton_down->setShortcut(QKeySequence(Qt::Key_S));
QPushButton* pushButton_left = new QPushButton(this);
pushButton_left->move(820,280);
pushButton_left->setGeometry(820,280,80,60);
pushButton_left->setIcon(QIcon(":Resource/left1.png"));
connect(pushButton_left,&QPushButton::clicked,[=](){
if(moveDirect != SnakeDirect::RIGHT)
moveDirect = SnakeDirect::LEFT;
});
pushButton_left->setShortcut(QKeySequence(Qt::Key_A));
QPushButton* pushButton_right = new QPushButton(this);
pushButton_right->move(980,280);
pushButton_right->setGeometry(980,280,80,60);
pushButton_right->setIcon(QIcon(":Resource/right1.png"));
connect(pushButton_right,&QPushButton::clicked,[=](){
if(moveDirect != SnakeDirect::LEFT)
moveDirect = SnakeDirect::RIGHT;
});
pushButton_right->setShortcut(QKeySequence(Qt::Key_D));
// 设置退出按钮
QPushButton* pushButton_exit = new QPushButton(this);
pushButton_exit->move(890,620);
pushButton_exit->setGeometry(890,620,100,60);
pushButton_exit->setText("退出");
pushButton_exit->setFont(font);
2.2 游戏规则
蛇移动时不能碰到自己的身体,否则游戏结束。每吃掉一个食物(食物会随机刷新),身体变长,分数增加。
// 初始化贪吃蛇
snakeList.push_back(QRectF(this->width() * 0.5,this->height() * 0.5,kSnakeNodeWight,kSnakeNodeHeight));
moveUP();
moveUP();
creatFood();
timer = new QTimer(this);
connect(timer,&QTimer::timeout,[=](){
int count = 1;
if(snakeList.front().intersects(foodRect))
{
creatFood();
count++;
QSound::play(":Resource/eatfood.wav");
}
while(count--)
{
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();
});
// 绘制蛇
if(moveDirect == SnakeDirect::UP)
{
pix.load(":Resource/up.png");
}
else if(moveDirect == SnakeDirect::DOWN)
{
pix.load(":Resource/down.png");
}
else if(moveDirect == SnakeDirect::LEFT)
{
pix.load(":Resource/left.png");
}
else
{
pix.load(":Resource/right.png");
}
// 绘制蛇头、身体和尾巴
auto Head = snakeList.front();
painter.drawPixmap(Head.x(),Head.y(),Head.width(),Head.height(),pix);
pix.load(":Resource/Bd.png");
for (int i = 0;i < snakeList.size() - 1;i++)
{
auto Body = snakeList.at(i);
painter.drawPixmap(Body.x(),Body.y(),Body.width(),Body.height(),pix);
};
auto tail = snakeList.back();
painter.drawPixmap(tail.x(),tail.y(),tail.width(),tail.height(),pix);
// 绘制食物
pix.load(":Resource/food.png");
painter.drawPixmap(foodRect.x(),foodRect.y(),foodRect.width(),foodRect.height(),pix);
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::creatFood()
{
foodRect = QRectF(qrand() % (800 / kSnakeNodeWight) * kSnakeNodeWight,
qrand() % (this->height() / kSnakeNodeHeight) * kSnakeNodeHeight,
kSnakeNodeWight,kSnakeNodeHeight);
}
2.3 控制方式
使用界面上的方向键(上、下、左、右)或者通过键盘上的快捷键(W、S、A、D)来控制蛇的移动方向,但不能直接反向移动。
void GameRoom::moveUP()
{
QPointF leftTop;
QPointF rightBottom;
// 蛇头
auto snakeNode = snakeList.front();
int headX = snakeNode.x();
int headY = snakeNode.y();
// 如果穿模
if(headY < 20)
{
leftTop = QPointF(headX,this->height() - kSnakeNodeHeight);
}
else
{
leftTop = QPointF(headX,headY - kSnakeNodeHeight);
}
rightBottom = leftTop + QPointF(kSnakeNodeWight,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(kSnakeNodeWight,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(800 - kSnakeNodeWight,headY);
}
else
{
leftTop = QPointF(headX - kSnakeNodeWight,headY);
}
rightBottom = leftTop + QPointF(kSnakeNodeWight,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 > 760)
{
leftTop = QPointF(0,headY);
}
else
{
leftTop = snakeNode.topRight();
}
rightBottom = leftTop + QPointF(kSnakeNodeWight,kSnakeNodeHeight);
snakeList.push_front(QRectF(leftTop,rightBottom));
}
三、结语
这个项目比较简单,通过这个项目,不仅可以学习到Qt的GUI开发和事件处理技术,还能熟悉C++编程及基本的游戏开发概念。关于Qt中的一些基础知识,我也会在后续逐步更新。
好了,源码我会放在下面,大家有兴趣的可以看一看,欢迎大家一键三连!!!
四、源码
https://gitee.com/hu-jiahao143/project/commit/f83b287c800dd105f8358d7ffb244202ebf015c9