Qt—贪吃蛇项目(由0到1实现贪吃蛇项目)
用Qt实现一个贪吃蛇项目
- 一、项目介绍
- 二、游戏大厅界面实现
- 2.1完成游戏大厅的背景图。
- 2.2创建一个按钮,给它设置样式,并且可以跳转到别的页面
- 三、难度选择界面实现
- 四、 游戏界面实现
- 五、在文件中写入历史战绩
- 5.1 从文件里提取分数
- 5.2 把贪吃蛇的长度存入文件
- 六、总结
一、项目介绍
贪吃蛇是久负盛名的游戏,它也和俄罗斯⽅块,扫雷等游戏位列经典游戏的⾏列。 在编程语⾔的教学中,我们以贪吃蛇为例,从设计到代码实现来提升编程能⼒和逻辑能⼒。它通过控制蛇头⽅向吃⻝物,从⽽使得蛇变得越来越⻓。在本游戏中设置了上下左右四个⽅向键来控制蛇的移动⽅向。⻝物的产⽣是随机⽣成的,当蛇每吃⼀次⻝物 就会增加⼀节⾝体,同时游戏积分也会相应的加⼀。
在本游戏的设计中,蛇的⾝体会越吃越⻓,⾝体越⻓对应的难度就越⼤,因为⼀旦蛇头和⾝体相交游戏就会结束。
本项⽬使⽤ Qt 实现⼀款简单的贪吃蛇游戏。
项目的目标:
- 游戏大厅界面实现
- 难度选择界面实现
- 游戏界面实现
- 分数记录界面实现
二、游戏大厅界面实现
2.1完成游戏大厅的背景图。
1.背景图的渲染,我们通过QT的绘图事件完成
void gamehall::paintEvent(QPaintEvent *event) { //实例化一个画家 QPainter paint(this); //实例化一个设备 QPixmap pix(":res/game_hall.png"); //进行绘画 paint.drawPixmap(0,0,this->width(),this->height(),pix); }2.对我们的标题栏进行设置
//设置窗口大小 this->setFixedSize(1000,800); //设置标题栏图标 this->setWindowIcon(QIcon(":res/ico.png")); //设置标题栏标签 this->setWindowTitle("游戏大厅");2.2创建一个按钮,给它设置样式,并且可以跳转到别的页面
1.创建一个按钮,给它设置样式
QFont font("华文行楷",25); //设置按钮 QPushButton *intoBtn= new QPushButton(this); intoBtn->setFont(font); intoBtn->setText("开始游戏"); intoBtn->setStyleSheet("QPushButton{border:0px;}"); intoBtn->move(450,520);2.创建的新页面
点击add new
3. 建立信号槽,使按钮被点击后跳转界面(这个跳转的界面,是新创建的文件,点击按钮会发出声音)
//创建另一个窗口 GameSelect *select=new GameSelect(); connect(intoBtn,&QPushButton::clicked,[=]{ //关闭上个窗口 this->close(); //设置窗口大小 select->setGeometry(this->geometry()); //显示窗口 select->show(); //发出声音 QSound::play(":res/clicked.wav");4.总代码
#include "gamesnake.h" #include "ui_gamesnake.h" #include "gameselect.h" #include #include #include #include #include GameSnake::GameSnake(QWidget *parent) : QWidget(parent) , ui(new Ui::GameSnake) { ui->setupUi(this); //设置窗口大小 this->setFixedSize(1000,800); //设置窗口图标 this->setWindowIcon(QIcon(":res/ico.png")); //设置窗口标签 this->setWindowTitle("游戏大厅"); //设置字体 QFont font("华文行楷",25); //设置按钮 QPushButton *intoBtn= new QPushButton(this); intoBtn->setFont(font); intoBtn->setText("开始游戏"); intoBtn->setStyleSheet("QPushButton{border:0px;}"); intoBtn->move(450,520); //创建另一个窗口 GameSelect *select=new GameSelect(); connect(intoBtn,&QPushButton::clicked,[=]{ //关闭上个窗口 this->close(); select->setGeometry(this->geometry()); select->show(); QSound::play(":res/clicked.wav"); }); } GameSnake::~GameSnake() { delete ui; } void GameSnake::paintEvent(QPaintEvent *event) { //实例化一个画家 QPainter paint(this); //实例化一个设备 QPixmap pix(":res/game_hall.png"); //进行绘画 paint.drawPixmap(0,0,this->width(),this->height(),pix); }注意pro文件里的配置加上multimedai
三、难度选择界面实现
我们已经把贪吃蛇的游戏界面完成了,下面就进入关卡选择界面。
在关卡选择界⾯上设置了四个游戏模式按钮,分别是:容易模式、正常模式、困难模式、地狱模式;⼀个 “历史战绩” 按钮;⼀个返回游戏⼤厅界⾯的按钮。
1. 创建按钮,给它设置样式。
//容易关卡 QPushButton *briefnessBtn= new QPushButton(this); briefnessBtn->setStyleSheet("QPushButton{background-color:#0072C6;color: black; border:0px groove gray;border-radius:10px;padding:6px;}"); briefnessBtn->move(400,200); QFont font("华文行楷",25); briefnessBtn->setFont(font); briefnessBtn->setText("容易模式");如图,我已经创建了5个按钮:
2. 为了使第二个界面可以返回到第一个界面,创建一个按钮,按下按钮触发信号,就可以返回第一个页面。(返回按钮)
//返回按钮 QPushButton *returnBtn= new QPushButton(this); returnBtn->move(850,700); returnBtn->setIcon(QIcon(":res/up.png")); connect(returnBtn,&QPushButton::clicked,[=]{ this->close(); GameSnake *snake= new GameSnake(); snake->show(); QSound::play(":res/clicked.wav"); });3. 游戏选择页面跳转到游戏大厅
实现页面之间的跳转
//实现游戏房间和选择关卡切换 //创建另一个窗口 GameRoom *room=new GameRoom(); //创建按钮,给按钮设置格式 //容易关卡 QPushButton *briefnessBtn= new QPushButton(this); briefnessBtn->setStyleSheet("QPushButton{background-color:#0072C6;color: black; border:0px groove gray;border-radius:10px;padding:6px;}"); briefnessBtn->move(400,200); QFont font("华文行楷",25); briefnessBtn->setFont(font); briefnessBtn->setText("容易模式"); connect(briefnessBtn,&QPushButton::clicked,[=]{ //关闭上个窗口 this->close(); room->setGeometry(this->geometry()); room->show(); QSound::play(":res/clicked.wav"); });以第一个按钮为例,运行后点击简单模式效果如下(这是一个新的界面):
4. 总代码
#include "gameselect.h" #include #include #include #include "gamesnake.h"# #include "gameroom.h" #include GameSelect::GameSelect(QWidget *parent) : QWidget(parent) { this->setFixedSize(1000,800); //设置窗口图标 this->setWindowIcon(QIcon(":res/ico.png")); //设置窗口标签 this->setWindowTitle("选择大厅"); //实现游戏房间和选择关卡切换 //创建另一个窗口 GameRoom *room=new GameRoom(); //创建按钮,给按钮设置格式 //容易关卡 QPushButton *briefnessBtn= new QPushButton(this); briefnessBtn->setStyleSheet("QPushButton{background-color:#0072C6;color: black; border:0px groove gray;border-radius:10px;padding:6px;}"); briefnessBtn->move(400,200); QFont font("华文行楷",25); briefnessBtn->setFont(font); briefnessBtn->setText("容易模式"); connect(briefnessBtn,&QPushButton::clicked,[=]{ //关闭上个窗口 this->close(); room->setGeometry(this->geometry()); room->show(); QSound::play(":res/clicked.wav"); }); //正常关卡 QPushButton *normalBtn= new QPushButton(this); normalBtn->setStyleSheet("QPushButton{background-color:#0072C6;color: black; border:0px groove gray;border-radius:10px;padding:6px;}"); normalBtn->move(400,280); QFont font1("华文行楷",25); normalBtn->setFont(font); normalBtn->setText("正常模式"); connect(normalBtn,&QPushButton::clicked,[=]{ //关闭上个窗口 this->close(); room->setGeometry(this->geometry()); room->show(); QSound::play(":res/clicked.wav"); }); //困难关卡 QPushButton *difficultyBtn= new QPushButton(this); difficultyBtn->setStyleSheet("QPushButton{background-color:#0072C6;color: black; border:0px groove gray;border-radius:10px;padding:6px;}"); difficultyBtn->move(400,360); QFont font2("华文行楷",25); difficultyBtn->setFont(font); difficultyBtn->setText("困难模式"); connect(difficultyBtn,&QPushButton::clicked,[=]{ //关闭上个窗口 this->close(); room->setGeometry(this->geometry()); room->show(); QSound::play(":res/clicked.wav"); }); //地狱关卡 QPushButton *bhellBtn= new QPushButton(this); bhellBtn->setStyleSheet("QPushButton{background-color:#0072C6;color: black; border:0px groove gray;border-radius:10px;padding:6px;}"); bhellBtn->move(400,440); QFont font3("华文行楷",25); bhellBtn->setFont(font); bhellBtn->setText("地狱模式"); connect(bhellBtn,&QPushButton::clicked,[=]{ //关闭上个窗口 this->close(); room->setGeometry(this->geometry()); room->show(); QSound::play(":res/clicked.wav"); }); //排行榜 QPushButton *ranpingBtn= new QPushButton(this); ranpingBtn->setStyleSheet("QPushButton{background-color:#0072C6;color: black; border:0px groove gray;border-radius:10px;padding:6px;}"); ranpingBtn->move(400,520); QFont font4("华文行楷",25); ranpingBtn->setFont(font); ranpingBtn->setText("历史战绩"); //返回按钮 QPushButton *returnBtn= new QPushButton(this); returnBtn->move(850,700); returnBtn->setIcon(QIcon(":res/up.png")); connect(returnBtn,&QPushButton::clicked,[=]{ this->close(); GameSnake *snake= new GameSnake(); snake->show(); QSound::play(":res/clicked.wav"); }); } void GameSelect::paintEvent(QPaintEvent *event) { //设置窗口大小 this->setFixedSize(1000,800); //创建绘画者 QPainter paint(this); //创建画板 QPixmap pix(":res/game_select.png"); paint.drawPixmap(0,0,this->width(),this->height(),pix); }注意:历史战绩还没有设计
四、 游戏界面实现
前两个页面已经实现完成了,最后完成游戏界面,我们就可以玩上自己写的游戏了。
游戏房间界⾯包含下⾯⼏个部分:
• 背景图的绘制。
• 蛇的绘制、蛇的移动、判断蛇是否会撞到⾃⼰ 。
• 积分的累加和绘制 在这⾥我们要考虑⼏个⽐较核⼼的问题:
- 怎么让蛇动起来?
• 我们可以⽤⼀个链表表⽰贪吃蛇,⼀个⼩⽅块表⽰蛇的⼀个节点, 我们设置蛇的默认⻓度为3。
• 向上移动的逻辑就是在蛇的上⽅加⼊⼀个⼩⽅块, 然后把最后⼀个⼩⽅块删除即可。
• 需要⽤到定时器Qtimer 每100 - 200ms 重新渲染。
- 怎么判断蛇有没有吃到⻝物?
• 判断蛇头和⻝物的坐标是否相交,Qt 有相关的接⼝调⽤。
- 怎么控制蛇的移动?
• 借助QT的实践机制实现, 重写keyPressEvent即可, 在函数中监控想要的键盘事件即可 。
• 我们通过绘制四个按钮,使⽤信号和槽的机制控制蛇的上、下、左、右移动⽅向。
1.先要对游戏界面进行渲染,用到pixmap和painter。
void GameRoom::paintEvent(QPaintEvent *event) { qDebug("开始绘画"); //创建绘画者 QPainter paint(this); //创建画板,不带有参数 QPixmap pix; //load方法,加载资源或者数据 //游戏区域 pix.load(":res/game_room.png"); paint.drawPixmap(0,0,800,800,pix); //控制区域 pix.load(":res/bg1.png"); paint.drawPixmap(800,0,201,900,pix); }2.创建一个按钮,实现页面之间的跳转
//创建一个返回按钮 QPushButton* returnButton = new QPushButton(this); //设置位置和按钮照片 returnButton->move(900,700); returnButton->setIcon(QIcon(":res/up.png")); //创建信号槽 connect(returnButton,&QPushButton::clicked,[=]{ //关闭当前页面 this->close(); GameSelect *gameRn = new GameSelect(); //显示上个页面 gameRn->show(); QSound::play(":res/clicked.wav"); });如图:
3. 对游戏房间数据结构进行封装
gameroom的头文件
#define GAMEROOM_H #include //枚举蛇的移动方向 enum class kSnakeDirect { UP = 0, DOWN, LEFT, RIGHT }; class GameRoom : public QWidget { Q_OBJECT public: explicit GameRoom(QWidget *parent = nullptr); //重写绘画事件 void paintEvent(QPaintEvent *event); private: //表示蛇身体结点的宽度 const int kSnakeNodeWidth = 20; //表示蛇身体节点的高度 const int kSnakeNodeHeight = 20; //表示蛇的速度 const int KDefountTimeout = 100; //表示贪吃蛇的链表 QList snakeList; //表示食物的结点 QRectF foodRect; //表示蛇初始方向的默认值 kSnakeDirect moveDirect = kSnakeDirect::UP; };4.蛇移动方向的实现
注意: 这⾥贪吃蛇不允许直接掉头, ⽐如当前是向上的, 不能直接修改为向下。
分别声明:
void moveUp();//蛇向上移动 void moveDown();//蛇向下移动 void moveLeft();//蛇向左移动 void moveRight();//蛇向右移动
列如蛇向上移动的定义
void GameRoom::moveUp() { QPointF leftTop; // 左上⻆坐标 QPointF rightBottom; // 右下⻆坐标 auto snakeNode = snakeList.front(); int headX = snakeNode.x(); int headY = snakeNode.y(); // 如果上⾯剩余的空间不够放⼊⼀个新的节点, 相当于到墙边了, 要处理穿墙逻辑 if(headY-kSnakeNodeWidth height() - kSnakeNodeHeight); } else { leftTop = QPointF(headX, headY - kSnakeNodeHeight); } rightBottom = leftTop + QPointF(kSnakeNodeWidth, kSnakeNodeHeight); snakeList.push_front(QRectF(leftTop, rightBottom)); }5.判断游戏是否结束
贪吃蛇游戏的结束标志是自己碰到自己,才算结束。
bool GameRoom::checkFail() { for(int i=0;i for(int j=i+1;j if(snakeList.at(i)==snakeList.at(j)) { return true;//游戏结束 } } } return false;//游戏正常运行 } pix.load(":res/rescopy.png");//加载资源 } else if(moveDirect == kSnakeDirect::DOWN) { pix.load(":res/rescopy1.png");//加载资源 } else if(moveDirect==kSnakeDirect::LEFT) { pix.load(":res/rescopy 2.png");//加载资源 } else { pix.load(":res/rescopy3.png");//加载资源 } //获取链表的头结点 auto snakeHeadNode=snakeList.front(); //画家绘画,给这个结点绘制pix里面的资源 paint.drawPixmap(snakeHeadNode.x(),snakeHeadNode.y(),snakeHeadNode.width(),snakeHeadNode.height(),pix); //绘制蛇身 pix.load(":res/rescopy 5.png"); for(int i=1;i auto node=snakeList.at(i); paint.drawPixmap(node.x()+2,node.y(),node.width()-3,node.height(),pix); } //绘制蛇尾 auto TailNode = snakeList.back(); paint.drawPixmap(TailNode.x()+2,TailNode.y(),TailNode.width()-3,TailNode.height(),pix); foodRect= QRectF(qrand()%(800/kSnakeNodeWidth)*kSnakeNodeWidth,qrand()%(800/kSnakeNodeHeight)*kSnakeNodeHeight,kSnakeNodeWidth,kSnakeNodeHeight); } //在绘图事件里,绘制食物 //绘制食物 pix.load(":res/food.bmp"); paint.drawPixmap(foodRect.x(),foodRect.y(),foodRect.width(),foodRect.height(),pix); int cnt = 1; // 判断是否贪吃蛇和⻝物是否相交 if (snakeList.front().intersects(foodRect)) { 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; default: qDebug() move(860,200); //设置按钮⽂本 startbtn->setText("开始"); stopbtn->setText("暂停"); //设置按钮样式 startbtn->setStyleSheet("QPushButton{border:0px;}"); stopbtn->setStyleSheet("QPushButton{border:0px;}"); //设置按钮字体格式 startbtn->setFont(ft); stopbtn->setFont(ft); //蛇移动 connect(startbtn,&QPushButton::clicked,this,[=](){ isGameStart = true; timer->start(kDefountTimeout); sound = new QSound(":res/Trepak.wav"); //声⾳路径 sound->play(); //播放 sound->setLoops(-1); //循环播放 }); //蛇暂停 connect(stopbtn,&QPushButton::clicked,this,[=](){ isGameStart = false; timer->stop();//停止工作 sound->stop();//停止播放 });10. 设置蛇的方向控制
//设置蛇2的移动方向 QPushButton *Up = new QPushButton(this); QPushButton *Down = new QPushButton(this); QPushButton *Left = new QPushButton(this); QPushButton *Right = new QPushButton(this); Up->move(880,400); Down->move(880,480); Left->move(840,440); Right->move(920,440); Up->setStyleSheet("QPushButton{border:0px;}"); Up->setFont(font); Up->setText("↑"); Down->setStyleSheet("QPushButton{border:0px;}"); Down->setFont(font); Down->setText("↓"); Left->setStyleSheet("QPushButton{border:0px;}"); Left->setFont(font); Left->setText("←"); Right->setStyleSheet("QPushButton{border:0px;}"); Right->setFont(font); Right->setText("→"); connect(Up,&QPushButton::clicked,this,[=](){ if(moveDirect != kSnakeDirect::DOWN) moveDirect = kSnakeDirect::UP; }); connect(Down,&QPushButton::clicked,this,[=](){ if(moveDirect != kSnakeDirect::UP) moveDirect = kSnakeDirect::DOWN; }); connect(Left,&QPushButton::clicked,this,[=](){ if(moveDirect != kSnakeDirect::RIGHT) moveDirect = kSnakeDirect::LEFT; }); connect(Right,&QPushButton::clicked,this,[=](){ if(moveDirect != kSnakeDirect::LEFT) moveDirect = kSnakeDirect::RIGHT; });我们也可以用键盘来控制蛇的方向
void GameRoom::keyPressEvent(QKeyEvent *event) { switch (event->key()) { case Qt::Key_W : case Qt::Key_Up : if(moveDirect != kSnakeDirect::DOWN) moveDirect = kSnakeDirect::UP; break; case Qt::Key_S : case Qt::Key_Down : if(moveDirect != kSnakeDirect::UP) moveDirect=kSnakeDirect::DOWN; break; case Qt::Key_D : case Qt::Key_Right : if(moveDirect != kSnakeDirect::LEFT) moveDirect=kSnakeDirect::RIGHT; break; case Qt::Key_A : case Qt::Key_Left : if(moveDirect != kSnakeDirect::RIGHT) moveDirect=kSnakeDirect::LEFT; break; //开始 case Qt::Key_F : isGameStart = true; timer->start(moveDefount); sound = new QSound(":res/Trepak.wav"); //声⾳路S径 sound->play(); //播放 sound->setLoops(-1); //循环播放 break; // //暂停 case Qt::Key_G : isGameStart = false; timer->stop();//停止工作 sound->stop();//停止播放 break; } }11.设置游戏退出按钮,并设置一个消息盒子。
//设置退出按钮,并给他设置样式 QPushButton *exitBtn=new QPushButton(this); exitBtn->setText("退出"); exitBtn->move(870,750); exitBtn->setFont(font); //设置消息盒子 QMessageBox *msg=new QMessageBox(this); QPushButton *okBtn =new QPushButton("OK"); QPushButton *cancelBtn =new QPushButton("cancel"); msg->addButton(okBtn,QMessageBox::AcceptRole); msg->addButton(cancelBtn,QMessageBox::RejectRole); msg->setWindowTitle("退出游戏"); msg->setText("确认退出游戏吗?"); connect(exitBtn,&QPushButton::clicked,[=]{ //显示消息盒子 msg->show(); QSound::play(":res/clicked.wav"); //事件轮询 msg->exec(); if(msg->clickedButton()==okBtn) { this->close();//关闭程序 } else { msg->close();//关闭信息盒子 } });12. 分数的绘制
//绘制分数 pix.load(":res/sorce_bg.png"); paint.drawPixmap(this->width()*0.85,this->height()*0.06,90,40,pix); QPen pen; pen.setColor(Qt::black); QFont font("方正舒体",22); paint.setPen(pen); paint.setFont(font); paint.drawText(this->width()*0.9,this->height()*0.1,QString("%1").arg(snakeList.size()));13.游戏失败的绘制
//绘制游戏失败效果 if(checkFail()) { pen.setColor(Qt::red); paint.setPen(pen); QFont font("方正舒体",50); paint.setFont(font); paint.drawText(this->width()*0.25,this->height()*0.5,QString("GAME OVER!")); timer->stop(); QSound::play((":res/gameover.wav")); sound->stop(); }14.实现4种不同游戏模式
先对各个模式的时间进行设置,然后设置房间的定时器时间,从而改变游戏的模式。
room->setTimer(200);//改变各个模式的速度默认值
通过默认值去改变时间,从而设置游戏模式
五、在文件中写入历史战绩
通过文件的方式用来显示历史战绩
5.1 从文件里提取分数
读⽂件:读取写⼊⽂件中蛇的⻓度
connect(ranpingBtn,&QPushButton::clicked,[=]{ //创建一个窗口控件 QWidget *wGet=new QWidget(); wGet->setWindowTitle("历史战绩"); wGet->setFixedSize(500,300); //创建一个多行输入框 QTextEdit *edit=new QTextEdit(wGet); edit->setFont(font); edit->setFixedSize(500,300); //打开文件,并且只能读,文件要自己创建 QFile file("D:/QT_creator_3_25/greedy_snake1/Snake/res/1.txt"); file.open(QIODevice::ReadOnly); //文本流对象 QTextStream in(&file); //in.表示文件,把文件里的数据按行读取,并且转化为整数 int data=in.readLine().toInt(); edit->append("得分为:"); //这里需要的是QString类型,把整形转化为字符串 edit->append(QString::number(data)); //显示窗口 wGet->show(); QSound::play(":res/clicked.wav"); file.close(); });5.2 把贪吃蛇的长度存入文件
写⽂件:往⽂件中写⼊蛇的⻓度
//把分数存入文件 int c=snakeList.size(); QFile file("D:/QT_creator_3_25/greedy_snake1/Snake/res/1.txt"); if(file.open(QIODevice::Text|QIODevice::WriteOnly)) { //输入文本和只写 QTextStream out(&file); out













