QT做的小游戏,技术成长ing(莫名押韵_dog)

做这个项目记录的一些笔记。
说明:翻金币项目是一款经典的益智类游戏,我们需要将金币都翻成同色,才视为胜利。首先,开始、选关、游戏、胜利界面如下:在这里插入图片描述
游戏的代码和软件资源请点击自取[资源在此](提取码:7758,制作不易,希望点个赞,非常感谢)

1、创建项目

  • 创建QT widgets Application,项目名任意,选择基类QMainWindow,类名MainScene,代表主场景。(注意项目所在路径千万不要出现中文,否则可能会出现点击运行,没有窗口弹出的情况
  • 选择MinGW作为编译器

2、导入资源实现退出

(1)导入资源

  • 将资源文件复制到当前项目下
  • 右击项目名—添加新文件—QT—QT Resource File—文件名为res
  • 添加前缀(/res)—添加文件(把所有文件都添加进去)
  • 点击小锤子按钮,将资源文件导入

(2)实现退出

  • 打开ui界面,删除工具栏和状态栏
  • 编辑界面左上角的菜单名称:开始—退出
  • 在mainscene.cpp文件中输入10-13行所示的代码:
#include "mainscene.h"
#include "ui_mainscene.h"

MainScene::MainScene(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainScene)
{
    
    
    ui->setupUi(this);

    //点击退出菜单项 实现退出
    connect(ui->actionQuit,&QAction::triggered,[=](){
    
    
        this->close();
    });
    //设置固定尺寸
    this->setFixedSize(320,588);
}

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


3、主场景的搭建

  • 1设置固定尺寸
  • 2设置标题
  • 3设置图标
  • 4添加背景

前3步的代码如下:

    //设置固定尺寸
    this->setFixedSize(320,588);
    //设置标题
    this->setWindowTitle("翻金币主场景");
    //设置图标
    this->setWindowIcon(QIcon(":/res/Coin0001.png"));

添加背景需要重写绘图事件,其主要代码和效果如下:

//绘图事件
void mainscene::paintEvent(QPaintEvent *)
{
    
    
    //创建画家对象
    QPainter painter(this);

    //创建QPixmap对象
    QPixmap pix;
    pix.load(":/res/PlayLevelSceneBg.png");  //加载第一个场景的背景图

    //绘制背景
    painter.drawPixmap(0,0,this->width(),this->height(),pix);  //参数3和4表示将图片与屏幕进行适配

    //加载图片左上角的标题图片
    pix.load(":/res/Title.png");  //复用一下pix对象

    //缩放图片
    pix = pix.scaled(pix.width()*0.5,pix.height()*0.5);

    //绘制标题
    painter.drawPixmap(10,30,pix);
}

在这里插入图片描述

图1

4、开始按钮封装

  • (1)新建一个名为MyPushButton的C++ Class,继承于QObject(创建完毕后在代码中改成QPushButton)
  • (2)在mypushbutton.h中重新写了一种构造方式,代码如下
#ifndef MYPUSHBUTTON_H
#define MYPUSHBUTTON_H

#include <QPushButton>

class MyPushButton : public QPushButton
{
    
    
    Q_OBJECT
public:

    //参数1  正常显示图片路径  参数2  按下后切换图片路径
    MyPushButton(QString normalImg,QString pressImg=" ");

    QString normalPath;
    QString pressPath;

signals:

public slots:
};

#endif // MYPUSHBUTTON_H

  • (3)mypushbutton.cpp中实现,代码如下:
#include "mypushbutton.h"
#include <QDebug>

//参数1  正常显示图片路径  参数2  按下后切换图片路径
MyPushButton::MyPushButton(QString normalImg,QString pressImg)
{
    
    
    this->normalPath = normalImg;
    this->pressPath = pressImg;

    //创建QPixmap对象
    QPixmap pix;
    bool ret = pix.load(this->normalPath);  //ret用于报错加载成功与否的结果
    if(!ret)
    {
    
    
        QString str = QString("图片加载失败,失败的路径是: %1").arg(this->normalPath);
        qDebug()<<str;
    }
    //如果成功
    //设置按钮固定尺寸
    this->setFixedSize(pix.width(),pix.height());
    //设置不规则样式
    this->setStyleSheet("QPushButton{border:0px;}");  //有点类似于网页设计中的CSS语法
    //设置图标
    this->setIcon(pix);
    //设置图标大小
    this->setIconSize(QSize(pix.width(),pix.height()));
}

  • 在mainscene.cpp中,显示了开始按钮,效果如下在这里插入图片描述
图2

5、开始按钮跳跃效果实现

  • 在MyPushButton中封装zoom1和zoom2函数实现跳跃
  • (1)在mypushbutton.h中添加了下图3所示的代码
    在这里插入图片描述
图3
  • (2)在mypushbutton.cpp中添加了下图4所示的代码,值得注意的是,在创建动画这一行代码中,动画的方式别打错字,否则可能出现以下报错QPropertyAnimation: you're trying to animate a non-existing property grometry of your QObject,导致点击按钮没反应。

在这里插入图片描述

图4
//让按钮向下跳跃
void MyPushButton::zoom1()
{
    
    
    //创建动画对象
    QPropertyAnimation * animation = new QPropertyAnimation(this,"geometry");  //参数1:让谁去做这个动画,参数2:动画的方式

    //设置动画间隔
    animation->setDuration(200);

    //设置起始位置
    animation->setStartValue(QRect(this->x(),this->y(),this->width(),this->height()));

    //设置结束位置(接口和起始位置不一样)
    animation->setEndValue(QRect(this->x(),this->y()+10,this->width(),this->height()));  //因为是向下跳跃,所以只有y坐标会增加

    //设置动画曲线(加速、减速、变速)
    animation->setEasingCurve(QEasingCurve::OutBounce);

    //执行动画
    animation->start(QAbstractAnimation::DeleteWhenStopped);  //DeleteWhenStopped表示动画执行完毕后就释放对象,KeepWhenStopped则表示保存对象


}

void MyPushButton::zoom2()
{
    
    
    //创建动画对象
    QPropertyAnimation * animation = new QPropertyAnimation(this,"geometry");  //参数1:让谁去做这个动画,参数2:动画的方式

    //设置动画间隔
    animation->setDuration(200);

    //设置起始位置
    animation->setStartValue(QRect(this->x(),this->y()+10,this->width(),this->height())); //这里和向下跳跃不同的是,他的起始位置是变化之后的位置

    //设置结束位置(接口和起始位置不一样)
    animation->setEndValue(QRect(this->x(),this->y(),this->width(),this->height()));

    //设置动画曲线(加速、减速、变速)
    animation->setEasingCurve(QEasingCurve::OutBounce);

    //执行动画
    animation->start(QAbstractAnimation::DeleteWhenStopped);  //DeleteWhenStopped表示动画执行完毕后就释放对象,KeepWhenStopped则表示保存对象
}

  • (3)在mainscene中调用这两个函数进行测试
    在这里插入图片描述
图5

6、选择关卡场景的基本配置

  • (1)创建ChooseLevelScene 选择关卡场景
  • (2)在主场景中维护了第二个场景的指针
  • (3)在主场景的cpp中去创建第二个场景,并且点击开始按钮进行跳转(有一个延时进入)

在chooselevelscene.cpp中用代码编写出了菜单栏,并且编写了背景的绘图事件
在这里插入图片描述

图6

7、返回按钮的创建

(1)在选关场景中创建返回按钮,重写按钮的鼠标按下和鼠标释放事件。具体效果为:当鼠标按下的时候显示另外一张图片,当鼠标松开时恢复出原来的效果。
在这里插入图片描述

图7

(2)在自定义的mypushbutton.h中重写了鼠标按下鼠标释放事件在这里插入图片描述

图8

(3)在mypushbutton.cpp中实现了鼠标按下鼠标释放事件在这里插入图片描述

图9

在这里插入图片描述

图10

8、返回按钮的功能实现

功能描述:点击返回按钮时,能够回到主场景。
实现手段:利用信号和槽。
(1)选择关卡场景的.h中定义了返回按钮的信号在这里插入图片描述

图11

(2)选择关卡场景的.cpp文件中实现了返回按钮的信号
在这里插入图片描述

图12

在主场景的.cpp文件中,监听了返回按钮的信号。在这里插入图片描述

图13

9、创建选择关卡的小按钮

由于是按钮,故直接可通过自己编写的mypushbutton类来创建。
因此,在选择关卡chooselevelscene.cpp中直接加入以下代码即可:

//--------创建20个具体关卡的按钮------------
    for(int i = 0 ;i <20;i++)
    {
    
    
        MyPushButton * menuBtn = new MyPushButton(":/res/LevelIcon.png","");  //创建选关按钮,并设置图标
        menuBtn->setParent(this);  //让选关按钮出现在当前场景
        menuBtn->move(25+(i%4)*70,130+(i/4)*70);  //通过取模、除法运算,使得一个for循环实现4行5列的矩阵
        
        connect(menuBtn,&MyPushButton::clicked,[=](){
    
      //监听每一个按钮的信号
            qDebug()<<"您选择的是第"<<i+1<<"关";
        });
    }

效果演示如下:
在这里插入图片描述

图14

核心代码说明: menuBtn->move(25+(i%4)*70,130+(i/4)*70);

可以看到上图,关卡按钮是4行5列的。

  • 当i为0、1、2、3时,25+(i%4)*70所得到的值分别为:25 95 165 235,对应按钮的横坐标;
  • 当i为0、4、8、12、16时,130+(i/4)*70所得到的值对应按钮的纵坐标。

10、显示关卡按钮上面的数字实现

在按钮上显示关卡数字。
实现思路1: 按钮底层本身封装了一个函数—setText,可以在按钮上直接显示内容。
具体代码:
menuBtn->setText(QString::number(i+1)); //因为setText要的是字符串,而QString::number表示将int型数据转化成字符串型。
具体效果如下:会发现该方法无法得到理想的效果,因为原先的按钮是矩形的,我们将其改为了圆形,因此文本也无法对应。

在这里插入图片描述

图15

实现思路2: 利用QLabel标签。
具体代码如下:

//显示按钮上面的数字
       QLabel * label = new QLabel;
       label->setParent(this);
       label->move(25+(i%4)*70,130+(i/4)*70);  //让QLabel移动到和按钮一样的位置
       //设置尺寸
       label->setFixedSize(menuBtn->width(),menuBtn->height());  //让QLabel的大小和按钮一样大
       //设置文本
       label->setText(QString::number(i+1));
       //设置对齐方式
       label->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);  //QT中的或相当于让两个条件都成立(水平和垂直居中)
       //设置属性   鼠标穿透属性
       label->setAttribute(Qt::WA_TransparentForMouseEvents);  //不设置这一代码点击按钮时会没有反应,因为Qlabel标签位于按钮的上一层,文字会吸收掉点击事件

在这里插入图片描述

图16

在这里插入图片描述

图17

11、翻金币场景的创建及基本搭建

(1)创建翻金币场景:创建PlayScene.h和PlayScene.cpp文件(父类为QMainWindow)
(2)在选择关卡场景chooselevelscene.h中维护 第三个场景的指针。在这里插入图片描述

图18

(3)当用户点击选关按钮,创建第三个场景。在这里插入图片描述

图19

(4)游戏场景的基本搭建
即绘图事件,在playscene.h和playscene.cpp中的代码分别为:
在这里插入图片描述

图20

在这里插入图片描述

图21

12、翻金币场景的返回按钮实现

(1)创建返回按钮。
(2)点击时发送自定义信号。
(3)在选关场景(第2场景)中监听翻金币游戏场景的信号,并作响应。

  • 在playscene.h中设置了一个返回按钮的信号
  • 在playscene.cpp中实现了信号的发射
  • 在ChooseLevelScene.cpp中监听了游戏场景发送回的信号(特别要注意监听信号的代码所在的范围,这种bug极难发现
  • 具体新增代码如下图所示在这里插入图片描述
图22

在这里插入图片描述

图23

在这里插入图片描述

图24

13、当前关卡号功能实现

在playscene.cpp文件中添加了如下代码,这个过程和第十章的内容差不多,只不过新增了两种技巧:
1: QString str = QString(“Level:%1”).arg(this->levelIndex); //arg的作用是拼接
2:QFont font(“华文新魏”,20); //设置标签的字体属性
label->setFont(font);
3: //设置标签的大小和位置
label->setGeometry(QRect(30,this->height()-50,this->width(),50));
在这里插入图片描述

图25

在这里插入图片描述

图26

14、金币背景图加载

(1)在playscene.cpp中新增了如下代码,注意,在新增代码中用到了两层for循环,这是为了在游戏规则处理时更加方便的选择硬币上下左右的关系。在这里插入图片描述

图27

效果如图28所示:在这里插入图片描述

图28

15、金币类创建

我们知道,金币是本游戏的核心对象,并且在游戏中可以利用二维数组进行维护,拥有支持点击,翻转特效等特殊性,因此不妨将金币单独封装到一个类中,完成金币所需的所有功能。
(1)创建一个MyCoin类,父类为QWidget,创建成功以后,将其构造函数的定义与实现删除,并将其父类改成QPushButton(头文件和继承都要改)
(2)MyCoin.h中的代码
在这里插入图片描述

图29

(3)MyCoin.cpp中的代码

在这里插入图片描述

图30

(4)playscene.cpp中的代码
在这里插入图片描述

图31

16、初始化关卡

首先将dataconfig.cpp和dataconfig.h文件导入项目中。
以dataconfig.h为例,该类只有一个成员,表示关卡号对应的金币规则

#ifndef DATACONFIG_H
#define DATACONFIG_H

#include <QObject>
#include <QMap>
#include <QVector>

class dataConfig : public QObject
{
    
    
    Q_OBJECT
public:
    explicit dataConfig(QObject *parent = 0);

public:

    QMap<int, QVector< QVector<int> > >mData;  //起到键值对的作用,参数1为key,代表关卡编号;参数2为value,其中value又是一个嵌套的容器,为一个二维数组



signals:

public slots:
};

#endif // DATACONFIG_H

下面给出了1-3关的dataconfig.cpp文件中的代码,其中1为金币,2为银币。

#include "dataconfig.h"
#include <QDebug>
dataConfig::dataConfig(QObject *parent) : QObject(parent)
{
    
    

     int array1[4][4] = {
    
    {
    
    1, 1, 1, 1},
                        {
    
    1, 1, 0, 1},
                        {
    
    1, 0, 0, 0},
                        {
    
    1, 1, 0, 1} } ;

     QVector< QVector<int>> v;
     for(int i = 0 ; i < 4;i++)
     {
    
    
         QVector<int>v1;
         for(int j = 0 ; j < 4;j++)
         {
    
    

            v1.push_back(array1[i][j]);
         }
         v.push_back(v1);
     }

     mData.insert(1,v);


     int array2[4][4] = {
    
     {
    
    1, 0, 1, 1},
                          {
    
    0, 0, 1, 1},
                          {
    
    1, 1, 0, 0},
                          {
    
    1, 1, 0, 1}} ;

     v.clear();
     for(int i = 0 ; i < 4;i++)
     {
    
    
          QVector<int>v1;
          for(int j = 0 ; j < 4;j++)
          {
    
    
             v1.push_back(array2[i][j]);
          }
          v.push_back(v1);
     }

     mData.insert(2,v);



     int array3[4][4] = {
    
      {
    
    0, 0, 0, 0},
                           {
    
    0, 1, 1, 0},
                           {
    
    0, 1, 1, 0},
                           {
    
    0, 0, 0, 0}} ;
     v.clear();
     for(int i = 0 ; i < 4;i++)
     {
    
    
          QVector<int>v1;
          for(int j = 0 ; j < 4;j++)
          {
    
    
             v1.push_back(array3[i][j]);
          }
          v.push_back(v1);
     }

     mData.insert(3,v);

然后分别在playscene.h和playscene.cpp中新增了以下代码,有用于给每个关卡的金币、银币进行图案排版:在这里插入图片描述

图32

在这里插入图片描述

图33

效果图如下,最后一关是全银币的。在这里插入图片描述

图34

17、翻转银币的功能实现(核心:切图)

(1)如下图所示,为了方便翻转硬币,在金币类中,拓展了属性posX、posY和flag。这三个属性分别代表了,该金币在二维数组中的x坐标,y坐标以及当前的正反标志(也就是是金币还是银币)

    int posX;  //x坐标
    int posY;  //y坐标
    bool flag;  //正反标志

在这里插入图片描述

图35

(2)关卡的初始化完成后,就应该点击金币,进行翻转的效果了,那么首先在MyCoin类中创建出该方法。
在MyCoin.h中声明:

void changeFlage();   //改变标志,执行翻转效果(只要点了,就会执行)
QTimer *timer1;      //正面翻反面   定时器(其实这两个用一个就行了,写两个是便于理解)
QTimer *timer2;      //反面翻正面   定时器
int min = 1;         //最小图片下标
int max = 8;         //最大图片下标

在这里插入图片描述

图36

(3)在MyCoin.cpp中实现:在这里插入图片描述

图37

在这里插入图片描述

图38

(4)在playscene.cpp中新增的测试代码如下在这里插入图片描述

图39

18、快速点击导致的翻转效果不完整

问题说明,当鼠标快速点击金币时,会出现金币翻银币,银币翻金币,然后又是银币翻金币。说白了就是金币翻银币的动作还没做,银币翻金币的动作就开始了。
解决方法,要将整个动作做完才能执行下一个动作(要做一些限制)。
加一个标志如isAnimation,用于表示当前金币是否做完了动画,默认为false,即做完了。
实现过程,在MyCoin.h中加入

bool isAnimation = false;  //做翻转动画的标志

实现过程,在MyCoin.cpp中加入
在这里插入图片描述

图40

19、翻转周围硬币

说明:点击硬币时,还需要将当前硬币周围上下左右4个硬币也进行延时翻转,代码写到监听硬币下。
此外,翻转硬币时还要注意,最左侧、右侧、上侧、下侧的几个位置的规则是不一样的。
(1)在playscene.h中,新增了如下代码:
在这里插入图片描述

图41

(2)在playscene.cpp中,新增了如下代码:
在这里插入图片描述

图42

在这里插入图片描述

图43

20、游戏胜利的检测以及禁用硬币点击

(1)检测游戏胜利
在playscene.h中新增以下代码:
在这里插入图片描述

图44

在playscene.cpp中新增以下代码:
在这里插入图片描述

图45

(2)禁用硬币翻转
在mycoin.h中新增以下代码
在这里插入图片描述

图46

在mycoin.cpp中新增以下代码
在这里插入图片描述

图47

21、游戏胜利后手速过快导致的bug

描述: 当手速过快时可能会出现下一步所示的bug,明明点击一步就赢了,但是在0.3s内又点了另外一个硬币,就会出现下图所示的情况。
原因: 这是因为周围的硬币是在0.3s后才翻转的,胜利检测也是如此,但是这时候玩家又点击了下一张个硬币,就会出现当前的bug。
在这里插入图片描述

图48

解决方法: 在playscene.cpp中加入以下代码在这里插入图片描述

图49

22、胜利图片显示

(1)在playscene.cpp的构造函数中准备好胜利图片
在这里插入图片描述

图50

(2)当游戏胜利后,将图片以动画的效果显示出来
在这里插入图片描述

图51

23、音效的添加

(1)添加音效要用到#include < QSound > 头文件,在这之前需要在.pro文件中添加multimedia模块,如下图所示:
在这里插入图片描述

图52

(2)分别在三个场景中添加音效

  • mainscene.cpp中添加开始音效
    在这里插入图片描述
图53
  • 在chooselevelscene.cpp中添加选择关卡返回音效在这里插入图片描述
图54
  • 在playscene.cpp中添加翻转金币返回游戏胜利音效在这里插入图片描述
图55

24、bug解决—场景位置统一

该游戏有三个场景,但是如果在不同场景下去移动位置,之后点击返回按钮,这三个场景的位置会不在一起,因此最好固定三个场景的位置。
(1)开始场景和选关场景的解决在这里插入图片描述

图56

在这里插入图片描述

图57

25、游戏辅助玩法介绍

(1)存档功能
(2)倒计时功能
(3)提示功能
(4)逐关解锁功能

26、项目打包发布

(1)首先将项目从debug模式改成release模式,点击左下方的电脑图标。在这里插入图片描述

图58

(2)点击运行
(3)之后就可以在资源路径下找到release文件夹,找到相应的exe文件在这里插入图片描述

图59

双击运行,会发现有错误提示(如图60所示),这是因为没有配置环境变量,需要图61的方法加入环境变量。但是如果我们想让其他人也玩这个游戏,那岂不是得让别人也安装QT,也配置环境变量。(当然不是)在这里插入图片描述

图60

在这里插入图片描述

图61

那么如何真正的打包发布呢?
首先在开始菜单中找到MinnGW软件,然后再看看自己的安装路径下有没有w开头的exe文程序,如图63所示,有的话说明具备打包发布的功能。在这里插入图片描述

图62

在这里插入图片描述

图63

将刚release生成的CoinFlip.exe单独放在一个文件夹,哪里都行。我放在了桌面的111文件夹中
在这里插入图片描述

图64

双击启动开始菜单中的MinnGW软件,然后输入以下代码:

//windeployqt 加 刚刚release出来的exe软件全部路径名称(我放在桌面了)
windeployqt C:\Users\Lenovo\Desktop\111\CoinFlip.exe

在这里插入图片描述

图65

执行完毕后就能看到,文件夹中多出了许多文件,并且这时候点击运行发现也能正常玩了,如果要发给别人,需要将这里面的所有文件一起发过去。
在这里插入图片描述

图66

最后优化:可以看到上述游戏要发给别人玩会发现附带了许多配置文件,很冗余的感觉,但是像在应用商店里面下载的游戏都是给出一个安装包,然后选择路径一步一步安装,最后不想玩了还可以卸载。那么QT能不能做到呢?当然能,但是这就需要第三方工具。
nisedit2.0.3.exensis-3.05-setup.exe
有需要的小伙伴可以自行去了解一下这两款软件。


总算是写完了,可以看到制作一个这么小的小游戏也没有想象中的那么简单,一个人要弄的话也要弄好久。麻雀虽小,五脏俱全。
能看到这也不容易,希望你也有所收获,最后,如果觉得本文对你有所帮助的话,希望能点赞收藏,您的鼓励是对我最大的支持!

猜你喜欢

转载自blog.csdn.net/qq_40077565/article/details/123178966