QT实战之翻金币游戏【详细过程及介绍】

news2024/11/28 4:35:39

目录

前言

一、游戏整体分析

二、创建项目

三、添加资源

四、主界面实现

1、设置游戏主场景基本配置

2、设置背景图片

3、创建开始按钮并设置动画

4、创建关卡选择界面并实现主界面与其的切换

五、关卡选择界面实现

1、设置关卡选择场景基本配置

2、设置关卡选择场景的背景图片

3、返回按钮的相关功能设置

4、创建选择关卡按钮

5、实现点击关卡按钮跳转到对应的翻金币场景功能

六、翻金币场景实现

1、设置游戏场景基本配置

2、设置游戏场景的背景图片

3、设置返回按钮及其功能

4、在左下角显示当前关卡号

5、创建游戏界面中间的金币背景

6、创建金币类

7、引入关卡数据

8、初始化各个关卡

9、实现翻金币特效

10、实现周围金币跟着翻转功能

11、判断是否胜利

12、显示胜利图片

13、胜利后禁用按钮

七、添加音效

八、优化项目

1、切换三个场景的进入与返回都在同一个位置下

2、在游戏胜利瞬间点了其他金币使得未胜利(速度过快)

九、打包项目​​​​​​​


前言

对QT的相关知识与控件进行简单的学习之后,通过实现“翻金币游戏”来巩固与实践所学的QT知识。在制作过程中是根据以下视频的教程进行制作的。感兴趣的可以移步视频。

02 案例简介_哔哩哔哩_bilibili

制作翻金币游戏的图片及音频都是直接利用课程提供的的现有资料,如果在课程没有找到的话,可以通过以下链接下载对应的资料。

链接:https://pan.baidu.com/s/1IxWIa47V0L_WuLu41pu7Tw 
提取码:lje1

本文将从头到尾循序渐进地对“CoinFlip”游戏的实现进行介绍,会相对详细一点,主要从整体分析、项目创建、项目实现、项目打包这几个方面进行介绍。

打包好的游戏链接如下,可以直接下载后运行就可以玩了。

链接:https://pan.baidu.com/s/1Nfzstm1AbsmwfKPL_ac4_Q 
提取码:rqa6

添加自己创建的头文件使用以下形式添加

#include "xxxx.h"


一、游戏整体分析

这个小游戏总的来看需要我们实现的有三个界面还有两个自定义控件

       一是游戏开始界面,对应图1;其中,开始界面需要一个开始按钮,能够进行页面切换,因而我们需要自定义一个控件,也就是MyPushButton,父类为QPushButton,进而实现这一按钮的设计与动作实现。

        二是关卡选择界面,对应图2;其中,关卡按钮还有返回按钮back也是利用我们自定义的控件来实现。

        三是关卡界面,对应图3。其中,因为游戏规则是点击银币然后上下左右的硬币跟着翻转,需要利用多个图片来实现金币翻转,因此我们需要创建一个金币类来实现金币所需要进行的对应操作。

        而图4只是在玩家胜利之后在关卡界面的基础上弹出成功图片,因而不需要另外创建界面。三个界面就对应三个类,分别是mainscene、chooselevelscene、playscene;自定义的类有两个,分别是mypushbutton、mycoin类。


二、创建项目

打开QT Creator(我用的是5.12.9版本)-->创建新项目-->选择Qt Widgets Application-->下一步

设置项目名称“CoinFlip”-->选择存放路径-->下一步

从主界面开始实现,设置类名为“MainScene”-->选择父类为“QMainWindow”-->勾选“Generate form”-->下一步

然后项目创建成功,项目结构如下:


三、添加资源

将以下资源添加到项目中

将前言中准备好的资料下载保存到电脑桌面-->点击项目右键选择“Add New”-->选择“QT”-->选择“QT Resource File”-->下一步-->设置名称“res”-->下一步-->完成

点击完成后会出现如下界面,点击“Add Prefix”-->将前缀设置为“/”即可。

(test是为了方便截图添加资源等操作,不用在意这个)

然后将前面保存的资料里面的res文件夹,复制粘贴到CoinFlip项目中(也就是前面创建项目时你选择的路径)【忽略test,不是重点】

回到项目界面,点击“Add Files”-->选择刚才添加的"res"文件-->选中文件夹中所有文件-->点击打开-->成功添加资源

添加成功后如下:


四、主界面实现

1、设置游戏主场景基本配置

首先我们利用ui界面设计来实现菜单栏的设计——双击“mainscene.ui”打开界面设计——界面如下

设计菜单栏——在中间上方“在这里输入”这里输入“开始”,然后点击“开始”,在下方的“在这里输入”,输入“Quit”,然后再找到其text属性将其改成退出。

【之所以先输入Quit是因为控件名称是自动生成的,总不能生成"action退出"】

然后移除自带的状态栏,然后就可以回到"mainscene.cpp",在构造函数进行代码的编写

实现场景的基本配置,设置窗口大小、应用的图标、还有窗口的标题,然后实现退出的功能实现,利用信号和lambda表达式来实现

代码如下:

//设置固定大小
this->setFixedSize(350,550);
//设置应用图片
this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
//设置窗口标题
this->setWindowTitle("一起来翻金币吧");
//点击退出,退出程序
connect(ui->actionQuit,&QAction::triggered,[=](){
    this->close();
});

运行界面结果如下:

2、设置背景图片

需要重写MainScene的PaintEvent事件,先在“mainscene.h”中添加声明,然后在“mainscene.cpp”中实现背景图片的添加。

【注:PaintEvent函数在项目运行时会自动调用生成,因此只需要重写而不需要在cpp中调用】

代码如下:

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、创建开始按钮并设置动画

      需要实现以下场景——开始按钮点击后有弹跳效果

这个效果我们利用自定义控件实现(因为QPushButton不会自带这类特效),我们可以自己封装出一个按钮控件,来实现这些效果。此时需要我们创建一个新的类——MyPushButton

     选择项目右键,选择“Add New”-->选择“C++”-->选择“C++ Class”-->下一步-->设置类名“MyPushButton”-->选择父类“QWidget”-->下一步-->完成

创建成功后项目结构如下:

分别打开“mypushbutton.h”和“mypushbutton.cpp”修改其继承的父类为QPushButton

修改后如下:

我们希望点击按钮之后能够看到按钮的变化,即可以让MyPushButton提供正常显示的图片以及按下后显示的图片,比如上下跳跃或者变成被按下的样子,因此我们需要重写MyPushButton类的构造函数,使得它能够接受两个参数,分别是正常显示的图片的路径以及按下后要显示的图片路径。

先在mypushbutton.h文件中对重载的构造函数进行声明以及定义两个成员变量,分别用来存放对应图片的路径。

代码如下:

    //normalImg 代表正常显示的图片
    //pressImg  代表按下后显示的图片,默认为空
    MyPushButton(QString normalImg,QString pressImg = "");
private:
    QString normalImgPath;  //默认显示图片路径
    QString pressedImgPath; //按下后显示图片路径

然后就是在“mypushbutton.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());
    //设置不规则图片的样式表
    this->setStyleSheet("QPushButton{border:0px;}");
    //设置图标
    this->setIcon(pixmap);
    //设置图标大小
    this->setIconSize(QSize(pixmap.width(),pixmap.height()));
}

前面两步做好之后,我们就可以在主界面上添加开始按钮,打开“mainscene.cpp”

代码如下:

//利用自定义控件定义开始按钮,同时设置其图标  这里就运用到了重载的构造函数
MyPushButton * startBtn = new MyPushButton(":/res/MenuSceneStartButton.png");
//将按钮的父亲设置为this,也就是添加到对象树上,这样当窗口析构时其也会被析构
startBtn->setParent(this);
//设置开始按钮在主界面的位置 也就是x,y
startBtn->move(this->width()*0.5-startBtn->width()*0.5,this->height()*0.7);

运行界面结果如下:

此时的开始按钮点击没有任何特效,因此我们需要添加点击后上下跳跃的特效,我们定义两个函数zoom1()和zoom2(),分别用来实现向下跳跃和向上跳跃,这两个函数放在mypushbutton类中实现。

先在mypushbutton.h中添加声明

在mypushbutton.cpp中添加实现代码,两个函数代码基本一致,只有位置有不同

代码如下:

//向下跳跃
void MyPushButton::zoom1()
{
    //创建动画对象
    QPropertyAnimation * animation1 = new QPropertyAnimation(this,"geometry");
    //设置时间间隔,单位毫秒
    animation1->setDuration(200);
    //创建起始位置
    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()));
    //设置缓和曲线,QEasingCurve::OutBounce 为弹跳效果
    animation1->setEasingCurve(QEasingCurve::OutBounce);
    //开始执行动画
    animation1->start();
}

然后到“mainscene.cpp”中连接信号槽,实现点击开始按钮后按钮上下跳动的效果

代码如下:

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

此时点击运行项目,点击开始按钮就可以看到上下跳跃的效果。

【到这一步主场景的创建已经基本实现了,接下来就是创建第二个界面——关卡选择,同时实现点击开始按钮后跳转到关卡选择界面】

4、创建关卡选择界面并实现主界面与其的切换

同创建自定义控件一样,我们创建关卡选择界面需要创建一个新的类——“ChooseLevelScene”

  选择项目右键,选择“Add New”-->选择“C++”-->选择“C++ Class”-->下一步-->设置类名“ChooseLevelScene”-->选择父类“QMainWindow”-->下一步-->完成

创建成功后项目结构如下:

然后就是实现点击开始按钮进入选择关卡场景的功能。因此我们需要在“mainscene.h”中添加关卡场景的对象,以此来实现在点击按钮之后,通过"chooseScene->show()"来实现界面切换。

代码如下:

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

然后就是在“mainscene.cpp”中点击开始按钮后的lambda表达式中添加代码实现界面切换。在调用zoom2()后面添加即可。

代码如下:

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

运行程序,执行特效后延时0.5秒进入选择关卡场景,结果如下:(因为关卡选择界面还未进行设置,因此是空白界面)


五、关卡选择界面实现

1、设置关卡选择场景基本配置

直接在“chooselevelscene.cpp”中的构造函数中去设置界面的配置,如大小、菜单栏等等

代码如下:

//设置窗口固定大小
this->setFixedSize(350,550);
//设置图标
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();});

此时运行结果如下:

2、设置关卡选择场景的背景图片

跟主场景一样,需要重写paintEvent函数,先在“chooselevelscene.h”中进行声明,然后在“chooselevelscene.cpp”中进行重写

代码如下:

void ChooseLevelScene::paintEvent(QPaintEvent *)
{
    //创建画家,制定绘图设备
    QPainter painter(this);
    //创建QPixmap对象
    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);
}

背景图片设置完成后,我们来设置右下角的返回按钮,在“chooselevelscene.cpp”中运用自定义控件“mypushbutton”来创建返回按钮。

代码如下:

//返回按钮
    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,从而实现不同的显示效果。

先在“mypushbutton.h”中进行声明,然后在“mypushbutton.cpp”中实现

代码如下:

//鼠标事件
void MyPushButton::mousePressEvent(QMouseEvent *e)
{
    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);
}

运行界面,结果如下:

3、返回按钮的相关功能设置

我们点击返回按钮后,设置延时0.5秒后隐藏自身,并且发送自定义的信号“chooseSceneBack()”,告诉外界自身已经选择了返回按钮。

我们先在“chooselevelscene.h”中声明自定义信号(但不需要实现)

然后在“chooselevelscene.cpp”中添加返回按钮的功能:

代码如下:

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

在主场景MainScene中我们添加功能——点击开始按钮显示选择关卡的同时,监听选择关卡的返回按钮消息,接受到返回按钮消息的话则展示主场景界面。在“mainscene.cpp”中添加代码

代码如下:

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

此时运行项目,点击开始按钮切换到关卡选择场景,点击back按钮返回主场景。

4、创建选择关卡按钮

现在我们需要实现下图中20个关卡的按钮,直接在“chooselevelscene.cpp”中添加代码,代码还是添加在构造函数中。

代码如下:

//创建关卡按钮
    for(int i = 0 ; i < 20;i++)
    {
        //运用自定义控件来生成关卡按钮
        MyPushButton * menuBtn = new MyPushButton(":/res/LevelIcon.png");
        menuBtn->setParent(this);
        //设置每个关卡按钮的位置  根据个人喜好还有前面设置的场景大小来设置
        menuBtn->move(40 + (i%4)*70 , 135+ (i/4)*70);

        //按钮上显示的文字
        QLabel * label = new QLabel;
        label->setParent(this);
        label->setFixedSize(menuBtn->width(),menuBtn->height());
        label->setText(QString::number(i+1));//显示关卡数字
        label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); //设置居中
        label->move(40 + (i%4)*70 , 135+ (i/4)*70);//让数字在按钮上显示
        label->setAttribute(Qt::WA_TransparentForMouseEvents,true);  //鼠标事件穿透
    }

5、实现点击关卡按钮跳转到对应的翻金币场景功能

同前面的场景创建一样,我们需要重新定义一个类——PlayScene,用来创建翻金币场景,总共有20关,但不需要创建20个类,只需要一个即可,不同关卡利用代码实现即可。

选择项目右键,选择“Add New”-->选择“C++”-->选择“C++ Class”-->下一步-->设置类名“PlayScene”-->选择父类“QMainWindow”-->下一步-->完成

创建成功够项目结构如下:

因为我们需要在关卡选择场景点击对应按钮然后跳转到对应的游戏界面,因此我们可以通过在关卡选择场景“chooselevelscene.h”中添加成员变量pScene用来指向不同关卡对应的游戏界面。

然后在关卡选择场景“chooselevelscene.cpp”中利用信号和槽,监听所选择的关卡并使得上面定义的场景指针指向对应的场景。此时new PlayScene(i+1)会报错,因为我们没有重写创建场景的构造函数(下一部分实现)。


六、翻金币场景实现

1、设置游戏场景基本配置

创建对应的关卡场景,需要接受关卡选择界面传递过来的关卡号,因此我们在“playscene.h”中定义变量接收关卡号,同时声明重载构造函数

然后在“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();});
}

2、设置游戏场景的背景图片

跟前面一样,需要重写paintEvent函数,先在“playscene.h”中进行声明,然后在“playscene.cpp”中进行重写

代码如下:

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);
}

此时运行项目,点击任意关卡进入,游戏场景如下:

3、设置返回按钮及其功能

与关卡选择场景一样,我们需要设置其按下的特效,还有就是自定义返回信号给关卡选择场景用于捕捉,一旦捕捉到则返回关卡选择场景。

先在“playscene.h”中声明自定义信号,然后在“playscene.cpp”中创建返回按钮并监听返回按钮

代码如下:

//返回按钮
    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();
         }
        );
      });

然后在关卡选择场景中设置监听palyscene的返回信号,在“chooselevelscene.cpp”中添加代码

代码如下:

//PlayScene的返回按钮监听,删除该scene并将指针置为空
connect(pScene,&PlayScene::chooseSceneBack,[=](){
      this->setGeometry(pScene->geometry());
      this->show();
      delete pScene;
      pScene = NULL;
});

4、在左下角显示当前关卡号

在“palyscene.cpp”中添加代码来显示关卡号

代码如下:

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

此时运行程序,随意选择关卡可看到左下角关卡号,结果如下:

5、创建游戏界面中间的金币背景

在“playscene.cpp”中添加代码

代码如下:

//创建金币的背景图片
    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(1).png"));
            label->setParent(this);
            label->move(70 + i*50,200+j*50);//所在位置,对应x,y
        }
    }

此时运行项目,选择任意关卡,游戏界面如下:

6、创建金币类

我们在创建好金币背景之后,则需要把金币添加到对应位置上。而金币它需要能被点击,然后点击后还有翻转特效,因此我们可以单独将其封装成一个金币类方便使用与创建。

 同前面的场景创建一样,我们需要重新定义一个类——MyCoin

选择项目右键,选择“Add New”-->选择“C++”-->选择“C++ Class”-->下一步-->设置类名“MyCoin”-->选择父类“QWidget”-->下一步-->完成

创建好后项目结构如下:

然后修改MyCoin类的父类为QPushButton

在资源图片中,我们可以看到,金币翻转的效果是利用多张图片切换而形成的,而以下八张图片中,正常显示的图片为金币Coin0001或者是银币 Coin0008这两种图。因此我们在创建一个金币对象时候,应该提供一个参数,代表着传入的是金币资源路径还是银币资源路径,根据路径我们创建不同样式的图案。所以我们要重写金币的构造函数。

先在“mycoin.h”中声明重载构造函数,然后在“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()));

}

然后我们去游戏场景中添加金币

代码如下:

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

此时运行项目,选择任意关卡,游戏界面如下:

7、引入关卡数据

每个关卡的初始化界面都不一样,因此需要我们引用一个现有的关卡文件,文件中记录了各个关卡的金币排列清空,也就是二维数组的数值。(文件在前言中有提供,就是dataconfig.h和dataconfig.cpp文件)

首先先将dataConfig.h 和 dataConfig.cpp文件放入到当前项目下:

选择项目右键,选择“添加现有文件”-->分别选择两个文件添加

添加成功后项目结构如下:

8、初始化各个关卡

首先,我们在playscene.h中声明一个成员变量,用于记录当前关卡的二维数组。然后在playscene.cpp中初始化这个二维数组,记得添加头文件“dataconfig.h”。添加代码的位置可以自己选择,可以放在前面,也可以跟我一样放

代码如下:

 //初始化二维数组
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的坐标,以及当前的正反标志。(正反标志是用于判断该图片是金币还是银币)

然后对金币进一步进行初始化,前面的添加金币仅仅只是添加金币,看金币是否创建成功,现在就是对关卡进行初始化,有金币也有银币

代码如下:

 //金币对象
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(72 + i*50,204+j*50);
coin->posX = i; //记录x坐标
coin->posY = j; //记录y坐标
coin->flag =gameArray[i][j]; //记录正反标志

此时运行项目,任意点击关卡,可以看到游戏界面已经初始化成功,结果如下:

9、实现翻金币特效

关卡的初始化完成后,下面就实现点击金币进行翻转的效果,我们在MyCoin类中实现该方法。先在“mycoin.h”中进行声明,然后在“mycoin.cpp”中实现翻转方法,同时在构造函数中创建定时器

代码如下:

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

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

设置好了之后我们就要在构造函数中实现金币和银币的翻转效果,代码添加在刚才设置的定时器后面就可以了

代码如下:

//监听正面翻转的信号槽
        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();
            }
        });

接着我们监听每个按钮的点击效果,并翻转金币,查看翻转特效是否成功(此时只是测试,因此只有点击的金币会进行翻转)

代码如下:

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

此时运行项目,任意选择关卡,点击任意金币,查看特效:

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

我们可以在MyCoin类中加入一个标志 isAnimation 代表是否正在做翻转动画。并且在changFlag()函数中将标志设为true,在做完动画后修改为false

同时我们重写按钮的按下事件,判断如果正在执行动画,那么直接return掉,不要执行后续代码。先在“mycoin.h”声明,然后在“mycoin.cpp”实现

代码如下:

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

10、实现周围金币跟着翻转功能

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

然后设置延时翻动其他周围金币,代码添加在“playscene.cpp”中

代码如下:

//翻转周围硬币
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;
      }
});

11、判断是否胜利

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

默认设置为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;
            break;
        }
     }
}

然后进行总的判断

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());

然后就是在判断胜利之后弹出此图片

代码如下:

//如果isWin依然是true,代表胜利了!
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();
}

13、胜利后禁用按钮

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

修改MousePressEvent中的代码

在判断胜利后禁用所有金币

代码如下:

 //将所有按钮的胜利标志改为true,即禁用
for(int i = 0 ; i < 4;i++)
{
    for(int j = 0 ; j < 4; j++)
    {
        coinBtn[i][j]->isWin = true;
    }
}

七、添加音效

在“mainscene.cpp”中添加开始音效

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

在后面添加上“multimedia”,然后重新构建一下即可

播放音效

在“chooselevelscene.cpp”中添加选择关卡音效

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

在“chooselevelscene.cpp”还有“playscene.cpp”中分别添加返回按钮音效

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

在playscene.cpp中添加,翻金币的音效以及胜利的音效

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


八、优化项目

1、切换三个场景的进入与返回都在同一个位置下

当我们移动场景后,如果进入下一个场景,发现场景还在中心位置,如果想设置场景的位置,需要添加如下下图中的代码:

在MainScene.cpp中添加:

在ChooseScene.cpp中添加:

2、在游戏胜利瞬间点了其他金币使得未胜利(速度过快)

解决这一bug,只需要我们设置在点击翻转的那一瞬间把所有按钮都禁用,在周围的金币都翻转之后再恢复


九、打包项目

点击左边的“电脑”-->选中项目-->选择“release”-->然后等待构建成功,可以再运行一遍

打开项目所在路径-->找到对应结尾为Release的文件夹-->点击“release”文件夹-->复制一份exe文件

在桌面任意创建一个文件夹-->将刚才复制的CoinFlip.exe复制粘贴到里面

在开始那里找到命令窗口打开-->输入cd 文件目录 转到桌面文件夹目录下-->输入

“windeployqt CoinFlip.exe”按下回车键等待完成

这样就是完成了,此时桌面的文件夹也会多很多文件

这样可执行程序有很多依赖选项,如果需要把程序给其他人使用就需要发整个文件夹,很麻烦

因此我们把整个文件夹打包为一个单一的可执行.exe文件。

安装Enigma virtual box工具(安装过程一直next就行)

Enigma virtual box官方链接:https://enigmaprotector.com/en/downloads.html

点击Add-->Add Folder Recursive-->选中文件夹-->OK

点击Files Options-->Compress Files-->OK-->点击右下角Process生成即可

然后打开文件夹,所生成的CoinFlip_boxed.exe就是打包好的程序了,想要发给别人的就只用发这一个文件就可以了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1008558.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2-2 自动微分机制

神经网络是如何更新网络参数的呢&#xff1f; 为什么需要自动微分机制&#xff1f; 以及什么是自动微分机制&#xff1f; 神经网络通常依赖反向传播求梯度来更新网络参数&#xff0c;求梯度过程通常是一件非常复杂而容易出错的事情。而深度学习框架可以帮助我们自动地完成这种求…

PostgreSQL数据库IPC——SI Message Queue

SI Message Queue代码位于src/backend/storage/ipc/sinvaladt.c和src/backend/storage/ipc/sinval.c文件中&#xff0c;属于PostgreSQL数据库IPC进程间通信的一种方式【之前介绍过PostgreSQL数据库PMsignal——后端进程\Postmaster信号通信也是作为PostgreSQL数据库IPC进程间通…

Linux下Minio分布式存储安装配置(图文详细)

文章目录 Linux下Minio分布式存储安装配置(图文详细)1 资源准备1.1 创建存储目录1.2 获取Minio Server资源1.3 获取Minio Client资源 2 Minio Server安装配置2.1 切换目录2.2 后台启动2.3 查看进程2.4 控制台测试 3 Minio Client安装配置3.1 切换目录3.2 移动mc脚本3.2 运行mc命…

记录一次LiteFlow项目实战

文章目录 学习LiteFlowspring boot整合LiteFlow依赖配置组件定义spring boot配置文件规则文件的定义 执行 组件EL规则串行并行 动态构建组件动态构建chain&#xff08;流程&#xff09;销毁chain高级特性 题外话&#xff1a; 最近喜欢上骑摩托车了&#xff0c;不是多大排量的摩…

C++ if...else 语句

一个 if 语句 后可跟一个可选的 else 语句&#xff0c;else 语句在布尔表达式为假时执行。 语法 C 中 if…else 语句的语法&#xff1a; if(boolean_expression) {// 如果布尔表达式为真将执行的语句 } else {// 如果布尔表达式为假将执行的语句 }如果布尔表达式为 true&…

联想拯救者Lenovo Legion Y7000 2020H(81Y7)原厂Win10系统

链接&#xff1a;https://pan.baidu.com/s/15GwrfjvhK3-_OlhkfPyUbA?pwd4v03 系统自带所有驱动、出厂主题壁纸LOGO、Office办公软件、联想电脑管家等预装程序 所需要工具&#xff1a;16G或以上的U盘 文件格式&#xff1a;ISO 文件大小&#xff1a;10.7GB 注&#xff1a;恢复…

用vb语言编写一个抄底的源代码程序实例

以下是一个基于通达信软件编写的简单抄底源代码程序&#xff0c;用于自动识别股票的底部形态并发出买入信号&#xff1a; vbs 复制 导入通达信软件自带的股票数据接口 Dim TdxApi Set TdxApi CreateObject("TdxApi.TdxLocalAPI") 设置股票代码、周期、数据类型等参…

算法通关村第十九关:白银挑战-动态规划高频问题

白银挑战-动态规划高频问题 1. 最少硬币数 LeetCode 322 https://leetcode.cn/problems/coin-change/description/ 思路分析 尝试用回溯来实现 假如coins[2,5,7]&#xff0c;amount27&#xff0c;求解过程中&#xff0c;每个位置都可以从[2,5,7]中选择&#xff0c;因此可以…

深入理解JVM虚拟机第六篇:内存结构与类加载子系统概述

文章目录 一&#xff1a;内存结构概述 1&#xff1a;运行时数据区 2&#xff1a;运行时数据区简图 3&#xff1a;运行时数据区详细图中英文版 二&#xff1a;类加载器子系统 1&#xff1a;加载 2&#xff1a;连接 3&#xff1a;初始化 一&#xff1a;内存结构概述 1…

CAN基础概念

文章目录 目的控制器、收发器、总线帧格式CAN2.0和CAN-FD波特率与采样点工作模式总结 目的 CAN是非常常用的一种数据总线&#xff0c;被广泛用在各种车辆系统中。大多数时候CAN的控制器和收发器干了比较多的工作&#xff0c;从而对于写代码使用来说比较简单。这篇文章将对CAN使…

经历网数据库共享

经历网&#xff0c;为留住您的经历而生 点击 经历网 进入网站查看当前数据 经历网网址&#xff1a;https://www.jili20.com/ 以下 数据库 数据 截止至 2023年9月13日 1&#xff09;百度网盘 提取 链接&#xff1a;https://pan.baidu.com/s/1WwR4cI9lbSAYTuffo8qmVQ 或点击 此…

微信小程序的在线课外阅读打卡记录系统uniapp

本文从管理员、学生和教师的功能要求出发&#xff0c;中学课外阅读记录系统中的功能模块主要是实现学生、教师、阅读任务、阅读打卡、提醒信息、阅读排行、任务计划、阅读类型、在线考试等。经过认真细致的研究&#xff0c;精心准备和规划&#xff0c;最后测试成功&#xff0c;…

zemax畸变与消畸变

物体不同位置的放大率不同&#xff0c;产生图形变形 这里选择zemax自带的案例&#xff1a; 畸变效果&#xff1a; 明显的负畸变&#xff08;桶形畸变&#xff09; 从场曲畸变图中可以看出&#xff1a; 该系统的最大畸变大约为38% 放入图片观察成像效果&#xff1a; 优化操作数…

GpsAndMap模块开源,欢迎测评

背景 之前的文章有提到&#xff0c;最近在使用folium的过程中&#xff0c;深感对于一个非专业人员来说&#xff0c;GPS坐标以及其所隐含的GPS坐标系&#xff0c;以及不同GPS坐标系之间的相互转换关系&#xff0c;不是一个十分清晰的概念&#xff0c;往往造成在使用GPS坐标在fo…

基本的SELECT语句——“MySQL数据库”

各位CSDN的uu们好呀&#xff0c;好久没有更新小雅兰的MySQL数据库专栏啦&#xff0c;接下来一段时间&#xff0c;小雅兰都会更新MySQL数据库的知识&#xff0c;下面&#xff0c;让我们进入今天的主题吧——基本的SELECT语句&#xff01;&#xff01;&#xff01; SQL概述 SQL语…

Linux - 性能可观察性工具

文章目录 常用的Linux性能可观察性工具图解小结 常用的Linux性能可观察性工具 以下是一些常用的Linux性能可观察性工具&#xff1a; top: 显示实时的系统性能数据&#xff0c;包括CPU使用率、内存使用情况、进程信息等。 htop: 类似于top&#xff0c;但提供了更多的交互式功能…

谷粒商城----rabbitmq

一、 为什么要用 MQ? 三大好处&#xff0c;削峰&#xff0c;解耦&#xff0c;异步。 削峰 比如秒杀&#xff0c;或者高铁抢票&#xff0c;请求在某些时间点实在是太多了&#xff0c;服务器处理不过来&#xff0c;可以把请求放到 MQ 里面缓冲一下&#xff0c;把一秒内收到的…

Arcgis栅格转点时ERROR 999999: 执行函数时出错。 无法创建要素数据集。 执行(RasterToPoint)失败

Arcgis栅格转点时ERROR 999999: 执行函数时出错。 无法创建要素数据集。 执行(RasterToPoint)失败。 问题描述 原因 输出点要素的位置不对 解决方案 点击新建文件地理数据库 然后在该文件地理数据库下输出

RocketMQ 消息传递模型

文章目录 0. 前言1. RocketMQ的消息传递模型1.1. 同步发送1.2. 异步发送1.3. 单向发送 2. RocketMQ的批量发送和消费2.1 批量发送2.2 批量消费2.3 Spring Boot集成RocketMQ官方starter 示例 3. 总结4. 参考文档5. 源码地址 0. 前言 RocketMQ 支持6种消息传递方式&#xff0c;我…

【Java 基础篇】Java 泛型:类型安全的编程指南

在 Java 编程中&#xff0c;泛型是一项强大的特性&#xff0c;它允许您编写更通用、更安全和更灵活的代码。无论您是初学者还是有经验的 Java 开发人员&#xff0c;了解和掌握泛型都是非常重要的。本篇博客将从基础概念一直深入到高级应用&#xff0c;详细介绍 Java 泛型。 什…