【Qt项目专栏】贪吃蛇小游戏1.0

发布于:2024-06-17 ⋅ 阅读:(17) ⋅ 点赞:(0)

  • 博客主页:Duck Bro 博客主页
  • 系列专栏:Qt 专栏
  • 关注博主,后期持续更新系列文章
  • 如果有错误感谢请大家批评指出,及时修改
  • 感谢大家点赞👍收藏⭐评论✍

贪吃蛇小游戏1.0

项目编号:01


一、项目介绍及演示

1. 项目介绍

贪吃蛇游戏规则:简单来说,贪吃蛇游戏规则是玩家通过四个方向键来控制蛇的移动控制其在地图上吃豆子。蛇运动过程中撞到墙壁或蛇身,则立即结束游戏。

当前是贪吃蛇1.0版本,后续会对贪吃蛇游戏进行美化及增加更多功能,尽情期待。
在贪吃蛇2.0中会对项目进行图片的美化,及键盘快捷键的添加。

2. 项目演示

动图演示【GIF动图演示】
在这里插入图片描述

3. 窗口介绍

3.1 游戏大厅窗口

在首页中有历史战绩、开始游戏两个按钮。历史战绩用于展示以前游玩的历史记录;开始游戏用于跳转到游戏关卡选择难度界面。
在这里插入图片描述

3.2 游戏关卡选择窗口

在这里插入图片描述

3.3 游戏房间窗口

在这里插入图片描述


二、创建项目及资源配置

1. 创建项目(QWidget)

在桌面上创建一个名为Snake的文件夹,用于存放项目代码

在这里插入图片描述
打开Qt,点击新建项目
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2. 资源配置(图片声音素材)

在上述创建完成后,新建一个qrc文件,步骤如下
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
完成上述步骤,将素材包复制到项目中
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


三、项目实现

1. 游戏大厅

1.1 重写绘图事件

在gamehall.h文件中,进行重写绘图事件函数的声明

在这里插入图片描述

鼠标当到painEvent函数上点击Alt+Enter(回车),为painEvent函数添加定义

在这里插入图片描述
在上述过程后,开始编辑painEvent函数。
第一步,先实例化画家,使用QPainter painter(this)实例化一个画家,this是让画家在当前窗口(gamehall)进行绘制。注意在使用QPainter需要包含头文件#include<QPainter>
第二步,再实例化绘图设备,将图片加载到绘图设备中。注意在使用QPixmap需要包含头文件#include<QPixmap>
第三步,再进行绘画,使用drawPixmap()方法,参数1和参数2,表示绘制开始的坐标;参数3,表示绘制的宽度;参数4,表示绘制的高度;参数5,表示绘制的图片。

void GameHall::paintEvent(QPaintEvent *event)
{
    //实例化画家,this:让画家在当前窗口进行绘制
    QPainter painter(this);
    //实例化绘图设备
    QPixmap pix(":/res/game_hall.png");
    //绘画 0,0从界面左上角开始绘画根据窗口的宽和高进行绘制
    painter.drawPixmap(0,0,this->width(),this->height(),pix);
}
1.2 设置窗口大小、图标、标题

第一步,先修改窗口的大小通过setFixedSize()方法将窗口的宽度设置为1000,高度设置为800。
第二步,设置窗口的图标先通过QIcon()将图片加载到项目中,再通过setWindowIcon()方法设置到窗口中。
第三步,设置窗口的标题通过setWindowTitle()方法将文字设置到窗口标题中。

GameHall::GameHall(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::GameHall)
{
    ui->setupUi(this);
    //设置窗口大小
    this->setFixedSize(1000,800);
    //设置窗口的图标
    this->setWindowIcon(QIcon(":/res/ico.png"));
    //设置窗口标题
    this->setWindowTitle("游戏大厅");
}
1.3 设置开始按钮样式

第一步,通过QPushButton创建一个游戏开始按钮,按钮文本设置为"开始游戏",this在父窗口中显示。
第二步,通过move移动按钮再窗口中的位置,消除按钮的边框。
第三步,修改按钮中的文本样式,需要先创建一个字体样式(字体为黑体,大小为20像素),最后通过setFont方法将字体样式加载到按钮中。

GameHall::GameHall(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::GameHall)
{
    ui->setupUi(this);
    //设置窗口大小
    this->setFixedSize(1000,800);
    //设置窗口的图标
    this->setWindowIcon(QIcon(":/res/ico.png"));
    //设置窗口标题
    this->setWindowTitle("游戏大厅");


    //添加开始游戏按钮
    QPushButton * startButton = new QPushButton("开始游戏",this);
    //设置按钮的位置
    startButton->move(450,600);
    //消除按钮边框
    startButton->setStyleSheet("QPushButton{border:0px}");
    //创建字体样式
    QFont font("黑体", 20);
    //将字体样式加入到按钮文本中
    startButton->setFont(font);

}

点击运行,游戏大厅窗口界面就完成了,如下图
在这里插入图片描述

2. 游戏关卡选择

2.1 创建GameSelect类

先创建一个新的类,名为为GameSelect,基类选择QWidget
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.2 连接按钮和窗口跳转

在gamehall.cpp中进行编辑按钮的信号和槽,实现点击按钮跳转至关卡选择窗口
第一步,先创建出一个GameSelect窗口,在创建时要包含头文件#include "gameselect.h"
第二步,进行连接使用connnect()方法进行信号和槽函数之间的连接,使用Lambda表达式进行槽函数的编写。
第三步,编辑Lambda表达式,使用close()方法关闭当前窗口。
第四步,将关卡选择窗口设置为同游戏大厅窗口大小相同。
第五步,打开游戏关卡选择窗口。
第六步,给按钮添加点击音效,首先在.pro文件中添加multimedia,然后使用play()进行添加音效。
在这里插入图片描述

#include "gamehall.h"
#include "ui_gamehall.h"
#include "gameselect.h"
#include <QPainter>
#include <QPixmap>
#include <QIcon>
#include <QPushButton>
#include <QFont>
#include <QSound>
GameHall::GameHall(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::GameHall)
{
    ui->setupUi(this);
    //设置窗口大小
    this->setFixedSize(1000,800);
    //设置窗口的图标
    this->setWindowIcon(QIcon(":/res/ico.png"));
    //设置窗口标题
    this->setWindowTitle("游戏大厅");


    //添加开始游戏按钮
    QPushButton * startButton = new QPushButton("开始游戏",this);
    //设置按钮的位置
    startButton->move(450,600);
    //消除按钮边框
    startButton->setStyleSheet("QPushButton{border:0px}");
    //创建字体样式
    QFont font("黑体", 20);
    //将字体样式加入到按钮文本中
    startButton->setFont(font);
    //新建GameSelect窗口
    GameSelect * gameSelect = new GameSelect;

    //Lambda表达式
    connect(startButton,&QPushButton::clicked,[=](){
        //先关闭当前窗口
        this->close();
        //设置gameSelect窗口大小跟gamehall窗口大小一样
        gameSelect->setGeometry(this->geometry());
        //打开gameselect窗口
        gameSelect->show();
        //添加按钮点击的音效
        QSound::play(":/res/clicked.wav");

    });

}

编写gameselect.h文件,同游戏大厅步骤,这里不重复进行介绍,,详细看下下方代码注释。

在这里插入图片描述

#include "gameselect.h"
#include "gamehall.h"
#include <QPainter>
#include <QPixmap>
#include <QPushButton>
#include <QIcon>
#include <QSound>
GameSelect::GameSelect(QWidget *parent) : QWidget(parent)
{
    //设置窗口大小
    this->setFixedSize(1000,800);
    //设置窗口的图标
    this->setWindowIcon(QIcon(":/res/ico.png"));
    //设置窗口标题
    this->setWindowTitle("关卡选择");


    //创建返回按钮
    QPushButton * returnbutton = new QPushButton(this);
    returnbutton->resize(50,50);
    returnbutton->setIcon(QIcon(":/res/back.png"));
    returnbutton->setIconSize(returnbutton->size());
    returnbutton->move(900,700);

    //建立信号和槽函数的联系
    connect(returnbutton,&QPushButton::clicked,[=](){
        //创建游戏大厅窗口
        GameHall * gamehall = new GameHall;
        //关闭当前窗口
        this->close();
        //展开游戏大厅窗口
        gamehall->show();
        //添加音效
        QSound::play(":/res/clicked.wav");

    });

    //创建文本样式
    QFont font("黑体",30);

    //创建简单难度按钮
    QPushButton * simplebutton = new QPushButton("简单模式",this);
    //将样式设置到按钮中
    simplebutton->setFont(font);
    //设置按钮在窗口中的位置
    simplebutton->move(400,170);
    //进行信号与槽的连接
    
    //创建普通难度按钮
    QPushButton * commonbutton = new QPushButton("普通模式",this);
    //将样式设置到按钮中
    commonbutton->setFont(font);
    //设置按钮在窗口中的位置
    commonbutton->move(400,270);

    //创建困难难度按钮
    QPushButton * difficultybutton = new QPushButton("困难模式",this);
    //将样式设置到按钮中
    difficultybutton->setFont(font);
    //设置按钮在窗口中的位置
    difficultybutton->move(400,370);

    //创建历史记录按钮
    QPushButton * hisbutton = new QPushButton("历史记录",this);
    //将样式设置到按钮中
    hisbutton->setFont(font);
    //设置按钮在窗口中的位置
    hisbutton->move(400,470);

}

void GameSelect::paintEvent(QPaintEvent *event)
{
    //实例化画家,this:让画家在当前窗口进行绘制
    QPainter painter(this);
    //实例化绘图设备
    QPixmap pix(":/res/game_select.png");
    //绘画
    painter.drawPixmap(0,0,this->width(),this->height(),pix);
}

3. 游戏房间

3.1 创建GameRoom类

根据上面步骤后,新建GameRoom类
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

3.2 对游戏房间数据结构进行封装
#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){moveTimeout =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 moveTimeout =  KDefaultTimeout;

};

#endif // GAMEROOM_H


3.3 初始化游戏房间界面
//设置窗口大小
    this->setFixedSize(1000,800);
    //设置窗口的图标
    this->setWindowIcon(QIcon(":/res/ico.png"));
    //设置窗口标题
    this->setWindowTitle("游戏房间");
3.4 绘制界面窗口
void GameRoom::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);

    QPixmap pix;

    pix.load(":/res/game_room.png");

    painter.drawPixmap(0,0,800,800,pix);

    pix.load(":/res/bg1.png");

    painter.drawPixmap(800,0,200,850,pix);
}
3.5 蛇的移动
//创建上下左右按钮
    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->setFont(font);
    down->setFont(font);
    left->setFont(font);
    right->setFont(font);

    up->setStyleSheet("QPushButton{border:0px}");
    down->setStyleSheet("QPushButton{border:0px}");
    left->setStyleSheet("QPushButton{border:0px}");
    right->setStyleSheet("QPushButton{border:0px}");

    //按钮控制移动方向,不能直接掉头
    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;
    });

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(800-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>800-2*KSnakeNodeWidth)
    {
        leftTop=QPointF(0,headY);
    }else{
        leftTop=snakeNode.topRight();
    }
    rightBottom = leftTop + QPointF(KSnakeNodeWidth,KSnakeNodeHeight);
    snakeList.push_front(QRectF(leftTop,rightBottom));
}
3.6 绘制蛇头身体尾巴
//绘制蛇
    //蛇头
    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{
        pix.load(":/res/right.png");
    }
    auto snakeHead = snakeList.front();
    painter.drawPixmap(snakeHead.x(),snakeHead.y(),snakeHead.width(),snakeHead.height(),pix);

    //蛇身
    pix.load(":/res/Bd.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 绘制食物与分数
//绘制食物节点
    //先进行加载食物
    pix.load(":/res/food.bmp");
    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.03,100,50,pix);
    QPen pen;
    pen.setColor(Qt::black);
    painter.setPen(pen);
    QFont font("黑体",22);
    painter.setFont(font);
    painter.drawText(this->width()*0.9,this->height()*0.08,QString("%1").arg(snakeList.size()));
    //往文件中写分数
    int c = snakeList.size();
    QFile file("C:/Users/26256/Desktop/his.txt");
    if(file.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        QTextStream out(&file);
        int num = c;
        out << num;
        file.close();
    }

3.7 判定游戏结束
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.8 随机生成食物节点
void GameRoom::createNewFood()
{
    foodRect=QRectF(qrand() %(800/KSnakeNodeWidth)*KSnakeNodeWidth,
                    qrand() %(this->height()/KSnakeNodeHeight)*KSnakeNodeHeight,
                    KSnakeNodeWidth,
                    KSnakeNodeHeight
                    );

}
3.9 创建游戏开始和暂停按钮
//开始游戏   结束游戏

    QPushButton * startButton = new QPushButton("开始",this);
    QPushButton *failButton = new QPushButton("暂停",this);

    startButton->move(860,100);
    failButton->move(860,150);

    startButton->setFont(font2);
    failButton->setFont(font2);

    connect(startButton,&QPushButton::clicked,[=](){
        isGameStart=true;
        timer->start(moveTimeout);
        sound = new QSound(":res/Trepak.wav");
        sound->play();
        sound->setLoops(-1);
    });

    connect(failButton,&QPushButton::clicked,[=](){
        isGameStart=false;
        timer->stop();
        sound->stop();
3.10 实现定时器的超时槽函数
  timer = new QTimer(this);
    connect(timer,&QTimer::timeout,[=]()
    {
        int cont =1;
        if(snakeList.front().intersects(foodRect))
        {
            createNewFood();
            cont++;
            QSound::play(":/res/eatfood.wav");

        }

        while(cont--)
        {
            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();
    });
3.11 设置游戏退出按钮

    //退出游戏按钮

    QPushButton *backButton = new QPushButton("退出",this);
    backButton->move(860,700);
    backButton->setFont(font2);
    QMessageBox * messbox = new QMessageBox(this);
     QPushButton * ok = new QPushButton("ok");
    QPushButton * no = new QPushButton("no");



    messbox->addButton(ok,QMessageBox::AcceptRole);
    messbox->addButton(no,QMessageBox::ResetRole);

    messbox->setText("确定要退出游戏吗?");

    connect(backButton,&QPushButton::clicked,[=](){
        messbox->show();
        messbox->exec();
        QSound::play("clicked.wav");

        GameSelect* select = new GameSelect;
        if(messbox->clickedButton()==ok)
        {
            this->close();
            select->show();
       }else{
            messbox->close();
        }

    });
3.12 获取历史战绩
//创建历史记录按钮
    QPushButton * hisbutton = new QPushButton("历史记录",this);
    //将样式设置到按钮中
    hisbutton->setFont(font);
    //设置按钮在窗口中的位置
    hisbutton->move(400,470);
    connect(hisbutton,&QPushButton::clicked,[=](){
        QWidget * widget = new QWidget();
        widget->setFixedSize(200,80);
        widget->setWindowTitle("历史战绩");
        QTextEdit * edit = new QTextEdit(widget);
        edit->setFixedSize(200,80);
        edit->setFont(font);
        QFile file("C:/Users/26256/Desktop/his.txt");
        file.open(QIODevice::ReadOnly);

        QTextStream in(&file);
        int data = in.readLine().toInt();
        edit->append("得分为:");
        edit->append(QString::number(data));

        widget->show();

    });

四、项目源码获取

1. 网盘链接

百度网盘:https://pan.baidu.com/s/1Vm1aJ6AtyzfPdYSCIC0ygg?pwd=duck
提取码:duck

2. Gitee链接

Gitee链接:Qt贪吃蛇小游戏项目代码


在这里插入图片描述