QT基础入门——项目案例(七)

news2024/9/20 7:58:48

前言:

前面我们已经把基础的QT知识学习了一遍,已经足够运用这些知识做一个简单的小项目了,那么现在就让我们将所学的知识运用到现实操作中,来做一个简单的翻硬币小项目练练手吧

目录

一、项目简介:

二、项目的基本配置和资源添加

1.创建项目

2.添加资源文件

三、主场景

1.设置游戏主场景配置

2.设置背景图片

3.创建开始按钮

4.开始按钮跳跃特效实现

5.创建选择关卡场景

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

四、选择关卡场景

1.场景基本设置

2. 背景设置

3.创建返回按钮

4.返回按钮

5.创建选择关卡按钮

五、翻金币场景

1.创建翻金币场景

2.场景基本设置

3.背景设置

4.返回按钮

5.显示当前关卡

 六、创建金币类

1.创建金币

2.创建金币类 MyCoin

3.构造函数

4.测试

七、引入关卡数据

1.添加现有文件dataConfig

2.添加现有文件

3.完成添加

 4.数据分析

5.测试关卡数据

 八、初始化各个关卡

 九、翻金币设置

1.翻硬币特效

(1).MyCoin类扩展属性和行为

(2) 创建特效

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

(4) 禁用按钮

2.翻周围金币

3.判断是否胜利

3.胜利图片显示

4.胜利后禁用按钮

十、音效添加

1 开始音效

2.选择关卡音效

返回按钮音效

十一、优化项目


一、项目简介:

翻金币项目是一款经典的益智类游戏,我们需要将金币都翻成同色,才视为胜利。首先,开始界面如下:

点击start按钮,进入下层界面,选择关卡:

        

 在这里我们设立了20个关卡供玩家选择,假设我们点击了第1关,界面如下:

如果想要赢取胜利,我们需要点击上图中红色方框选取的区域,翻动其上下左右的金币,然后当所有金币都变为金色,视为胜利,胜利界面如下:

二、项目的基本配置和资源添加

1.创建项目

打开Qt,创建项目:注意名称不要包含空格和回车,路径不要有中文

类信息中,选择基类为QMainWindow类名称为 MainScene,代表着主场景。

点击完成,创建出项目:

 创建的项目结构如下:

2.添加资源文件

将资源添加到当前项目下

然后创建.qrc文件

进入编辑模式,添加前缀 “/” ,添加文件

进入编辑模式,添加前缀 “/” ,添加文件

将所有资源文件进行添加 

至此将所有需要的资源添加到了本项目中。

三、主场景

1.设置游戏主场景配置

点击mainscene.ui文件,设计其菜单栏如下:

设计“退出”菜单项,objectName为 actionQuit,  text 为 退出;

移除自带的工具栏与状态栏

回到MainScene.cpp文件,进入构造函数中,进行场景的基本配置,代码如下:

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

运行效果如图:

实现点击开始,退出游戏功能,代码如下:

2.设置背景图片

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

重写MainScene的PaintEvent事件,并添加一下代码,绘制背景图片

void MainWindow::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,继承与QPushButton

点击完成。

修改MyPushButton的父类

提供MyPushButton的构造的重载版本,可以让MyPushButton提供正常显示的图片以及按下后显示的图片

代码如下:

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

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

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

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的构造函数中,创建开始按钮

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

运行效果如图:

不规则的开始按钮添加完成。

4.开始按钮跳跃特效实现

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

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

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

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()));
    animation1->setEasingCurve(QEasingCurve::OutBounce);
    animation1->start();
}

5.创建选择关卡场景

点击开始按钮后,进入选择关卡场景。

首先我们先创建选择关卡场景,添加新的C++文件

类名为ChooseLevelScene 选择基类为QMainWindow,点击下一步,然后点击完成。

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

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

    

  在MainScene.h中 保存ChooseScene选择关卡场景对象

chooseScene = new ChooseLevelScens;

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

     //进入到选择关卡的场景中
     //延时0.5秒后 进入选择场景
     QTimer::singleShot(500, this,[=](){
             //自身隐藏
             this->hide();
             //显示选择关卡
             chooseScene->show();
        });

测试点击开始,执行特效后延时0.5秒进入选择关卡场景

四、选择关卡场景

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

运行效果如图:

        

2. 背景设置

void ChooseLevelScens::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);
}

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,[=](){
           qDebug() << "点击了返回按钮";
       });

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

 //重写按钮   按下  
void MyPushButton::mousePressEvent(QMouseEvent *e)
{
    if(pressedImgPath != "") //传入的按下图片不为空   说明需要有按下状态  切换图片
    {
        //创建QPixmap对象
         QPixmap pixmap;
         //判断是否能够加载正常显示的图片,若不能提示加载失败
         bool ret = pixmap.load(this->pressedImgPath);
         if(!ret)
         {
             qDebug() << "加载图片失败!";
             return;
         }
         //设置图片的固定尺寸
         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(pressedImgPath != "") //传入的按下图片不为空   说明需要有按下状态  切换成初始图片
    {
        //创建QPixmap对象
         QPixmap pixmap;
         //判断是否能够加载正常显示的图片,若不能提示加载失败
         bool ret = pixmap.load(this->pressedImgPath);
         if(!ret)
         {
             qDebug() << "加载图片失败!";
             return;
         }
         //设置图片的固定尺寸
         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.返回按钮

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

       //点击返回
       connect(backBtn,&MyPushButton::clicked,[=](){
           qDebug() << "点击了返回按钮";
           //告诉主场景  我返回了,主场景监听ChooseLevelScene的返回按钮
           QTimer::singleShot(500, this,[=](){
                this->hide();
                //触发自定义信号,关闭自身,该信号写到 signals下做声明
                emit this->chooseSceneBack();

           });

      });

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

    //监听选择场景的返回按钮
    connect(chooseScene,&ChooseLevelScene::chooseSceneBack,this,[=](){
              chooseScene->hide();//将选择关卡  隐藏掉
              this->show();//重新显示主场景
   });

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

5.创建选择关卡按钮

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

           //监听每个按钮的点击事件
               connect(menuBtn,&MyPushButton::clicked,[=](){
                QString str = QString("您选择的是第%1关").arg(i+1);
                qDebug()<<str;
           });
           //按钮上显示的文字
           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(25 + (i%4)*70 , 130+ (i/4)*70);
           label->setAttribute(Qt::WA_TransparentForMouseEvents,true);  //鼠标事件穿透
    }

运行效果如果:

五、翻金币场景

1.创建翻金币场景

点击关卡按钮后,会进入游戏的核心场景,也就是翻金币的场景,首先先创建出该场景的.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 要提供重载的有参构造版本,来接受这个参数

2.场景基本设置

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

int levelIndex;//内部成员属性,记录所选关卡

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

    //初始化游戏场景
    //设置固定大小
    //设置窗口固定大小
    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();});

3.背景设置

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

4.返回按钮

    //返回按钮
    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.显示当前关卡

      //当前关卡标题
      QLabel * label = new QLabel;
      label->setParent(this);
      QFont font;
      font.setFamily("华文新魏");
      font.setPointSize(20);    
      QString str1 = QString("Leavel: %1").arg(this->levelIndex);
      
      //将字体设置到标签控件中
      label->setFont(font);
      label->setText(str1);
      label->setGeometry(QRect(30, this->height() - 50,120, 50)); //设置大小和位置

假设我们选择了第15关卡,运行效果如果:

 六、创建金币类

  //创建金币的背景图片
    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);
        }
    }

运行效果如图:

1.创建金币

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

2.创建金币类 MyCoin

并修改MyCoin的基类为QPushButton

3.构造函数

        在资源图片中,我们可以看到,金币翻转的效果原理是多张图片切换而形成的,而以下八张图片中,第一张与最后一张比较特殊,因此我们在给用户看的时候,无非是金币Coin0001或者是银币 Coin0008这两种图。

         因此我们在创建一个金币对象时候,应该提供一个参数,代表着传入的是金币资源路径还是银币资源路径,根据路径我们创建不同样式的图案。

在MyCoin.h中声明:

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

 在MyCoin.cpp中进行实现

 MyCoin::MyCoin(QString btnImg)
 {
     QPixmap pix;
     bool ret = pix.load(btnImg);
     if(!ret)
     {
         QString str = QString("图片 %1加载失败").arg(btnImg);
         qDebug() << str;
         return;
     }
     
     this->setFixedSize( pix.width(), pix.height() );
     this->setStyleSheet("QPushButton{border:0px;}");
     this->setIcon(pix);
     this->setIconSize(QSize(pix.width(),pix.height()));
     
 }

4.测试

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

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

运行效果如图

七、引入关卡数据

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

1.添加现有文件dataConfig

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

2.添加现有文件

其次在Qt_Creator项目右键,点击添加现有文件

3.完成添加

选择当前项目下的文件,并进行添加

 4.数据分析

我们可以看到,其实dataConfig.h中只有一个数据是对外提供的,如下图

  QMap<int, QVector< QVector<int> > >mData;

在上述代码中,QMap<int,QVector<QVector<int>>>mData;都记录着每个关卡中的数据。

其中,int代表对应的关卡 ,也就是QMap中的key值,而value值就是对应的二维数组,我们利用的是 QVector<QVector<int>>来记录着其中的二维数组。

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中的数据来对关卡进行初始化了 

 八、初始化各个关卡

首先,可以在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 str;
    if(gameArray[i][j] == 1)
    {
      //显示金币
      str = ":/res/Coin0001.png";
    }
    else
    {
      //显示银币
      str = ":/res/Coin0008.png";
    }

    //金币对象
    int posX; //x坐标
    int posY; //y坐标
    bool flag; //正反标志
      
    MyCoin * coin = new MyCoin(str);
    coin->setParent(this);
    coin->move(59 + i*50,204+j*50);
    //给金币属性赋值
    coin->posX = i; //记录x坐标
    coin->posY = j; //记录y坐标
    coin->flag =gameArray[i][j]; //记录正反标志

运行测试各个关卡初始化,例如第一关效果如图:

 九、翻金币设置

1.翻硬币特效

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

(2) 创建特效

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

         构造函数中 进行下列监听代码:

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

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

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

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

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

2.翻周围金币

将用户点击的周围 上下左右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;
        }
});

3.判断是否胜利

在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;
            break;
        }
    }
}

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

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

3.胜利图片显示

将胜利的图片提前创建好,如果胜利触发了,将图片弹下来即可

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 * animation = new QPropertyAnimation(winLabel,"geometry");
    //设置时间间隔
    animation->setDuration(1000);
    //设置开始位置
    animation->setStartValue(QRect(winLabel->x(),winLabel->y(),winLabel->width(),winLabel->height()));
    //设置结束位置
    animation->setEndValue(QRect(winLabel->x(),winLabel->y()+114,winLabel->width(),winLabel->height()));
    //设置缓和曲线
    animation->setEasingCurve(QEasingCurve::OutBounce);
    //执行动画
    animation->start();
}

4.胜利后禁用按钮

当胜利后,应该禁用所有按钮的点击状态,可以在每个按钮中加入标志位 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;
   }
}

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

十、音效添加

1 开始音效

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

点击开始按钮,播放音效

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

2.选择关卡音效

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

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

选中关卡后,播放音效

chooseSound->play();

返回按钮音效

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

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

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

backSound->play();

翻金币与胜利音效

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

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

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

flipSound->play();

胜利时,播放胜利音效

winSound->play();

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

十一、优化项目

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

MainWindow中添加:

ChooseScene中添加:

测试切换三个场景的进入与返回都在同一个位置下,优化成功。

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

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

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

相关文章

PyPy+Cython对Python进行加速,以及乱码问题

在之前测试了使用Cython对代码进行优化加速 https://blog.csdn.net/qq_43199509/article/details/133860665 PyPy具体是啥可以看之前的知乎介绍&#xff0c;个人理解就是Python的另一种解释器 https://www.zhihu.com/question/266096929/answer/2383570933 PyPy下载 https://…

知名低代码公司有哪些?最新国内十大低代码平台排名

什么是低代码和低代码平台 低代码&#xff08;Low-Code&#xff09;是一种软件开发方法&#xff0c;它使得开发人员能够通过图形界面、拖放组件和模型驱动的逻辑&#xff0c;快速地构建和部署应用程序&#xff0c;而无需编写大量的代码。 而低代码开发平台&#xff08;LCDP&am…

Sync Folders Pro for Mac文件夹数据同步工具

Sync Folders Pro for Mac 是一款功能强大的文件夹同步工具&#xff0c;旨在帮助用户在 Mac 计算机和移动设备之间创建双向同步。这款软件支持各种文件系统和设备&#xff0c;如 iPhone&#xff0c;iPad&#xff0c;iPod&#xff0c;Android 等。通过这款软件&#xff0c;用户可…

众和策略:几点开盘和收盘股票?

股票开盘和收盘时间是投资者有必要知道的要害信息&#xff0c;因为它们挑选了股票生意的初步和结束时间。在此文章中&#xff0c;咱们将从多个视点分析股票开盘和收盘时间&#xff0c;包括全球商场开盘时间、技术分析对开盘前后价格不坚决的影响、以及日内生意者如安在开盘和收…

【社区小程序制作】便捷社区服务的新选择

社区小程序的出现为居民提供了便捷的社区服务&#xff0c;促进了社区内的信息共享和互动。下面将介绍社区小程序的优点和制作流程。 优点 便捷的社区服务&#xff1a;社区小程序为居民提供了便捷的社区服务。居民可以通过小程序查询社区通知、报修维修等&#xff0c;高效且节省…

字符与数字的相互转换

一、字符转数字 char类型字符转换为数字&#xff0c;其实是转换为ASCII码值 有两种方式&#xff1a; 1.强制类型转换&#xff0c;结果为对应的ASCII码值 char v1 a;char v2 z;char v3 1;char v4 9;int num1 (int)v1;int num2 (int)v2;int num3 (int)v3;int num4 (int)v…

word字间距突然变大怎么办?

文章目录 原因&#xff1a;word字间距突然变大&#xff0c;是文字布局设置造成的。 解决方法如下&#xff1a; 1、首先在打开的文档中&#xff0c;可以看到如下图所示部分字间距变大。 2、这时鼠标选中字间距变大的区域、或者全选该部分文字。命令选项卡内一般选择文字靠左设置…

微信视频发出去后无法播放怎么办?解决办法来了

微信作为现今生活工作使用最频繁的软件之一&#xff0c;发送文档、播放视频、分享照片截图等是经常操作的&#xff0c;然而当我们向对方发送一个视频文件的时候&#xff0c;电脑端播放器播放正常&#xff0c;通过微信发出去之后结果无法播放了。其实微信无法播放视频有很多原因…

(三十四)大数据实战——scala运行环境安装配置及IDEA开发工具集成

前言 本节内容我们主要介绍一下scala运行环境的安装配置以及在idea开发工具中集成scala插件&#xff0c;便于scala项目的开发。 在开始scala安装配置之前&#xff0c;我们要先安装好jvm运行环境&#xff0c;scala运行于Java虚拟机&#xff08;JVM&#xff09;上&#xff0c;并…

JS中欺骗词法作用域的eval和with

1、词法作用域 简单地说&#xff0c;词法作用域就是定义在词法阶段的作用域。换句话说&#xff0c;词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的&#xff0c;因此当词法分析器处理代码时会保持作用域不变&#xff08;大部分情况下是这样的&#xff09;。 考虑…

上抖音热搜榜需要怎么做?

抖音热搜榜是根据用户的搜索行为和搜索量数据进行排名的榜单。具体排名规则如下&#xff1a; 1. 关键词匹配度&#xff1a;抖音搜索引擎会根据用户搜索的关键词与视频标题、标签、内容等进行匹配&#xff0c;匹配度越高&#xff0c;排名越靠前。 2. 视频质量&#xff1a;抖音…

6-k8s-控制器版本管理

文章目录 一、概念介绍二、配置介绍三、版本生成测试四、版本回滚测试 一、概念介绍 什么是控制器&#xff1a;在k8s中&#xff0c;控制器是一种用于控制和管理Pod的管理器&#xff0c;包括Deployment、ReplicaSet、StatefulSet等。 什么是控制器版本管理&#xff1a;是指对于…

django无法导入第三方库

引子 有的人可能会很困惑&#xff0c;为什么自己在pip中安装了某个包&#xff0c;但是在django中死活无法导入。 在cmd中能够导入。 启动django&#xff0c;总是无法导入。 本文将会用一分钟解决你的困惑。 正文 那么本文以上述的第三方库dj_db_conn_pool为例&#xff0c;…

Android:展锐T760平台camera驱动调试

一、模块平台框架 平台介绍 基于UMS9620x平台集成Camera时&#xff0c;共有四类信号需要了解&#xff1a; 1、MIPI CSI信号&#xff1a;该平台有DPHY和CPHY两种MIPI信号。  DPHY配置&#xff1a;包括数对差分数据信号DP/N&#xff0c;和一对差分时钟信号CKP/N。  …

超全面的前端工程化配置指南

前端工程化配置指南 本文讲解如何构建一个工程化的前端库&#xff0c;并结合 Github Actions&#xff0c;自动发布到 Github 和 NPM 的整个详细流程。 示例 我们经常看到像 Vue、React 这些流行的开源项目有很多配置文件&#xff0c;他们是干什么用的&#xff1f;他们的 Commit…

Typora+PicGo+Github+CSDN梦幻联动

文章目录 一、快速搭建个人免费图床二、Typora图片实现自动上传三、Typora图片上传到CSDN出现错误 一、快速搭建个人免费图床 之前写过一篇 快速搭建个人免费图床 的文章&#xff0c;但是每次都要把图片拖到PicGo里面才能生成链接很麻烦&#xff0c;而且在本地用Typora写的文章…

C++ —— Tinyxml2在Vs2017下相关使用2(较文1更复杂,附源码)

相关链接 C —— Tinyxml2在Vs2017下相关使用1&#xff08;附源码&#xff09; tinyxml2简介 TinyXML2是一个简单&#xff0c;小巧&#xff0c;高效&#xff0c;CXML解析器&#xff0c;可以很容易地集成到其他程序中。TinyXML-2解析一个XML文档&#xff0c;并从中构建一个 可以…

强化学习章节脉络

强化学习是在求解最优策略

Python爬虫:制作一个属于自己的IP代理模块

前言 在Python爬虫过程中&#xff0c;为了避免被网站的反爬虫机制干扰&#xff0c;我们需要使用IP代理。所谓IP代理&#xff0c;就是通过修改网络请求中的IP地址&#xff0c;来达到隐藏真实IP地址的效果。本文将教你如何制作一个自己的IP代理模块&#xff0c;让你的爬虫更加稳…

网络库OKHttp(1)流程

序、慢慢来才是最快的方法。 背景 OkHttp 是一套处理 HTTP 网络请求的依赖库&#xff0c;由 Square 公司设计研发并开源&#xff0c;目前可以在 Java 和 Kotlin 中使用。对于 Android App 来说&#xff0c;OkHttp 现在几乎已经占据了所有的网络请求操作。 OKHttp源码官网 版…