Qt实战项目——贪吃蛇

一、项目介绍

本项目是一个使用Qt框架开发的经典贪吃蛇游戏,旨在通过简单易懂的游戏机制和精美的用户界面,为玩家提供娱乐和编程学习的机会。

游戏展示

二、主要功能

2.1 游戏界面

游戏主要是由三个界面构成,分别是游戏大厅、难度选择和游戏内界面,因此需要建立三个.cpp文件,分别对应的是gamehall.cpp、gameselect.cpp和gameroom.cpp。

2.1.1 gamehall.cpp

在游戏大厅界面,需要将图片设置到背景板上,并且创建一个"开始游戏"按钮

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

在游戏选择界面中,同样是需要设置背景,创建按钮等操作。

其中点击"简单模式"、"正常模式"、"困难模式"可以直接进入游戏房间内,而查阅"历史战绩"时会弹出一个新窗口显示战绩。

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);
}

同时在这个界面中,我们需要创建一个"回退"按钮,点击可回退到游戏大厅界面

cpp 复制代码
    // 设置回退按钮
    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

在游戏房间界面中,我们可以看到许多元素,其中不仅有"开始"、"暂停"、"退出"三个按钮,还有控制小蛇移动的方向键按钮,还有计分板等等元素。

我们首先要做的是设计背景以及创建各个按钮。

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 游戏规则

蛇移动时不能碰到自己的身体,否则游戏结束。每吃掉一个食物(食物会随机刷新),身体变长,分数增加。

cpp 复制代码
    // 初始化贪吃蛇
    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);
cpp 复制代码
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)来控制蛇的移动方向,但不能直接反向移动。

cpp 复制代码
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

相关推荐
Elihuss2 小时前
ONVIF协议操作摄像头方法
开发语言·php
Swift社区5 小时前
在 Swift 中实现字符串分割问题:以字典中的单词构造句子
开发语言·ios·swift
没头脑的ht5 小时前
Swift内存访问冲突
开发语言·ios·swift
没头脑的ht5 小时前
Swift闭包的本质
开发语言·ios·swift
wjs20245 小时前
Swift 数组
开发语言
stm 学习ing6 小时前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
湫ccc7 小时前
《Python基础》之字符串格式化输出
开发语言·python
mqiqe8 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin8 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python