【QT】贪吃蛇小游戏 -- 童年回忆

成品展示

项目分析:

🐍基本元素如下

🐍小蛇的设计,初始大小蛇头占一个方块,蛇身占两个方块。

🐍关于小蛇的移动,采用蛇头前进方向增加一个方块,蛇尾减掉一个方块的实现方法。

🐍在移动过程中不能立即向前进反方向移动(如正在向左前进的小蛇,按下向右的按键应当无效果),当小蛇吃到食物后,蛇身自增,并记录得分。

🐍当小蛇碰到边界处 或 自身时游戏结束。

🐍食物随机生成。

工程创建

游戏背景图片准备

小游戏的设计采用的是"穿越方案",到达边界后会从另一边出现。并未设置墙壁碰撞检测。下图墙壁仅为图片,在代码实现过程中可根据需求添加碰撞检测。

主体框架预览

主要函数分析

按键处理

void Widget::keyPressEvent(QKeyEvent* event)
{
    switch(event->key())
    {
    case Qt::Key_Up:
        if(this->moveFlag != DIR_DOWN)
        {
            this->moveFlag = DIR_UP;
        }
        break;
    case Qt::Key_Down:
        if(this->moveFlag != DIR_UP)
        {
            this->moveFlag = DIR_DOWN;
        }
        break;
    case Qt::Key_Right:
        if(this->moveFlag != DIR_LEFT)
        {
            this->moveFlag = DIR_RIGHT;
        }
        break;
    case Qt::Key_Left:
        if(this->moveFlag != DIR_RIGHT)
        {
            this->moveFlag = DIR_LEFT;
        }
        break;
    case Qt::Key_Space:
        if(this->gameStart == false)
        {
            this->gameStart = true;
            //游戏开始,启动定时器
            this->timer->start(TIME);
        }
        else
        {
            this->gameStart = false;
            this->timer->stop();
        }
        break;
    default:
        break;
    }
}

渲染绘图

//渲染
void Widget::paintEvent(QPaintEvent* et)
{
    QPainter painter(this);
    QPen pen;
    QBrush brush;
   //背景图片
    QPixmap pix;
    pix.load("D:/snake/snake_b1.png");
    painter.drawPixmap(0,0,820,460,pix);


    //画蛇
    pen.setColor(Qt::black);
    brush.setColor(Qt::darkMagenta);
    brush.setStyle(Qt::SolidPattern);

    painter.setPen(pen);
    painter.setBrush(brush);


    for(int i = 0; i<snake.length();++i)
    {
        painter.drawRect(snake[i]);
    }
    //画 食物
    pen.setColor(Qt::red);
    brush.setColor(Qt::red);
    brush.setStyle(Qt::SolidPattern);

    painter.setPen(pen);
    painter.setBrush(brush);

    painter.drawEllipse(this->rewardNode);

    //绘制得分
    QFont font("方正舒体",17,QFont::ExtraLight,false);
    painter.setFont(font);
    painter.drawText((this->width() - 75),this->height()- 427, QString::number(this->score));

    if(checkContact())
    {
        painter.drawText((this->width() - 300)/2,(this->height()-30)/2,QString("小蛇不小心咬到了自己,游戏结束!!!"));
        this->timer->stop();
    }



    QWidget::paintEvent(et);
}

槽函数

void Widget::timeout()
{
    int count = 1;
    //蛇头是否 触碰到食物
    if(snake[0].intersects(this->rewardNode))
    {
        addNewReword();
        score++;
        count++;
    }
    while(count--)
    {
        switch(this->moveFlag)
        {
        case DIR_UP:
            //蛇上方加方格
            addTop();
            break;
        case DIR_DOWN:
            //蛇下方加方格
            addDown();
            break;
        case DIR_LEFT:
            //蛇左边加空格
            addLeft();
            break;
        case DIR_RIGHT:
            //蛇右边加空格
            addRight();
            break;
        default:
            break;
        }
    }
    //删除尾部方块
    deleteLast();
    update();
}

蛇运动

以向上运动为例:

情况1:蛇头运动越界,从另一端"穿越"回游戏界面。

情况2:正常运动

运动实现原理:

向上增加方块,代码实现:

void Widget::addTop()
{
    QPointF leftTop;//左上
    QPointF rightBotom;//右下

    //方案1:如果越界(蛇头碰到墙体) -- 游戏失败
    //方案2:穿墙(上越界,从下边界出来)
    if(snake[0].y() - NODEHEIGHT < 0)
    {
        //方案1:游戏直接结束

        //方案2:穿墙
        leftTop = QPoint(snake[0].x(),this->height() - NODEHEIGHT);
        rightBotom = QPoint(snake[0].x()+NODEHEIGHT,this->height());

    }
    else
    {
        //正常增加方块
        leftTop = QPointF(snake[0].x(),snake[0].y()-NODEHEIGHT);
        rightBotom = snake[0].topRight();
    }
    snake.insert(0,QRectF(leftTop,rightBotom));
}

整体代码

widget.h

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QList>
#include <Qtime>
QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

typedef enum DIR
{
    DIR_LEFT,
    DIR_RIGHT,
    DIR_UP,
    DIR_DOWN
}dir_t;

//超时时间 单位毫秒
#define TIME 100
#define NODEHEIGHT 20

//y轴边界
#define BORDER_X 300
//x轴边界
#define BORDER_Y 500

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
 //槽函数
protected slots:
    //定时器启动 触发该槽函数
    void timeout();
protected:
    //上 -- 蛇身增长
    void addTop();
    //下 -- 蛇身增长
    void addDown();
    //左 -- 蛇身增长
    void addRight();
    //右 -- 蛇身增长
    void addLeft();
    //删除蛇尾方块
    void deleteLast();
    //随机生成食物
    void addNewReword();
    //渲染
    void paintEvent(QPaintEvent* event);
    //蛇头碰到蛇身判断
    bool checkContact();
    //按键处理 -- 重写
    void keyPressEvent(QKeyEvent* event);
private:
    Ui::Widget *ui;
    //记录当前头部朝向
    int moveFlag = DIR_UP;
    //游戏是否开始
    bool gameStart = false;
    //记录蛇身体
    QList<QRectF> snake;
    //定时器
    QTimer* timer;
    //食物
    QRectF rewardNode;
    //积分
    int score;
};
#endif // WIDGET_H

widget.cpp

#include "widget.h"
#include "ui_widget.h"
#include <QKeyEvent>
#include <QTimer>
#include <QPainter>
#include <QPaintEvent>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //初始化定时器
    this->timer = new QTimer();
    //信号和槽
    connect(timer,SIGNAL( timeout() ),this,SLOT( timeout() ));

    //调整窗口带下
    this->resize(820,460);

    //初始化蛇身
    QPointF leftTop = QPointF(200,200);
    QPointF rightBotom = QPointF(220,220);
    snake.insert(0,QRectF(leftTop,rightBotom));

    //初始化食物
    addNewReword();
    //初始化积分
    this->score = 0;
    addTop();
    addTop();
}

Widget::~Widget()
{
    delete ui;
}


void Widget::keyPressEvent(QKeyEvent* event)
{
    switch(event->key())
    {
    case Qt::Key_Up:
        if(this->moveFlag != DIR_DOWN)
        {
            this->moveFlag = DIR_UP;
        }
        break;
    case Qt::Key_Down:
        if(this->moveFlag != DIR_UP)
        {
            this->moveFlag = DIR_DOWN;
        }
        break;
    case Qt::Key_Right:
        if(this->moveFlag != DIR_LEFT)
        {
            this->moveFlag = DIR_RIGHT;
        }
        break;
    case Qt::Key_Left:
        if(this->moveFlag != DIR_RIGHT)
        {
            this->moveFlag = DIR_LEFT;
        }
        break;
    case Qt::Key_Space:
        if(this->gameStart == false)
        {
            this->gameStart = true;
            //游戏开始,启动定时器
            this->timer->start(TIME);
        }
        else
        {
            this->gameStart = false;
            this->timer->stop();
        }
        break;
    default:
        break;
    }
}

//渲染
void Widget::paintEvent(QPaintEvent* et)
{
    QPainter painter(this);
    QPen pen;
    QBrush brush;
   //背景图片
    QPixmap pix;
    pix.load("D:/snake/snake_b1.png");
    painter.drawPixmap(0,0,820,460,pix);


    //画蛇
    pen.setColor(Qt::black);
    brush.setColor(Qt::darkMagenta);
    brush.setStyle(Qt::SolidPattern);

    painter.setPen(pen);
    painter.setBrush(brush);


    for(int i = 0; i<snake.length();++i)
    {
        painter.drawRect(snake[i]);
    }
    //画 食物
    pen.setColor(Qt::red);
    brush.setColor(Qt::red);
    brush.setStyle(Qt::SolidPattern);

    painter.setPen(pen);
    painter.setBrush(brush);

    painter.drawEllipse(this->rewardNode);

    //绘制得分
    QFont font("方正舒体",17,QFont::ExtraLight,false);
    painter.setFont(font);
    painter.drawText((this->width() - 75),this->height()- 427, QString::number(this->score));

    if(checkContact())
    {
        painter.drawText((this->width() - 300)/2,(this->height()-30)/2,QString("小蛇不小心咬到了自己,游戏结束!!!"));
        this->timer->stop();
    }



    QWidget::paintEvent(et);
}

void Widget::addTop()
{
    QPointF leftTop;//左上
    QPointF rightBotom;//右下

    //方案1:如果越界(蛇头碰到墙体) -- 游戏失败
    //方案2:穿墙(上越界,从下边界出来)
    if(snake[0].y() - NODEHEIGHT < 0)
    {
        //方案1:游戏直接结束

        //方案2:穿墙
        leftTop = QPoint(snake[0].x(),this->height() - NODEHEIGHT);
        rightBotom = QPoint(snake[0].x()+NODEHEIGHT,this->height());

    }
    else
    {
        //正常增加方块
        leftTop = QPointF(snake[0].x(),snake[0].y()-NODEHEIGHT);
        rightBotom = snake[0].topRight();
    }
    snake.insert(0,QRectF(leftTop,rightBotom));
}
//向下增加方块
void Widget::addDown()
{
    QPointF leftTop;
    QPointF rightBotom;

    if(snake[0].y() + NODEHEIGHT*2 > this->height())//越界
    {
        //修正坐标
        leftTop = QPointF(snake[0].x(),0);
        rightBotom = QPointF(snake[0].x()+NODEHEIGHT,NODEHEIGHT);
    }
    else
    {
        //
        leftTop = snake[0].bottomLeft();
        rightBotom = snake[0].bottomRight() + QPointF(0,NODEHEIGHT);
    }
    snake.insert(0,QRectF(leftTop,rightBotom));
}
void Widget::addLeft()
{
    QPointF leftTop;
    QPointF rightBotom;

    if(snake[0].x() - NODEHEIGHT < 0)//越界
    {
        //修正坐标
        leftTop = QPointF(this->width()-NODEHEIGHT,snake[0].y());
    }
    else
    {
        leftTop = snake[0].topLeft() - QPointF(NODEHEIGHT,0);
    }
    rightBotom = leftTop + QPointF(NODEHEIGHT,NODEHEIGHT);

    snake.insert(0,QRectF(leftTop,rightBotom));
}
void Widget::addRight()
{
    QPointF leftTop;
    QPointF rightBotom;

    if(snake[0].x() + NODEHEIGHT*2 > this->width())//越界
    {
        //修正坐标
        leftTop = QPointF(0,snake[0].y());
    }
    else
    {
        //
        leftTop = snake[0].topRight();
    }
    rightBotom = leftTop + QPointF(NODEHEIGHT,NODEHEIGHT);
    snake.insert(0,QRectF(leftTop,rightBotom));
}

void Widget::deleteLast()
{
    snake.removeLast();
}

void Widget::timeout()
{
    int count = 1;
    //蛇头是否 触碰到食物
    if(snake[0].intersects(this->rewardNode))
    {
        addNewReword();
        score++;
        count++;
    }
    while(count--)
    {
        switch(this->moveFlag)
        {
        case DIR_UP:
            //蛇上方加方格
            addTop();
            break;
        case DIR_DOWN:
            //蛇下方加方格
            addDown();
            break;
        case DIR_LEFT:
            //蛇左边加空格
            addLeft();
            break;
        case DIR_RIGHT:
            //蛇右边加空格
            addRight();
            break;
        default:
            break;
        }
    }
    //删除尾部方块
    deleteLast();
    update();
}
//随机生成食物
void Widget::addNewReword()
{
    rewardNode = QRectF(
        qrand()%(this->width()/20) * 20,
        qrand()% (this->height()/20) * 20,
        NODEHEIGHT,
        NODEHEIGHT
        );
}

bool Widget::checkContact()
{
    for(int i = 0;i<snake.length();++i)
    {
        for(int j = i+1;j<snake.length();++j)
        {
            if(snake[i] == snake[j])
            {
                return true;
            }
        }
    }
    return false;
}
相关推荐
源码哥_博纳软云22 分钟前
JAVA同城服务场馆门店预约系统支持H5小程序APP源码
java·开发语言·微信小程序·小程序·微信公众平台
学会沉淀。29 分钟前
Docker学习
java·开发语言·学习
西猫雷婶1 小时前
python学opencv|读取图像(二十一)使用cv2.circle()绘制圆形进阶
开发语言·python·opencv
kiiila1 小时前
【Qt】对象树(生命周期管理)和字符集(cout打印乱码问题)
开发语言·qt
小_太_阳1 小时前
Scala_【2】变量和数据类型
开发语言·后端·scala·intellij-idea
直裾1 小时前
scala借阅图书保存记录(三)
开发语言·后端·scala
唐 城2 小时前
curl 放弃对 Hyper Rust HTTP 后端的支持
开发语言·http·rust
码银4 小时前
【python】银行客户流失预测预处理部分,独热编码·标签编码·数据离散化处理·数据筛选·数据分割
开发语言·python
从善若水4 小时前
【2024】Merry Christmas!一起用Rust绘制一颗圣诞树吧
开发语言·后端·rust
黄金右肾4 小时前
Qt之数据库使用(十四)
sql·qt·sqlite·database