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

相关推荐
爱吃生蚝的于勒2 分钟前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
binishuaio12 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE13 分钟前
【Java SE】StringBuffer
java·开发语言
就是有点傻18 分钟前
WPF中的依赖属性
开发语言·wpf
洋24026 分钟前
C语言常用标准库函数
c语言·开发语言
进击的六角龙28 分钟前
Python中处理Excel的基本概念(如工作簿、工作表等)
开发语言·python·excel
wrx繁星点点29 分钟前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
NoneCoder1 小时前
Java企业级开发系列(1)
java·开发语言·spring·团队开发·开发
苏三有春1 小时前
PyQt5实战——UTF-8编码器功能的实现(六)
开发语言·qt
Aniay_ivy1 小时前
深入探索 Java 8 Stream 流:高效操作与应用场景
java·开发语言·python