当前位置:   article > 正文

QT开发实例(一):简单实现一个贪吃蛇游戏_qt项目开发实例

qt项目开发实例

QT开发实例

提示:本专栏所用版本仅供参考,其他版本也可

版本
QT5.14:下载
源码下载点击下载
游戏包下载点击下载
QQ群点击加群:928357277

一:学前准备

本教程将讲述如下内容
1:如何在画布上绘制贪吃蛇的身体
2:如何生成豆子
3:如何让贪吃蛇被键盘控制
4:如何让贪吃蛇在固定画布内移动
5:键盘事件,定时器事件,画布事件的学习
关于本教程所需的源码及其EXE文件将打包上传,欢迎大家下载,也可以加群免费下载。

二:实现

1、创建画布

我们首先在类的protected成员中创建一个QT类中的画布事件,在事件函数内实现相关功能

//函数在类内申明
void paintEvent(QPaintEvent *event);//定义画笔事件
{
 	QPainter painter(this);//创建一张画布
    QPen pen;//创建一只画笔对pen
    painter.setPen(pen);//将画笔设置到画布中去
    
    QBrush brs(Qt::black);//创建并设置笔刷为黑色
    
    QFont font("黑体",12,QFont::ExtraLight,false);//说明栏设置
    painter.setFont(font);//画布插入字体
    
    painter.drawText(20,40,QString("控制:")+QString("移动:上下左右——出豆子:空格——退出:ESC"));
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

2、创建一条4格原始小蛇

定义一个长10宽10,且初始长度为4(也就是4个10*1长度的小蛇),他的每个身体方块的坐标存入QLIST容器

#define itsBoy  10//定义蛇的身体大小为
#define mapMin 40 //地图X坐标的最远距离
#define mapMax 490 //地图Y坐标的最远距离


private:
	int snakeInit = 4;//定义初始化长度
	QList<int>snake_x;//用LIST保存蛇的身体
	QList<int>snake_y;
	int snake_boy=itsBoy; //蛇的身体大小

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在画布中画出蛇的第一个身体

void paintEvent(QPaintEvent *event);//定义画笔事件
{
	QPainter painter(this);//定义一张画布
    QPen pen;//定义一张画笔
    painter.setPen(pen);//将画笔设置到画布中去
    pen.setWidth(snake_boy);//笔刷宽度设置为蛇的宽度

    QBrush brs(Qt::black);//设置笔刷为黑色

    QFont font("黑体",12,QFont::ExtraLight,false);//计数栏设置
    painter.setFont(font);//画布插入字体


    painter.drawText(20,40,QString("控制:")+QString("移动:上下左右——出豆子:空格——退出:ESC"));
    brs.setColor(Qt::darkGray);//改变笔刷颜色
    painter.setBrush(brs);
   //画出矩形,蛇
    for(int i=0;i<snake_x.length();i++)
    {
        painter.setBrush(brs);//设置画笔笔刷
        painter.drawEllipse(snake_x.at(i),snake_y.at(i),snake_boy,snake_boy);//画出蛇的身体
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

3、创建个豆子

创建一个大小等于蛇身体方块大小的方块作为豆子

  private:
    int snakeBean=itsBoy;//豆子的身体
    int beanx=-100;//豆子的坐标
    int beany=-100;
  • 1
  • 2
  • 3
  • 4

在画布中绘制豆子

void mainUi::paintEvent(QPaintEvent *event) //画家函数
{
    QPainter painter(this);//定义一张画布
    QPen pen;//定义一张画笔
    painter.setPen(pen);//将画笔设置到画布中去
    pen.setWidth(snake_boy);//笔刷宽度设置为蛇的宽度

    QBrush brs(Qt::black);//设置笔刷为黑色

    QFont font("黑体",12,QFont::ExtraLight,false);//计数栏设置
    painter.setFont(font);//画布插入字体

    painter.drawText(20,40,QString("控制:")+QString("移动:上下左右——出豆子:空格——退出:ESC"));
    brs.setColor(Qt::darkGray);//改变笔刷颜色
    painter.setBrush(brs);
   //画出矩形,蛇
    for(int i=0;i<snake_x.length();i++)
    {
        painter.setBrush(brs);//设置画笔笔刷
        painter.drawEllipse(snake_x.at(i),snake_y.at(i),snake_boy,snake_boy);//画出蛇的身体
    }
    //画豆子
    if(beanx>0&&beany>0)
    {
        brs.setColor(Qt::darkMagenta);//设置豆子颜色
        brs.setStyle(Qt::ConicalGradientPattern);//设置笔刷样式
        painter.setBrush(brs);
        painter.drawRect(beanx,beany,snake_boy,snake_boy);//画出豆子的身体
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

4、定义键盘事件

需要注意的是,当小蛇头部朝下的时候,按照游戏的逻辑,不能直接控制小蛇朝上移动,所以,在写键盘事件的时候,我们应该判断小蛇的朝向问题

void mainUi::keyPressEvent(QKeyEvent *event) //定义键盘事件
{
    switch(event->key())
    {
    case Qt::Key_Up://枚举值为键盘上的向上方向键
        if(moveFlag!= 'D')//如果小蛇头没有朝下,则可以直接朝上移动,否则,小蛇不产生任何变化
        {
            moveFlag ='U';
        }
        break;
    case Qt::Key_Down:
        if(moveFlag !='U')
        {
            moveFlag ='D';
        }
        break;
    case Qt::Key_Right:
        if(moveFlag != 'L')
        {
            moveFlag ='R';
        }
        break;
    case Qt::Key_Left:
        if(moveFlag != 'R')
        {
            moveFlag='L';
        }
        break;
    case Qt::Key_Space://按下空格键
        getaBean();//生成豆子,大家也可以不写按下空格键,直接把该函数写在吃豆子的函数内,这样写是为了方便演示
        break;
    case Qt::Key_Escape://按下退出键,退出游戏
        this->close();
    }
    update();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36

5、控制小蛇移动

前面,我们已经在画布上绘制了一条小蛇,并且在键盘事件中写入了小蛇移动的条件。那么小蛇具体应该怎么移动呢?
写之前我们要明白如下原理
1、小蛇的移动首先应该判断他当前移动的方向,如左边,右边,我们用一个变量来保存这个这个方向。
2、小蛇在移动期间,除了头部,也就是第一个坐标需要判断下一个要移动的坐标,其他蛇身只需要将坐标移信息改变为前一个身体的信息即可。即第三个身体在向前移动后,他的坐标信息将变成第二个身体的坐标信息,不需要重新计算。
3、小蛇要在没有按下按键的清空下移动,就需要有一个一直更新画布的函数,在QT中,定时器可以完成这个动作。
5,、小蛇移动过过程中,判断窗口边界和吃豆子,只需要用头部所处的位置判断即可,因为后续所有身体的移动,都要经过头部。

//除了第一个,之后每次的坐标都和前一次坐标相等
void mainUi::moveUp()//要注意移动的时候的坐标
{
    int snakexbrfore[2]={0},snakeybrfore[2]={0},signale=0;
    if((snake_y[0]>snake_y[1]&&snake_x[0]==snake_x[1])||snake_y[0]<=mapMin){signale=1;}//判断边界,并且判断第二个身体在第一个身体的哪个位置
    for(int i=0;i<snake_y.length()&&signale==0;i++)//如果没有就吃豆子
    {
        if(i==0&&snake_y[i]<=snake_y[i+1]){
            snakexbrfore[0]=snake_x[i];snakeybrfore[0]=snake_y[i];
            snake_y[i]-=snake_boy;
            if(snake_x[i]==beanx&&snake_y[i]==beany){
                snake_x.append(beanx);snake_y.append(beany);
                beanx=-mapMax;beany=-mapMax;
            }
            continue;
        }
        movedll(snakexbrfore,snakeybrfore,i);//除头部以外的身体移动函数
    }
}
//向下移动
void mainUi::moveDown()
{
    int snakexbrfore[2]={0},snakeybrfore[2]={0},signale=0;
    if((snake_y[0]<snake_y[1]&&snake_x[0]==snake_x[1])||snake_y[0]>=mapMax){signale=1;}//判断边界,并且判断第二个身体在第一个身体的哪个位置
    for(int i=0;i<snake_y.length()&&signale==0;i++)
    {
        if(i==0){
            snakexbrfore[0]=snake_x[i];snakeybrfore[0]=snake_y[i];
            snake_y[i]+=snake_boy;
            if(snake_x[i]==beanx&&snake_y[i]==beany){
                snake_x.append(beanx);snake_y.append(beany);
                beanx=-mapMax;beany=-mapMax;
            }
            continue;
        }
           movedll(snakexbrfore,snakeybrfore,i);//除头部以外的身体移动函数
    }
}
/*
 遍历整个列表:同X坐标判断,每次只移动一次
*/
void mainUi::moveLeft()//向左移动
{
    int snakexbrfore[2]={0},snakeybrfore[2]={0},signale=0;
    if((snake_x[0]>snake_x[1]&&snake_y[0]==snake_y[1])||snake_x[0]<=mapMin){signale=1;}
    for(int i=0;i<snake_y.length()&&signale==0;i++)
    {
        if(i==0){
            snakexbrfore[0]=snake_x[i];snakeybrfore[0]=snake_y[i];
            snake_x[i]-=snake_boy;
            if(snake_x[i]==beanx&&snake_y[i]==beany){
                snake_x.append(beanx);snake_y.append(beany);
                beanx=-mapMax;beany=-mapMax;
            }
            continue;
        }
        movedll(snakexbrfore,snakeybrfore,i);
    }
}
void mainUi::moveRight()//向右移动
{
    int snakexbrfore[2]={0},snakeybrfore[2]={0},signale=0;
    if((snake_x[0]<snake_x[1]&&snake_y[0]==snake_y[1])||snake_x[0]>=mapMax){signale=1;}
    for(int i=0;i<snake_y.length()&&signale==0;i++)
    {
        if(i==0){
            snakexbrfore[0]=snake_x[i];snakeybrfore[0]=snake_y[i];
            snake_x[i]+=snake_boy;
            if(snake_x[i]==beanx&&snake_y[i]==beany){
                snake_x.append(beanx);snake_y.append(beany);
                beanx=-mapMax;beany=-mapMax;
            }
            continue;
        }
        movedll(snakexbrfore,snakeybrfore,i);
    }
}

void mainUi::movedll(int *x,int *y,int i)//公共函数部分//除头部以外的身体移动
{
    *(x+1)=snake_x[i];*(y+1)=snake_y[i];
    snake_x[i]=*x;snake_y[i]=*y;
    *x=*(x+1);*y=*(y+1);
}

//定时器中断执行函数
void mainUi::timefoult()
{
    current_time++;
    if(moveFlag == 'D')
    {
        moveDown();
    }
    if(moveFlag == 'U')
    {
        moveUp();
    }
    if(moveFlag == 'L')
    {
        moveLeft();
    }
    if(moveFlag == 'R')
    {
        moveRight();
    }
    update();//更新画布
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108

类中的相关构造和定义

#define mapMin 40 //地图X坐标的最远距离
#define mapMax 490 //地图Y坐标的最远距离

private:
	char moveFlag ='u'; //定义初始化方向
	QTimer *timer = nullptr;//创建定时器对象
protected:
    void keyPressEvent(QKeyEvent *event);//定义键盘事件
    void paintEvent(QPaintEvent *event);//定义画笔事件
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

6、生成豆子

用C++提供的随机数生成器Srand()生成豆子的坐标

void mainUi::getaBean()//生成一个豆子,也就是随机生成一个整数坐标(位于500之内)
{
  //用四舍五入法生成坐标
    int x=40+(rand()%mapMax-40),y=rand()%mapMax;//X,Y的范围为40 ~ mapMax之间
    if((x%10)!=0){x+=(10-(x%10));}
    if((y%10)!=0){y+=(10-(y%10));}
    beanx=x,beany=y;//坐标保存至类中的beanx,beany变量中
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

7、类的构造函数.cpp


mainUi::mainUi(QWidget *parent) :
    QWidget(parent)
{
   // ui->setupUi(this);
    this->setWindowTitle("行走的小蜜蜂");
    this->setWindowIcon(QIcon(":/photo/green.jpg"));//加入图标
    resize(mapMax+10,mapMax+10);//窗口尺寸 X ,Y
    setStyleSheet("QWidget{background:white}"); //设置窗口颜色
    setWindowFlags(Qt::FramelessWindowHint);//去掉标题栏
    this->setAttribute(Qt::WA_TranslucentBackground);//设置背景透明
    srand((unsigned)time(NULL));//把时间作为随机数种子
    timer = new QTimer;//创建一个定时器对象
    timer->start(100);//开启定时器100ms
    connect(timer,&QTimer::timeout,this,&mainUi::timefoult);//定时器信号

    snake_x<<100<<100<<100<<100;//定义初始化坐标位置
    snake_y<<100<<110<<120<<130;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

8、类的定义文件.h

//头文件中包含了其他项目的头文件,大家根据需要删除即可
#include "QKeyEvent"
#include <QWidget>
#include <QRectF>
#include <QPainter>
#include <QPen>
#include <QBrush>
#include <QDebug>
#include <QTimer>
#include <QDebug>
#include <QPaintEvent>
#include <QTimerEvent>
#include <QIcon>
#include <QPushButton>
#include <QVBoxLayout>
#include <QHBoxLayout>
#include <QPalette>


#define itsBoy  10
#define mapMin 40
#define mapMax 490

namespace Ui {
class mainUi;
}


class mainUi : public QWidget
{
    Q_OBJECT

public:
    explicit mainUi(QWidget *parent = 0);
    ~mainUi();

private:
/********************************
移动
********************************/
    void moveLeft();
    void moveRight();
    void moveUp();
    void moveDown();
    void movedll(int *x,int *y,int i);//公共函数部分用一个函数代替
/********************************
豆子
********************************/
    void getaBean();
    void timefoult();
private:
    Ui::mainUi *ui;
    char moveFlag ='u'; //定义初始化方向
    int snakeInit = 4;//定义初始化长度
    QTimer *timer = nullptr;
    QList<int>snake_x;//用LIST保存蛇的身体
    QList<int>snake_y;
    int snake_boy=itsBoy;
    int current_time=0;
    int snakeBean=itsBoy;
    int beanx=-100;//豆子的坐标
    int beany=-100;

protected:
    void keyPressEvent(QKeyEvent *event);//定义键盘事件
    void paintEvent(QPaintEvent *event);//定义画笔事件
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

运行结果:(屏幕中间区域为游戏运行区域)
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Monodyee/article/detail/81657
推荐阅读
相关标签
  

闽ICP备14008679号