QT从入门到实战x篇_32_实战篇:翻金币(创建项目;场景切换;设置背景图片;按钮控件封装;跳跃特效;QTimer::singleShot();引入数据类;QMap<>;翻金币特效;插入音效;打包)

2023-11-07

本篇将会根据前面讲解的关于Qt的相关内容进行一个实战项目,项目链接:翻金币案例
关于此项目比较好的博客地址如下:【QT】翻金币项目

翻金币项目总结


整个项目的思维导图如下图所示:具体的XMind文件地址: Qt翻金币项目XMind思维导图,见上。

在这里插入图片描述

1 项目简介

翻金币项目是一款经典的益智类游戏,我们需要将金币都翻成同色,才视为胜利。

  • 案例的一级页面如下图所示,采用QMainWindow,左上角的菜单栏中有 “开始”“退出” 按钮,点击 “START”“START” 按钮上下跳动之后进入二级页面。

在这里插入图片描述

  • 案例的二级页面如下图所示,采用QMainWindow,右下角的 “BACK” 按钮在按下和释放时会有不同的点击效果,“BACK”按钮按下之后会返回上一级页面,选择对应的关卡之后就会跳入对应的关卡页面。
    在这里插入图片描述
  • 下图为其中一个关卡的三级页面,以关卡1的页面为例,当进入页面后的初始页面如下,其翻转规则为:点击一个按钮,其上下左右的按钮都会进行翻转,因此选中下图中红色框选部分,红色框内及上下左右均会进行翻转,从银币翻转为金币,整个页面就均为金币,此时就代表了胜利。
    在这里插入图片描述
    下图中点击了箭头所指的金币,它和它上下左右的币都翻转为另一种类型,如下图所示
    在这里插入图片描述
  • 一旦胜利之后就会弹出如下按钮,除了弹出 “SUCCEED” ,还需要将金币的按钮禁掉,这样就不可以再点动了,但是 “BACK” 按钮还是可以点动的
    在这里插入图片描述

2 项目基本配置

2.1 创建项目

打开Qt-Creator,创建项目:注意名称不要包含空格和回车,路径不要有中文
在这里插入图片描述
类信息中,选择基类为QMainWindow,类名称为 MainScene,代表着主场景。
在这里插入图片描述
点击完成,创建出项目:
在这里插入图片描述
创建的项目结构如下:
在这里插入图片描述

2.2 添加资源

将资源添加到当前项目下
在这里插入图片描述
然后创建.qrc文件
在这里插入图片描述
进入编辑模式,添加前缀 “/” ,添加文件
在这里插入图片描述
在这里插入图片描述
将所有资源文件进行添加,并进行编译
在这里插入图片描述
至此将所有需要的资源添加到了本项目中。

3 主场景

3.1 设置游戏主场景配置

点击mainscene.ui文件,设计其菜单栏如下:
在这里插入图片描述
在这里插入图片描述
设计“退出”菜单项,objectName为 actionQuit, text 为 退出;
移除自带的工具栏与状态栏
在这里插入图片描述
回到MainScene.cpp文件,进入构造函数中,进行场景的基本配置,代码如下:

//设置固定大小
    this->setFixedSize(320,588);
    //设置应用图片
    this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
    //设置窗口标题
    this->setWindowTitle("老帮主带你翻金币");

运行效果如图:
在这里插入图片描述
实现点击开始,退出游戏功能,代码如下:

//点击退出,退出程序
   connect(ui->actionQuit,&QAction::triggered,[=](){this->close();});

3.2 设置背景图片

重写MainScene的PaintEvent事件,并添加一下代码,绘制背景图片。
另外一种方法是设置与一级页面大小相同的Label来绘制背景,但是很少使用这种方式来做。

void MainScene::paintEvent(QPaintEvent *)
{
    //创建画家,指定绘图设备
    QPainter painter(this);
    //创建QPixmap对象
    QPixmap pix;
    //加载图片
    pix.load(":/res/PlayLevelSceneBg.png");
    //绘制背景图
    painter.drawPixmap(0,0,this->width(),this->height(),pix);


    //加载标题
    pix.load(":/res/Title.png");
    //缩放图片
    pix = pix.scaled(pix.width()*0.5,pix.height()*0.5);
    //绘制标题
    painter.drawPixmap( 10,30,pix.width(),pix.height(),pix);
}

运行效果如图:
在这里插入图片描述

3.3 创建开始按钮(控件封装)

开始按钮点击后有弹跳和按下抬起的按钮图片不同的效果,这两个效果是我们利用自定义控件实现的(QPushButton不会自带这类特效),我们可以自己封装出一个按钮控件,来实现这些效果。
创建MyPushButton,继承与QPushButton
在这里插入图片描述
在这里插入图片描述
点击完成。
修改MyPushButton的父类
在这里插入图片描述
提供MyPushButton的构造的重载版本,可以让MyPushButton提供正常显示的图片以及按下后显示的图片
代码如下:

//normalImg 代表正常显示的图片
    //pressImg  代表按下后显示的图片,默认为空 如果不传入值,按下之后按钮的图片就不会发生变化
    MyPushButton(QString normalImg,QString pressImg = "");

    QString normalImgPath;  //默认显示图片路径
    QString pressedImgPath; //按下后显示图片路径

实现的重载版本MyPushButton构造函数代码如下:

//QString pressImg在.h中已经有默认参数,在.cpp中就不需要再声明,.h和.cpp中有一个地方声明默认参数即可
MyPushButton::MyPushButton(QString normalImg,QString pressImg)
{
    //成员变量normalImgPath保存正常显示图片路径
    normalImgPath = normalImg;
    //成员变量pressedImgPath保存按下后显示的图片
    pressedImgPath = pressImg;
    //创建QPixmap对象
    QPixmap pixmap;
    //判断是否能够加载正常显示的图片,若不能提示加载失败
    bool ret = pixmap.load(normalImgPath);
    if(!ret)
    {
        qDebug() << normalImg << "加载图片失败!";
    }
    //设置图片的固定尺寸
    this->setFixedSize( pixmap.width(), pixmap.height() );
    //设置不规则图片的样式表 实现边框为0的不规则图片
    this->setStyleSheet("QPushButton{border:0px;}");
    //设置图标
    this->setIcon(pixmap);
    //设置图标大小
    this->setIconSize(QSize(pixmap.width(),pixmap.height()));
}

回到MainScene的构造函数中,创建开始按钮

//创建开始按钮
    MyPushButton * startBtn = new MyPushButton(":/res/MenuSceneStartButton.png");
    startBtn->setParent(this);
    startBtn->move(this->width()*0.5-startBtn->width()*0.5,this->height()*0.7);

运行效果如图:
在这里插入图片描述
不规则的开始按钮添加完成。

3.4 开始按钮跳跃特效实现

连接信号槽,监听开始按钮点击

//监听点击事件,执行特效
    connect(startBtn,&MyPushButton::clicked,[=](){
        startBtn->zoom1(); //向下跳跃
        startBtn->zoom2(); //向上跳跃
      
    });

zoom1与zoom2 为MyPushButton中扩展的特效代码,具体如下:

void MyPushButton::zoom1()
{
    //创建动画对象 
    QPropertyAnimation * animation1 = new QPropertyAnimation(this,"geometry");
    //设置时间间隔,单位毫秒
    animation1->setDuration(200);
    //创建起始位置 矩形的设置为起始的x,起始的y,起始的width和height
    animation1->setStartValue(QRect(this->x(),this->y(),this->width(),this->height()));
    //创建结束位置
    animation1->setEndValue(QRect(this->x(),this->y()+10,this->width(),this->height()));
    //设置缓和曲线,QEasingCurve::OutBounce 为弹跳效果    animation1->setEasingCurve(QEasingCurve::OutBounce);
    //开始执行动画
    animation1->start();
}

void MyPushButton::zoom2()
{
    QPropertyAnimation * animation1 =  new QPropertyAnimation(this,"geometry");
    animation1->setDuration(200);
    animation1->setStartValue(QRect(this->x(),this->y()+10,this->width(),this->height()));
    animation1->setEndValue(QRect(this->x(),this->y(),this->width(),this->height()));
    animation1->setEasingCurve(QEasingCurve::OutBounce);
    animation1->start();
}

运行代码,点击按钮,测试弹跳效果。

3.5 创建选择关卡场景

点击开始按钮后,进入选择关卡场景。
首先我们先创建选择关卡场景,添加新的C++文件
在这里插入图片描述
在这里插入图片描述
类名为ChooseLevelScene 选择基类为QMainWindow,点击下一步,然后点击完成。

3.6 点击开始按钮进入选择关卡场景

目前点击主场景的开始按钮,只有弹跳特效,但是我们还需要有功能上的实现,特效结束后,我们应该进入选择关卡场景
在MainScene.h中 保存ChooseScene选择关卡场景对象

//选择关卡场景
    ChooseLevelScene *chooseScene = new ChooseLevelScene;

我们在zoom1和zoom2特效后,延时0.5秒,进入选择关卡场景,代码如下:

//延时0.5秒后 进入选择场景 静态成员函数
        QTimer::singleShot(500, this,[=](){
            this->hide();
            chooseScene->show();
        });

在这里插入图片描述
测试点击开始,执行特效后延时0.5秒进入选择关卡场景

4 选择关卡场景

4.1 场景基本设置

选择关卡构造函数如下:

//设置窗口固定大小
    this->setFixedSize(320,588);
    //设置图标
    this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
    //设置标题
    this->setWindowTitle("选择关卡");

    //创建菜单栏
    QMenuBar * bar = this->menuBar();
    this->setMenuBar(bar);
    //创建开始菜单
    QMenu * startMenu = bar->addMenu("开始");
    //创建按钮菜单项
    QAction * quitAction = startMenu->addAction("退出");
    //点击退出 退出游戏
    connect(quitAction,&QAction::triggered,[=](){this->close();});

运行效果如图:
在这里插入图片描述

4.2 背景设置

void ChooseLevelScene::paintEvent(QPaintEvent *)
{
    QPainter painter(this);
    QPixmap pix;
    pix.load(":/res/OtherSceneBg.png");
    painter.drawPixmap(0,0,this->width(),this->height(),pix);

     //加载标题
    pix.load(":/res/Title.png");
    painter.drawPixmap( (this->width() - pix.width())*0.5,30,pix.width(),pix.height(),pix);
}

4.3 创建返回按钮

//返回按钮
    MyPushButton * closeBtn = new MyPushButton(":/res/BackButton.png",":/res/BackButtonSelected.png");
    closeBtn->setParent(this);
    closeBtn->move(this->width()-closeBtn->width(),this->height()-closeBtn->height());

返回按钮是有正常显示图片和点击后显示图片的两种模式,所以我们需要在传入前后的图片之后,重写MyPushButton中的 MousePressEvent和MouseReleaseEvent

//鼠标事件
void MyPushButton::mousePressEvent(QMouseEvent *e)
{
    //START不需要按钮图片的切换,使用pressedImgPath来进行判断是否需要切换
    if(pressedImgPath != "") //选中路径不为空,显示选中图片
    {
        QPixmap pixmap;
        bool ret = pixmap.load(pressedImgPath);
        if(!ret)
        {
            qDebug() << pressedImgPath << "加载图片失败!";
        }

        this->setFixedSize( pixmap.width(), pixmap.height() );
        this->setStyleSheet("QPushButton{border:0px;}");
        this->setIcon(pixmap);
        this->setIconSize(QSize(pixmap.width(),pixmap.height()));
    }
    //交给父类执行按下事件
    return QPushButton::mousePressEvent(e);
}
void MyPushButton::mouseReleaseEvent(QMouseEvent *e)
{
    if(normalImgPath != "") //选中路径不为空,显示选中图片
    {
        QPixmap pixmap;
        bool ret = pixmap.load(normalImgPath);
        if(!ret)
        {
            qDebug() << normalImgPath << "加载图片失败!";
        }
        this->setFixedSize( pixmap.width(), pixmap.height() );
        this->setStyleSheet("QPushButton{border:0px;}");
        this->setIcon(pixmap);
        this->setIconSize(QSize(pixmap.width(),pixmap.height()));
    }
    //交给父类执行 释放事件
    return QPushButton::mouseReleaseEvent(e);
}

4.4 返回按钮

在这里我们点击返回后,延时0.5后隐藏自身,并且发送自定义信号,告诉外界自身已经选择了返回按钮。

//返回按钮功能实现
    connect(closeBtn,&MyPushButton::clicked,[=](){
        QTimer::singleShot(500, this,[=](){
            this->hide();
            //触发自定义信号,关闭自身,该信号写到 signals下做声明
            emit this->chooseSceneBack();
             }
        );
    });

在主场景MainScene中 点击开始按钮显示选择关卡的同时,监听选择关卡的返回按钮消息

//监听选择场景的返回按钮    
connect(chooseScene,&ChooseLevelScene::chooseSceneBack,[=](){
                this->show();
            });

测试主场景与选择关卡场景的切换功能。

4.5 创建选择关卡按钮

//创建选择关卡的按钮 每个按钮均创建一个对象 名字相同但是地址不同 将每个按钮对象与槽函数绑定
    for(int i = 0 ; i < 20;i++)
    {
        MyPushButton * menuBtn = new MyPushButton(":/res/LevelIcon.png");
        menuBtn->setParent(this);
        //一层的循环做出二维矩阵,%取余定义X轴,/取整定义Y轴
        menuBtn->move(25 + (i%4)*70 , 130+ (i/4)*70);

        //按钮上显示的文字 使用设置数字直接显示很难看 利用QLabel实现
        QLabel * label = new QLabel;
        label->setParent(this);
        label->setFixedSize(menuBtn->width(),menuBtn->height());
        label->setText(QString::number(i+1));
        //设置label上文字的对齐方式
        label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); //设置居中
        label->move(25 + (i%4)*70 , 130+ (i/4)*70);
        //label盖在了menuBtn上,鼠标点击事件作用在label上没有传递到按钮上,需要设置穿透传递给按钮
        label->setAttribute(Qt::WA_TransparentForMouseEvents,true);  //鼠标事件穿透
    }

运行效果如果:
在这里插入图片描述

4.6 创建翻金币场景

点击关卡按钮后,会进入游戏的核心场景,也就是翻金币的场景,首先先创建出该场景的.h和.cpp文件
创建PlayScene
在这里插入图片描述
点击选择关卡按钮后会跳入到该场景
建立点击按钮,跳转场景的信号槽连接

在ChooseLevelScene.h 中声明
PlayScene *pScene = NULL;

//监听选择关卡按钮的信号槽
        connect(menuBtn,&MyPushButton::clicked,[=](){
           // qDebug() << "select: " << i;
            if(pScene == NULL)  //游戏场景最好不用复用,直接移除掉创建新的场景
            {
                this->hide();
                pScene = new PlayScene(i+1); //将选择的关卡号 传入给PlayerScene
                pScene->show();
            }
        });

这里pScene = new PlayScene(i+1); 将用户所选的关卡号发送给pScene,也就是翻金币场景,当然PlayScene 要提供重载的有参构造版本,来接受这个参数

5 翻金币场景

5.1 场景基本设置

PlayScene.h中 声明成员变量,用于记录当前用户选择的关卡

//成员变量 记录关卡索引
    int levalIndex;

PlayScene.cpp中 初始化该场景配置

PlayScene::PlayScene(int index)
{
    //qDebug() << "当前关卡为"<< index;
    this->levalIndex = index;
    //设置窗口固定大小
    this->setFixedSize(320,588);
    //设置图标
    this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
    //设置标题
    this->setWindowTitle("翻金币");

    //创建菜单栏
    QMenuBar * bar = this->menuBar();
    this->setMenuBar(bar);
    //创建开始菜单
    QMenu * startMenu = bar->addMenu("开始");
    //创建按钮菜单项
    QAction * quitAction = startMenu->addAction("退出");
    //点击退出 退出游戏
    connect(quitAction,&QAction::triggered,[=](){this->close();});
}

5.2 背景设置

void PlayScene::paintEvent(QPaintEvent *)
{
    //加载背景
    QPainter painter(this);
    QPixmap pix;
    pix.load(":/res/PlayLevelSceneBg.png");
    painter.drawPixmap(0,0,this->width(),this->height(),pix);

    //加载标题
    pix.load(":/res/Title.png");
    pix = pix.scaled(pix.width()*0.5,pix.height()*0.5);
    painter.drawPixmap( 10,30,pix.width(),pix.height(),pix);
}

5.3 返回按钮

 //返回按钮
    MyPushButton * closeBtn = new MyPushButton(":/res/BackButton.png",":/res/BackButtonSelected.png");
    closeBtn->setParent(this);
    closeBtn->move(this->width()-closeBtn->width(),this->height()-closeBtn->height());

    //返回按钮功能实现
    connect(closeBtn,&MyPushButton::clicked,[=](){
        QTimer::singleShot(500, this,[=](){
            this->hide();
            //触发自定义信号,关闭自身,该信号写到 signals下做声明
            emit this->chooseSceneBack();
             }
        );
    });

在ChooseScene选择关卡场景中,监听PlayScene的返回信号

connect(pScene,&PlayScene::chooseSceneBack,[=](){
                    this->show();
                    delete pScene;
                    pScene = NULL;
                });

在这里插入图片描述

5.4 显示当前关卡

//当前关卡标题
    QLabel * label = new QLabel;
    label->setParent(this);
    QFont font;
    font.setFamily("华文新魏");
    font.setPointSize(20);
    label->setFont(font);
    //格式化字符串
    QString str = QString("Leavel: %1").arg(this->levalIndex);
    label->setText(str);
    label->setGeometry(QRect(30, this->height() - 50,120, 50)); //设置大小和位置

假设我们选择了第15关卡,运行效果如下:
在这里插入图片描述

5.5 创建金币背景图片

//创建金币的背景图片
    for(int i = 0 ; i < 4;i++)
    {
        for(int j = 0 ; j < 4; j++)
        {
           //绘制背景图片
            QLabel* label = new QLabel;
            label->setGeometry(0,0,50,50);
            label->setPixmap(QPixmap(":/res/BoardNode.png"));
            label->setParent(this);
            label->move(57 + i*50,200+j*50);
        }
    }

运行效果如图:
在这里插入图片描述

5.6 创建金币类

我们知道,金币是本游戏的核心对象,并且在游戏中可以利用二维数组进行维护,拥有支持点击,翻转特效等特殊性,因此不妨将金币单独封装到一个类中,完成金币所需的所有功能。
金币的动作与MPushButton类的动作相差太多,因此需要重新创建类

5.6.1 创建金币类 MyCoin

在这里插入图片描述
并修改MyCoin的基类为QPushButton

5.6.2 构造函数

在资源图片中,我们可以看到,金币翻转的效果原理是多张图片切换而形成的,而以下八张图片中,第一张与最后一张比较特殊,因此我们在给用户看的时候,无非是金币Coin0001或者是银币 Coin0008这两种图。
因此我们在创建一个金币对象时候,应该提供一个参数,代表着传入的是金币资源路径还是银币资源路径,根据路径我们创建不同样式的图案。
在这里插入图片描述
在MyCoin.h中声明:

MyCoin(QString butImg); //代表图片路径

在MyCoin.cpp中进行实现

MyCoin::MyCoin(QString butImg)
{

    QPixmap pixmap;
    bool ret = pixmap.load(butImg);
    if(!ret)
    {
        qDebug() << butImg << "加载图片失败!";
    }

    this->setFixedSize( pixmap.width(), pixmap.height() );
    this->setStyleSheet("QPushButton{border:0px;}");
    this->setIcon(pixmap);
    this->setIconSize(QSize(pixmap.width(),pixmap.height()));

}

5.6.3 测试

在翻金币场景 PlayScene中,我们测试下封装的金币类是否可用,可以在创建好的金币背景代码后,添加如下代码:

//金币对象
            MyCoin * coin = new MyCoin(":/res/Coin0001.png");
            coin->setParent(this);
            coin->move(59 + i*50,204+j*50);

运行效果如图
在这里插入图片描述

5.7 引入关卡数据

当然上述的测试只是为了让我们知道提供的对外接口可行,但是每个关卡的初始化界面并非如此,因此需要我们引用一个现有的关卡文件,文件中记录了各个关卡的金币排列情况,也就是二维数组的数值。

5.7.1 添加现有文件dataConfig

首先先将dataConfig.h 和 dataConfig.cpp文件放入到当前项目下:
在这里插入图片描述

5.7.2 添加现有文件

其次在Qt_Creator项目右键,点击添加现有文件
在这里插入图片描述

5.7.3 完成添加

选择当前项目下的文件,并进行添加
在这里插入图片描述

5.7.4 数据分析

我们可以看到,其实dataConfig.h中只有一个数据是对外提供的,如下图
在这里插入图片描述
在上图中,QMap<int,QVector<QVector>>mData;都记录着每个关卡中的数据。
其中,int代表对应的关卡 ,也就是QMap中的key值,而value值就是对应的二维数组,我们利用的是 QVector<QVector>来记录着其中的二维数组。

5.7.5 测试关卡数据

在Main函数可以测试第一关的数据,添加如下代码:

dataConfig config;
    for(int i = 0 ; i < 4;i++)
    {
        for(int j = 0 ; j < 4; j++)
        {
            //打印第一关所有信息
           qDebug() << config.mData[1][i][j];

        }
        qDebug()<< "";
    }

输出结果如下图:
在这里插入图片描述
对应着dataConfig.cpp中第一关数据来看,与之匹配成功,以后我们就可以用dataConfig中的数据来对关卡进行初始化了
在这里插入图片描述

5.8 初始化各个关卡

首先,可以在playScene中声明一个成员变量,用户记录当前关卡的二维数组

int gameArray[4][4]; //二维数组数据 维护每个数组的具体数据

之后,在.cpp文件中,初始化这个二维数组

//初始化二维数组
    dataConfig config;
    for(int i = 0 ; i < 4;i++)
    {
        for(int j = 0 ; j < 4; j++)
        {
            gameArray[i][j] = config.mData[this->levalIndex][i][j];
        }
    }

初始化成功后,在金币类 也就是MyCoin类中,扩展属性 posX,posY,以及flag
这三个属性分别代表了,该金币在二维数组中 x的坐标,y的坐标,以及当前的正反标志。

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

然后完成金币初始化,代码如下:

//金币对象
            	QString img;
            if(gameArray[i][j] == 1)
            {
              img = ":/res/Coin0001.png";
            }
            else
            {
              img = ":/res/Coin0008.png";
            }
            MyCoin * coin = new MyCoin(img);
            coin->setParent(this);
            coin->move(59 + i*50,204+j*50);
            coin->posX = i; //记录x坐标
            coin->posY = j; //记录y坐标
            coin->flag =gameArray[i][j]; //记录正反标志

运行测试各个关卡初始化,例如第一关效果如图:
在这里插入图片描述

5.9 翻金币特效

5.9.1 MyCoin类扩展属性和行为

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

void changeFlag();//改变标志,执行翻转效果
    QTimer *timer1; //正面翻反面 定时器
    QTimer *timer2; //反面翻正面 定时器
    int min = 1; //最小图片
    int max = 8; //最大图片

MyCoin.cpp中做实现

void MyCoin::changeFlag()
{
    if(this->flag) //如果是正面,执行下列代码
    {
        timer1->start(30);
        this->flag = false;
    }
    else //反面执行下列代码
    {
        timer2->start(30);
        this->flag = true;
    }
}

当然在构造函数中,记得创建出两个定时器

//初始化定时器
    timer1 = new QTimer(this);
    timer2 = new QTimer(this);

5.9.2 创建特效

当我们分别启动两个定时器时,需要在构造函数中做监听操作,并且做出响应,翻转金币,然后再结束定时器。
构造函数中 进行下列监听代码:

//监听正面翻转的信号槽
    connect(timer1,&QTimer::timeout,[=](){
        QPixmap pixmap;
        QString str = QString(":/res/Coin000%1.png").arg(this->min++);
        pixmap.load(str);
        this->setFixedSize(pixmap.width(),pixmap.height() );
        this->setStyleSheet("QPushButton{border:0px;}");
        this->setIcon(pixmap);
        this->setIconSize(QSize(pixmap.width(),pixmap.height()));
        if(this->min > this->max) //如果大于最大值,重置最小值,并停止定时器
        {
            this->min = 1;
            timer1->stop();
        }
    });

    connect(timer2,&QTimer::timeout,[=](){
        QPixmap pixmap;
        QString str = QString(":/res/Coin000%1.png").arg((this->max)-- );
        pixmap.load(str);
        this->setFixedSize(pixmap.width(),pixmap.height() );
        this->setStyleSheet("QPushButton{border:0px;}");
        this->setIcon(pixmap);
        this->setIconSize(QSize(pixmap.width(),pixmap.height()));
        if(this->max < this->min) //如果小于最小值,重置最大值,并停止定时器
        {
            this->max = 8;
            timer2->stop();
        }
    });

5.9.3 测试

监听每个按钮的点击效果,并翻转金币

connect(coin,&MyCoin::clicked,[=](){
                //qDebug() << "点击的位置: x = " <<  coin->posX << " y = " << coin->posY ;
                coin->changeFlag();
                gameArray[i][j] = gameArray[i][j] == 0 ? 1 : 0; //数组内部记录的标志同步修改
            });

在这里插入图片描述

5.9.4 禁用按钮

此时,确实已经可以执行翻转金币代码了,但是如果快速点击,会在金币还没有执行一个完整动作之后 ,又继续开始新的动画,我们应该在金币做动画期间,禁止再次点击,并在完成动画后,开启点击。

在MyCoin类中加入一个标志 isAnimation 代表是否正在做翻转动画。
bool isAnimation  = false; //做翻转动画的标志

在MyCoin做动画期间加入

this->isAnimation  = true;

也就是changeFlag函数中将标志设为true
加入位置如下:
在这里插入图片描述
并且在做完动画时,将标志改为false
在这里插入图片描述
重写按钮的按下事件,判断如果正在执行动画,那么直接return掉,不要执行后续代码。

代码如下:

void MyCoin::mousePressEvent(QMouseEvent *e)
{
    if(this->isAnimation )
    {
        return;
    }
    else
    {
        return QPushButton::mousePressEvent(e);
    }
}

5.10 翻周围金币

将用户点击的周围 上下左右4个金币也进行延时翻转,代码写到监听点击金币下。
此时我们发现还需要记录住每个按钮的内容,所以我们将所有金币按钮也放到一个二维数组中,在.h中声明

MyCoin * coinBtn[4][4]; //金币按钮数组

并且记录每个按钮的位置

coinBtn[i][j] = coin;

在这里插入图片描述
延时翻动其他周围金币

QTimer::singleShot(300, this,[=](){
                    if(coin->posX+1 <=3)
                    {
                      coinBtn[coin->posX+1][coin->posY]->changeFlag();
                      gameArray[coin->posX+1][coin->posY] = gameArray[coin->posX+1][coin->posY]== 0 ? 1 : 0;
                    }
                    if(coin->posX-1>=0)
                    {
                      coinBtn[coin->posX-1][coin->posY]->changeFlag();
                      gameArray[coin->posX-1][coin->posY] = gameArray[coin->posX-1][coin->posY]== 0 ? 1 : 0;
                    }
                    if(coin->posY+1<=3)
                    {
                     coinBtn[coin->posX][coin->posY+1]->changeFlag();
                     gameArray[coin->posX][coin->posY+1] = gameArray[coin->posX+1][coin->posY]== 0 ? 1 : 0;
                    }
                    if(coin->posY-1>=0)
                    {
                     coinBtn[coin->posX][coin->posY-1]->changeFlag();
                     gameArray[coin->posX][coin->posY-1] = gameArray[coin->posX+1][coin->posY]== 0 ? 1 : 0;
                    }
                });

5.11 判断是否胜利

在MyCoin.h中加入 isWin标志,代表是否胜利。

  bool isWin = true; //是否胜利

默认设置为true,只要有一个反面的金币,就将该值改为false,视为未成功。
代码写到延时翻金币后 进行判断

            //判断是否胜利
            this->isWin = true;
            for(int i = 0 ; i < 4;i++)
            {
                for(int j = 0 ; j < 4; j++)
                {
                    //qDebug() << coinBtn[i][j]->flag ;
                    if( coinBtn[i][j]->flag == false)
                    {
                        this->isWin = false; //只要有一个false代表没有胜利
                        break;
                    }
                }
            }

如果isWin依然是true,代表胜利了!

if(this->isWin)
       {
              qDebug() << "胜利";
       }

5.12 胜利图片显示

将胜利的图片提前创建好,初始放在屏幕的上方,用户是无法看到的,如果胜利触发了,将图片弹下来即可

 QLabel* winLabel = new QLabel;
    QPixmap tmpPix;
    tmpPix.load(":/res/LevelCompletedDialogBg.png");
    winLabel->setGeometry(0,0,tmpPix.width(),tmpPix.height());
    winLabel->setPixmap(tmpPix);
    winLabel->setParent(this);
    winLabel->move( (this->width() - tmpPix.width())*0.5 , -tmpPix.height());

如果胜利了,将上面的图片移动下来,使用动画实现

if(this->isWin)
    {
       qDebug() << "胜利";
        QPropertyAnimation * animation1 =  new QPropertyAnimation(winLabel,"geometry");
                        animation1->setDuration(1000);
                        //设置起始位置
                        animation1->setStartValue(QRect(winLabel->x(),winLabel->y(),winLabel->width(),winLabel->height()));
                        //设置最终位置
                        animation1->setEndValue(QRect(winLabel->x(),winLabel->y()+114,winLabel->width(),winLabel->height()));
                        animation1->setEasingCurve(QEasingCurve::OutBounce);
                        animation1->start();
                    }

5.13 胜利后禁用按钮

当胜利后,应该禁用所有按钮的点击状态,可以在每个按钮中加入标志位 isWin,如果isWin为true,MousePressEvent直接return掉即可
MyCoin中.h里添加:

bool isWin = false;//胜利标志

在鼠标按下事件中修改为

void MyCoin::mousePressEvent(QMouseEvent *e)
{
    if(this->isAnimation|| isWin == true )
    {
        return;
    }
    else
    {
        return QPushButton::mousePressEvent(e);
    }
}
//禁用所有按钮点击事件
    for(int i = 0 ; i < 4;i++)
      {
        for(int j = 0 ; j < 4; j++)
        {
          coinBtn[i][j]->isWin = true;
        }
}

测试,胜利后不可以点击任何的金币。

6 音效添加

6.1 开始音效

QSound *startSound = new QSound(":/res/TapButtonSound.wav",this);

点击开始按钮,播放音效

startSound->play(); //开始音效

6.2 选择关卡音效

在选择关卡场景中,添加音效

//选择关卡按钮音效
    QSound *chooseSound = new QSound(":/res/TapButtonSound.wav",this);

选中关卡后,播放音效

chooseSound->play();

6.3 返回按钮音效

在选择关卡场景与翻金币游戏场景中,分别添加返回按钮音效如下:

//返回按钮音效
    QSound *backSound = new QSound(":/res/BackButtonSound.wav",this);

分别在点击返回按钮后,播放该音效

backSound->play();

6.4 翻金币与胜利音效

在PlayScene中添加,翻金币的音效以及 胜利的音效

//翻金币音效
    QSound *flipSound = new QSound(":/res/ConFlipSound.wav",this);
    //胜利按钮音效
    QSound *winSound = new QSound(":/res/LevelWinSound.wav",this);

在翻金币时播放 翻金币音效

flipSound->play();

胜利时,播放胜利音效

winSound->play();

测试音效,使音效正常播放。

7 优化项目

当我们移动场景后,如果进入下一个场景,发现场景还在中心位置,如果想设置场景的位置,需要添加如下下图中的代码:
MainScene中添加:
在这里插入图片描述
ChooseScene中添加:
在这里插入图片描述
测试切换三个场景的进入与返回都在同一个位置下,优化成功。

8 打包发布

8.1 项目打包

(1)设置程序发布状态:将Debug状态修改为程序发布状态
  在这里插入图片描述
  (2)重新编译项目
  在这里插入图片描述
(3)打开项目所在文件,找到release版本的项目
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

8.2 游戏扩展

项目打包后生成的exe文件,要想使用,必须保持本机有qt的环境,因此为了扩展游戏,使得普通的电脑也可以运行程序,需要进行扩展。
  (1)新建文件夹,并且将项目打包的exe文件复制到该文件夹内

在这里插入图片描述
(2)在文件夹内打开cmd命令窗口

在这里插入图片描述
(3)首先需要保证Qt的安装位置有windeployqt.exe文件
在这里插入图片描述

使用windeployqt 命令运行exe程序:生成打包项目
在这里插入图片描述
在这里插入图片描述

8.3 游戏打包nsis安装包(更深层拓展)

将游戏更深入打包,生成压缩包:参考:参考链接

至此,本案例全部制作完成。

我自己按照博文写的项目源代码请见链接:Qt翻金币项目所有源代码

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

QT从入门到实战x篇_32_实战篇:翻金币(创建项目;场景切换;设置背景图片;按钮控件封装;跳跃特效;QTimer::singleShot();引入数据类;QMap<>;翻金币特效;插入音效;打包) 的相关文章

  • 带 Qt 的菜单栏/系统托盘应用程序

    我是 Qt PyQt 的新手 我正在尝试制作一个应用程序 其功能将从菜单栏 系统托盘执行 这里展示了一个完美的例子 我找不到关于如何做到这一点的好资源 有人可以建议吗 Thanks 我认为您正在寻找与QMenu and QMainWindo
  • 如何在Qt无框窗口中实现QSizeGrip?

    如何使用 Qt 无框窗口实现 QSizeGrip 代码会是什么样的 您只需在布局内窗口的一角添加 QSizeGrip 即可使其保持在该角落 QDialog dialog new QDialog 0 Qt FramelessWindowHin
  • 如何在qt中进行异步文件io?

    我想知道如何在qt中实现异步文件io 这在普通的 qt 中是否可以实现 或者有人需要使用另一个库 例如 libuv 来实现这样的事情 我正在查看 QDataStream 但即使它是一个 流 它也不是非阻塞的 我想一种解决方案是制作一个在内部
  • 如何将 QByteArray 转换为十六进制字符串?

    我有打击QByteArray QByteArray ba ba 0 0x01 ba 1 0x10 ba 2 0x00 ba 3 0x07 我真的不知道如何将此 QByteArray 转换为结果字符串 其中包含 01100007 我将使用 Q
  • 无法在 QGLWidget 中设置所需的 OpenGL 版本

    我正在尝试在 Qt 4 8 2 中使用 QGLWidget 我注意到 QGLWidget 创建的默认上下文不显示 OpenGL 3 1 以上的任何输出 Qt wiki 有一个教程 http qt project org wiki How t
  • 获取 QPushButton 在 2D 数组 QPushButton 上的索引

    我有一个二维数组QPushButton 当用户单击按钮时如何获取按钮的索引 例如当用户单击按钮时a 2 3 它会显示 2 3 该示例如下所示 Qt 4 5 使用对象名称 您可以为按钮指定唯一的对象名称 理想情况下 名称应该是有效的 C 标识
  • QML:无法读取未定义的属性“xxx”

    ApplicationWindow id root property string rootName rootName visible true width 800 height 400 title qsTr WatchFace Maker
  • 如何在 Qt Creator 中编辑 QtWebKit 的右键上下文菜单?

    好吧 这是我的困境 我正在使用 Qt Creator 制作一个使用 Webkit 的简单应用程序 我认为 Qt Creator 会有一种简单的方法来使用信号和槽编辑器编辑右键单击上下文菜单 但事实证明这不是真的 我知道 webkit 有与上
  • 在 Qt 服务器上验证用户身份

    我正在尝试使用 C QtTcpSocket 为个人项目 多人国际象棋游戏 实现身份验证系统 我的朋友建议了一种验证用户的方法 但我想问是否有更简单或更好的方法 来自 Python 背景 做这个项目主要是为了加深对 C 的理解 我将发布我朋友
  • QMutex 是否需要是静态的,以便此类实例的其他线程调用知道暂停其操作?

    从多个线程调用以下附加函数 我不希望数据重写附加 因为计数器尚未增加 除了当前使用 Append 的线程之外 这是否会挂起所有进入的线程 或者其他线程会继续运行而不追加数据吗 互斥锁是否需要是 静态 的 或者每个实例都知道要暂停操作吗 如果
  • 获取 QListView 中所有可见项目的简单方法

    我正在尝试使用 Qt Framework 开发一个图像库应用程序 应用程序从所选文件夹加载所有图像 并使用 QListView 控件显示这些图像 但现在我想通过仅加载用户可见的图像来减少内存消耗 由于没有直接函数来获取视图中的所有可见项目
  • 如何在QT上暂停和重新启动Qtimer

    我有 Ubuntu 我正在使用 IDEQT on C 我将暂停和恢复计时器 例如 void Ordonnancer les taches on pushButton clicked connect dataTimer SIGNAL time
  • Retina 显示屏中具有 QOpenGLWIdget 的 Qt MainWindow 显示错误大小

    我有一个 Qt 应用程序MainWindow 我嵌入一个QOpenGLWidget在里面 一切正常 直到我开始使用 Apple Retina 显示屏并在高 DPI 模式下运行我的应用程序 我的QOpenGLWidget只是它应该具有的大小的
  • QMainWindow 上的 Qt 布局

    我设计了一个QMainWindow with QtCreator s设计师 它由默认的中央小部件 aQWidget 其中包含一个QVBoxLayout以及其中的所有其他小部件 现在我想要的一切就是QVBoxLayout自动占据整个中央小部件
  • 如何在Qt 5中的paintEvent上使用mouseMoveEvent?

    我是 Qt 和 c 的新手 所以我遇到了一些困难 我正在尝试创建一个小部件 它可以获取 mouseMoveEvent 位置并在鼠标位置的像素图上绘制椭圆 下面你可以看到代码 include myimage h include
  • 如何声明一个带有成员函数指针的函数

    我有一个类 其中的成员变量指向库对象 class myClassA private libraryClass libraryObject 该库类发出事件 以字符串为特征 并提供一种机制 允许客户端类指定在发出事件时应调用的成员函数 因此在m
  • PyQt4 QPalette 不工作

    btn QtGui QPushButton Button self palettes btn palette palettes setColor btn backgroundRole QtCore Qt green btn setPalet
  • 是否有 Qt 小部件可以浏览应用程序中小部件的层次结构(类似于 Spy++)?

    我们有一个具有复杂的小部件层次结构的应用程序 我希望能够以与 Spy 类似的方式浏览此层次结构 查看和编辑属性 例如大小 如果有一个小部件可以显示此信息 则它不需要在外部应用程序中运行 那么问题来了 这样的神兽存在吗 您可以使用Gammar
  • 覆盖 QWebView 中的页面回复

    我试图在 Qt 的 QWebView 中拦截页面 表单请求 并在某些情况下使用替代内容进行响应 QNetworkReply ngcBrowser createRequest Operation operation const QNetwor
  • 如何将自定义 Qt 类型与 QML 信号一起使用?

    我在 Qt 5 2 qml 应用程序中创建了一个自定义类型 class Setting public QObject Q OBJECT Q PROPERTY QString key READ key WRITE setKey Q PROPE

随机推荐

  • python中去除字符串中 表示的空格

    去掉 nbsp 硬空格 必须在unicode下替换才行 如下所示 text replace u xa0 其中text就是包含 nbsp 的一个变量
  • Unity3D中API常用方法和类详细讲解 (Transform类)

    目录 Transform类 点击这里进入官网 该类表示的是对象的位置 旋转和缩放 Properties Transform parent public Transform parent 让一个游戏物体成为另一游戏物体的子对象 那么该物体的
  • 计算机考研复试上机算法学习

    计算机考研复试上机算法学习 这篇博客是博主在准备可能到来的线下上机复试基于王道机试指南的学习 将各道习题链接和代码记录下来 这篇博客权且当个记录 文章目录 计算机考研复试上机算法学习 1 STL容器学习 1 1 vector动态数组 1 1
  • mongoDB 一些操作命令

    如果你想创建一个 myTest 的数据库 先运行use myTest命令 之后就做一些操作 如 db createCollection user 这样就可以创 建一个名叫 myTest 的数据库 一 数据库常用命令 1 Help查看命令提示
  • 微信小程序调用微信支付

    1 首先肯定是要去微信公众平台申请接入微信支付 2 申请成功之后就可以调用商户号的接口进行微信支付交易了 3 携带的参数肯定是从后端接口拿取的 我们回调的时候直接拉起支付就可以了 wx showLoading title 处理中 调用后端接
  • 将docker容器设置为宿主机同一网段

    本文主要讲述 将docker的容器ip设置为宿主机同一网段 并且允许宿主机以及局域网其它机器访问它 创建docker的虚拟网络 本人局域网的网段为192 168 1 0 24 网关为路由器的192 168 1 1 docker networ
  • STM32F103ZET6【标准库函数开发】------09 高级定时器TIM1输出7个PWM,三对为互补PWM

    只有高级定时器可以输出互补的PWM 所以只有TIM1和TIM8可以实现这个功能 而TIM1又分为三种情况没有重映射 部分重映射 完全重映射 一 没有重映射 下面展示主要的time c main c函数的代码 void TIM1 PWM In
  • 机器人路径规划的算法有很多种,其中RRT算法是其中一种比较流行的算法之一

    机器人路径规划的算法有很多种 其中RRT算法是其中一种比较流行的算法之一 在这篇文章中 我们将为大家介绍如何使用Matlab实现基于RRT算法的机器人最短路径规划 并附上相应的源代码 我们首先需要明确RRT算法的基本思路 RRT全称为Rap
  • Android——Binder机制

    1 简介 Binder是什么 机制 Binder是一种进程间通信的机制 驱动 Binder是一个虚拟物理设备驱动 应用层 Binder是一个能发起进程间通信的JAVA类 Binder就是Android中的血管 在Android中我们使用Ac
  • Java设计模式之七大设计原则

    Java设计模式之七大设计原则 本文对Java设计模式中的七大设计原则进行汇总介绍 提炼最核心的概念 设计模式总结笔记 一 设计模式七大原则 设计模式的目的 代码可重用性 相同功能的代码 不用多次编写 可读性 编程规范性 便于其他程序员的阅
  • 基于TCP协议实现HTTP_GET请求

    前言 之前一直使用MQTT的物联网协议 偶然间发现互联网中HTTP的通信协议也应用广泛 想要更好的理解这个协议 可以基于tcp来实现这个协议 这样可以更理解底层组包结构 http与mqtt类似都是基于tcp udp 的基础上规范了传输的报文
  • 看完这篇,轻松get限流!

    引言 本文推选自 技思广益 腾讯技术人原创集 专栏 该专栏是腾讯云开发者社区为腾讯技术人与广泛开发者打造的分享交流窗口 栏目邀约腾讯技术人分享原创的技术积淀 与广泛开发者互启迪共成长 作者是腾讯云开发者社区的作者 一只小黄鱼 限流在确保现代
  • 记自动调参平台raytune和chemprop的一次实验

    首先介绍一下raytune这个东西 了解机器学习深度学习的朋友应该知道调参是机器学习中必不可少的一个环节 当你的模型被设计出来之后 或者你使用别人现成的模型的时候 你是要去调整模型的一个超参数从而是模型在你的数据集上达到一个比较好的效果的
  • MyBatis Plus 拦截器实现数据权限控制(完整版)

    一 说明 变化 相比于之前写的数据权限拦截器 新增了白名单功能 通过注解的方式让哪些SQL不进行数据权限拦截 之前的文章地址 思路 通过MyBatisPlus的拦截器对每个要执行的SQL进行拦截 然后判断其是否为查询语句 如果是查询语句 则
  • TraceWatch等10个强大开源Web流量分析工具下载地址

    10个强大开源Web流量分析工具 Web 流量分析工具多不胜数 从 WebTrends 这样专业而昂贵的 到 Google Analytics 这样强大而免费的 从需要在服务器端单独部署的 到可以从前端集成的 不一而足 本文收集并介绍了10
  • 第四十五讲:神州防火墙P2P流量控制配置

    实验拓扑图如下所示 配置要求 出口带宽 100Mbps 外网为 eth0 1 接口 内网连接两个网段172 16 1 0 24 和 192 168 1 0 24 需限制 P2P 应用其下行带宽为 10M 上传最大 5M 配置步骤 一 指定接
  • element ui + vue项目,el-select选择器,选中值后无法及时回显到页面上

    出现原因 不详 解决方法 el select 标签上绑定chang事件 强制刷新 change forceUpdate
  • Jsoniter简单的使用介绍

    2017 7 1日更新 前几天在公司做一个模块的时候想使用Jsoniter解析一个json字符串 结果发现 当字符串长度大于一定值的时候 就会抛错 查了写资料 无果 最后换成了gson 成功解析 之前一直在使用google的gson以解析j
  • Qt的MOC机制

    Qt的MOC机制 Qt扩展了C 使得开发者拥有很多方便使用的工具 如何使用Qt提供的特性呢 比如信号与槽 那就需要开发者在类中声明Q OBJECT宏 这样程序员就能使用Qt提供的功能了 为什么这样可以呢 先从C 文件的编译过程开始讲 一般C
  • QT从入门到实战x篇_32_实战篇:翻金币(创建项目;场景切换;设置背景图片;按钮控件封装;跳跃特效;QTimer::singleShot();引入数据类;QMap<>;翻金币特效;插入音效;打包)

    本篇将会根据前面讲解的关于Qt的相关内容进行一个实战项目 项目链接 翻金币案例 关于此项目比较好的博客地址如下 QT 翻金币项目 翻金币项目总结 1 项目简介 2 项目基本配置 2 1 创建项目 2 2 添加资源 3 主场景 3 1 设置游