Qt的简单游戏实现提供完整代码

news2024/11/26 17:30:48

文章目录

  • 1 项目简介
  • 2 项目基本配置
    • 2.1 创建项目
    • 2.2 添加资源
  • 3 主场景
    • 3.1 设置游戏主场景配置
    • 3.2 设置背景图片
    • 3.3 创建开始按钮
    • 3.4 开始按钮跳跃特效实现
    • 3.5 创建选择关卡场景
    • 3.6 点击开始按钮进入选择关卡场景
  • 4 选择关卡场景
    • 4.1场景基本设置
    • 4.2 背景设置
    • 4.3 创建返回按钮
      • 4.4 选择关卡的返回按钮特效制作
    • 4.5 返回按钮
      • 4.5.1 开始场景与选择关卡场景的切换
    • 4.6 创建选择关卡按钮
    • 4.7 创建翻金币场景
  • 5 翻金币场景
    • 5.1 场景基本设置
    • 5.2 背景设置
    • 5.3 返回按钮
    • 5.4 显示当前关卡
    • 5.5 创建金币背景图片
    • 5.6 创建金币类
      • 5.6.1 创建金币类 MyCoin
      • 5.6.2 构造函数
      • 5.6.3测试
    • 5.7引入关卡数据
      • 5.7.1 添加现有文件dataConfig
      • 5.7.2 添加现有文件
      • 5.7.3 完成添加
      • 5.7.4 数据分析
      • 5.7.5 测试关卡数据
    • 5.8 初始化各个关卡
    • 5.9 翻金币特效
      • 5.9.1 MyCoin类扩展属性和行为
      • 5.9.2 创建特效
      • 5.9.3 禁用按钮
      • 5.9.4 翻周围金币
    • 5.10 判断是否胜利
    • 5.11 胜利图片显示
    • 5.12 胜利后禁用按钮
  • 6 音效添加
    • 6.1 开始音效
    • 6.2 选择关卡音效
    • 6.3 返回按钮音效
    • 6.4 翻金币与胜利音效
  • 7 优化项目
  • 8 项目打包

1 项目简介

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

2 项目基本配置

2.1 创建项目

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

在这里插入图片描述

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

在这里插入图片描述

点击完成,创建出项目:

在这里插入图片描述

创建的项目结构如下:

在这里插入图片描述

2.2 添加资源

将资源添加到当前项目下

在这里插入图片描述

然后创建.qrc文件

在这里插入图片描述

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

在这里插入图片描述

将所有资源文件进行添加

在这里插入图片描述

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

3 主场景

3.1 设置游戏主场景配置

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

在这里插入图片描述

设计“退出”菜单项,oblectName为actionQult,text为退出;

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

在这里插入图片描述

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

    // 设置固定大小
    this->setFixedSize(390, 570);

    // 设置图标
    this->setWindowIcon(QIcon(":/res/Coin0001.png"));

    // 设置窗口标题
    this->setWindowTitle("翻金币主场景");

运行效果如图:

在这里插入图片描述

3.2 设置背景图片

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

// MainScene.h
	// 重写paintEvent事件 画背景图
    void paintEvent(QPaintEvent *);
// MainScene.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);
}

在这里插入图片描述

3.3 创建开始按钮

开始按钮点击后有弹跳效果,这个效果是我们利用自定义控件实现的(QPushButton不会自带这类特效),我们可以自己封装出一个按钮控件,来实现这些效果。

创建MyPushButton,继承与 QPushButton

在这里插入图片描述

在这里插入图片描述

点击完成。

修改MyPushButton的父类
在这里插入图片描述

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

代码如下

// mypushbutton.h
	// 构造函数 参数1 正常显示的图片路径 参数2 按下后显示路径
    MyPushButton(QString normalImg, QString pressImg = "");

    // 成员属性 保存用户传入的默认显示路径 以及按下后显示的图片路径
    QString normalImgPath;
    QString pressImgPath;

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

MyPushButton::MyPushButton(QString normalImg, QString pressImg) {
    this->normalImgPath = normalImg;
    this->pressImgPath = pressImg;

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

    // 设置图片固定大小
    this->setFixedSize(pix.width(), pix.height());
    // 设置不规则图片样式
    this->setStyleSheet("QPushButton{ border: 0px;}");
    // 设置图标
    this->setIcon(pix);
    // 设置图标大小
    this->setIconSize(QSize(pix.width(), pix.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 开始按钮跳跃特效实现

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

// mainscene.cpp
connect(startBtn, &QPushButton::clicked, this, [=](){
        // 做弹起特效
        startBtn->zoom1();
        startBtn->zoom2();

    });

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

// mypushbutton.cpp
void MyPushButton::zoom1() {
    // 创建动态对象
    QPropertyAnimation *animation = new QPropertyAnimation(this, "geometry");
    // 设置动画时间间隔
    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()));

    // 设置弹跳曲线
    animation->setEasingCurve(QEasingCurve::OutBounce);

    // 执行动画
    animation->start();
}
void MyPushButton::zoom2() {
    // 创建动态对象
    QPropertyAnimation *animation = new QPropertyAnimation(this, "geometry");
    // 设置动画时间间隔
    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();
}

3.5 创建选择关卡场景

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

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

在这里插入图片描述

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

在这里插入图片描述

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

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

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

// 在mainscene.h中
// ChooseLevelScene *chooseScene = nullptr;

// 在mainscene.cpp中
// 实例化选择关卡场景
    chooseScene = new ChooseLevelScene;

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

// mainscene.cpp
connect(startBtn, &QPushButton::clicked, this, [=](){
        // 做弹起特效
        startBtn->zoom1(); // 向下跳跃
        startBtn->zoom2(); // 向上跳跃
        // 延时0.5秒 进入到选择关卡场景中
        QTimer::singleShot(500, this, [=](){
            // 自身隐藏
            this->hide();
            // 显示选择关卡场景
            chooseScene->show();
        });
    });

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

4 选择关卡场景

4.1场景基本设置

选择关卡构造函数如下

// chooselevelscene.cpp
// 配置选择关卡场景
    this->setFixedSize(390, 570);

    // 设置图标
    this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
    // 设置标题
    this->setWindowTitle("选择关卡场景");
    // 创建菜单栏
    QMenuBar *bar = new QMenuBar();
    this->setMenuBar(bar);
    // 创建开始菜单
    QMenu *startMenu = bar->addMenu("开始");
    // 创建退出 菜单项
    QAction *quitAction = startMenu->addAction("退出");
    // 点击退出 实现退出游戏
    connect(quitAction, &QAction::triggered, this, [=](){
        this->close();
    });

运行效果如图:

在这里插入图片描述

4.2 背景设置

// chooselevelscene.cpp
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 创建返回按钮

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

4.4 选择关卡的返回按钮特效制作

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

// mypushbutton.h
// 重写按钮 按下 和 释放事件
    void mousePressEvent(QMouseEvent *e);
    void mouseReleaseEvent(QMouseEvent *e);


// mypushbutton.cpp
void MyPushButton::mousePressEvent(QMouseEvent *e) {
    // 传入的按钮图片不为空 说明需要有按下状态, 切换图片
    if (this->pressImgPath != "") {
        QPixmap pix;
        bool ret = pix.load(this->pressImgPath);
        if (!ret) {
            qDebug() << "图片加载失败";
            return;
        }

        // 设置图片固定大小
        this->setFixedSize(pix.width(), pix.height());
        // 设置不规则图片样式
        this->setStyleSheet("QPushButton{ border: 0px;}");
        // 设置图标
        this->setIcon(pix);
        // 设置图标大小
        this->setIconSize(QSize(pix.width(), pix.height()));
    }

    // 让父类执行其他内容
    return QPushButton::mousePressEvent(e);
}
void MyPushButton::mouseReleaseEvent(QMouseEvent *e) {
    // 传入的按钮图片不为空 说明需要有按下状态, 切换成初始图片
    if (this->pressImgPath != "") {
        QPixmap pix;
        bool ret = pix.load(this->normalImgPath);
        if (!ret) {
            qDebug() << "图片加载失败";
            return;
        }

        // 设置图片固定大小
        this->setFixedSize(pix.width(), pix.height());
        // 设置不规则图片样式
        this->setStyleSheet("QPushButton{ border: 0px;}");
        // 设置图标
        this->setIcon(pix);
        // 设置图标大小
        this->setIconSize(QSize(pix.width(), pix.height()));
    }

    // 让父类执行其他内容
    return QPushButton::mouseReleaseEvent(e);
}


4.5 返回按钮

4.5.1 开始场景与选择关卡场景的切换

  • 点击选择关卡场景的返回按钮,发送一个自定义信号
  • 在主场景中监听这个信号,并且当触发信号后,重新显示主场景,隐藏掉选择关卡的场景

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

// chooselevelscene.h
	// 写一个自定义信号, 告诉主场景  点击了返回
    void chooseSceneBack();



// chooselevelscene.cpp
	// 点击返回
    connect(backBtn, &QPushButton::clicked, this, [=](){
        // 告诉主场景 我返回了, 主场景监听ChooseLevelScene的返回按钮
        // 延时返回
        QTimer::singleShot(500, this, [=](){
            // 发送自定义信号
            emit this->chooseSceneBack();
        });
    });

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

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

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

4.6 创建选择关卡按钮

选择关卡中的按钮创建

  • 利用一个for循环将所有按钮布置到场景中
  • 在按钮上面设置一个aLabel显示关卡数
  • QLabel设置大小、显示文字、对齐方式、鼠标穿透8.3给每个按钮监听点击事件
// chooselevelscene.cpp
// 创建选择关卡的按钮
    for (int i = 0; i < 20; i++) {
        MyPushButton *menuBtn = new MyPushButton(":/res/LevelIcon.png");
        menuBtn->setParent(this);
        menuBtn->move(60 + i % 4 * 70, 140 + i / 4 * 70);

        // 监听每个按钮的点击事件
        connect(menuBtn, &QPushButton::clicked, this, [=](){
            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(1 + i));
        label->move(60 + i % 4 * 70, 140 + i / 4 * 70);

        // 设置label上的文字对齐方式 水平居中 垂直居中
        label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter);
        // 设置让鼠标进行穿透 51属性
        label->setAttribute(Qt::WA_TransparentForMouseEvents);
    }

4.7 创建翻金币场景

点击关卡按钮后,会进入游戏的核心场景,也就是翻金币的场景,首先先创建出该场景的h和.cpp文件创建PlayScene

点击选择关卡按钮后会跳入到该场景

建立点击按钮,跳转场景的信号槽连接

在ChooseLevelScene.h中声明

PlayScene *pScene = NULL;

// chooselevelscene.cpp
        // 监听每个按钮的点击事件
        connect(menuBtn, &QPushButton::clicked, this, [=](){
            QString str = QString("选了第 %1 关").arg(i + 1);
            qDebug() << str;

            // 进入到游戏场景
            this->hide(); // 将选关场景隐藏掉
            play = new PlayScene(i + 1);    // 创建游戏场景
            play->show();   // 显示游戏场景
        });

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

5 翻金币场景

5.1 场景基本设置

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

// PlayScene.h
int levelIndex; // 内部成员属性 记录所选的关卡

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

// PlayScene.cpp
PlayScene::PlayScene(int levelNum) {
    QString str = QString("进入第 %1 关").arg(levelNum);
    qDebug() << str;
    this->levelIndex = levelNum;
    // 初始化游戏场景
    // 设置固定大小
    this->setFixedSize(390, 570);
    // 设置图标
    this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
    // 设置标题
    this->setWindowTitle("翻金币场景");
    // 创建菜单栏
    QMenuBar *bar = new QMenuBar();
    this->setMenuBar(bar);
    // 创建开始菜单
    QMenu *startMenu = bar->addMenu("开始");
    // 创建退出 菜单项
    QAction *quitAction = startMenu->addAction("退出");
    // 点击退出 实现退出游戏
    connect(quitAction, &QAction::triggered, this, [=](){
        this->close();
    });
}

5.2 背景设置

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

5.3 返回按钮

// PlayScene.h
// 写一个自定义信号, 告诉选关场景  点击了返回
    void chooseSceneBack();

// PlayScene.cpp
// 点击返回
    connect(backBtn, &QPushButton::clicked, this, [=](){
        // 告诉选关场景 我返回了, 选关场景监听ChooseLevelScene的返回按钮
        // 延时返回
        QTimer::singleShot(500, this, [=](){
            // 发送自定义信号
            emit this->chooseSceneBack();
        });
    });


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

// chooselevelscene.cpp
// 返回从游戏场景返回到选关场景
            connect(play, &PlayScene::chooseSceneBack, this, [=](){
                this->show();
                delete play;    // 删除游戏场景,因为每个场景不一样
                play = nullptr;
            });

5.4 显示当前关卡

// playscene.cpp
// 显示当前的关卡数
    QLabel *label = new QLabel;
    label->setParent(this);
    QFont font;
    font.setFamily("华文新魏");
    font.setPointSize(20);
    QString str1 = QString("Level: %1").arg(this->levelIndex);
    // 将字体设置到标签控件中
    label->setFont(font);
    label->setText(str1);
    label->setGeometry(30, this->height() - 50, 120, 50);

5.5 创建金币背景图片

// playscene.cpp
// 显示金币背景图案
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            // 绘制背景图片
            QPixmap pix = QPixmap(":/res/BoardNode.png");
            QLabel *label = new QLabel;
            label->setGeometry(0, 0, pix.width(), pix.height());
            label->setPixmap(pix);
            label->setParent(this);
            label->move(97 + i * 50, 200 + j * 50);
        }
    }

5.6 创建金币类

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

5.6.1 创建金币类 MyCoin

image-20231222131915462

并修改MyCoin的基类为QPushButton

5.6.2 构造函数

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

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

在这里插入图片描述

在MyCoin.h中声明

// MyCoin.h
// 参数代表 传入的金币路径 还是银币路径
    MyCoin(QString btnImg);

在MyCoin.cpp中进行实现

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

5.6.3测试

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

// playscene.cpp
// 创建金币
            MyCoin *coin = new MyCoin(":/res/Coin0001.png");
            coin->setParent(this);
            coin->move(99 + i * 50, 203 + j * 50);

运行效果 :

<img 在这里插入图片描述

5.7引入关卡数据

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

5.7.1 添加现有文件dataConfig

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

在这里插入图片描述

5.7.2 添加现有文件

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

在这里插入图片描述

5.7.3 完成添加

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

在这里插入图片描述

5.7.4 数据分析

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

// dataConfig.h
QMap<int, QVector< QVector<int> > > mData;

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

5.7.5 测试关卡数据

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

// main.cpp
// 测试关卡数据
    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中声明一个成员变量,用户记录当前关卡的二维数组

// playScene.h
int gameArray[4][4];    // 二维数组 维护每个关卡的具体数据

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

// playScene.cpp
// 初始化每个关卡的二维数组
    dataConfig config;
    for (int i = 0; i < 4; i++) {
        for (int j = 0; j < 4; j++) {
            this->gameArray[i][j] = config.mData[this->levelIndex][i][j];
        }
    }

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

// mycoin.h
	// 金币的属性
    int posX; // x坐标位置
    int posY; // y坐标位置
    bool flag; // 正反标志

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

// playscene.cpp

// 创建金币
            QString str;
            if (this->gameArray[i][j] == 1) {
                // 显示金币
                str = ":/res/Coin0001.png";
            }
            else {
                str = ":/res/Coin0008.png";
            }
            MyCoin *coin = new MyCoin(str);
            coin->setParent(this);
            coin->move(99 + i * 50, 203 + j * 50);
            // 给金币的属性赋值
            coin->posX = i;
            coin->posY = j;
            coin->flag = this->gameArray[i][j]; // 1正面 0反面

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

在这里插入图片描述

5.9 翻金币特效

5.9.1 MyCoin类扩展属性和行为

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

在MyCoin.h中声明:

// MyCoin.h
// 改变标志的方法
    void changeFlag();
    QTimer *timer1; // 正面翻反面的定时器
    QTimer *timer2; // 反面翻正面的定时器
    int min = 1;    // 最小图片(正面)
    int max = 8;    // 最大图片(反面)

MyCoin.cpp中做实现

// MyCoin.cpp
// 改变正反面标志的方法
void MyCoin::changeFlag() {
    // 如果是正面 翻成反面
    if (this->flag) {
        // 开始正面翻反面的定时器
        timer1->start(30);
        this->flag = false;
    }
    else {
        this->flag = true;
    }
}

5.9.2 创建特效

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

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

// mycoin.cpp
// 监听正面翻反面的信号 并且翻转金币
    connect(timer1, &QTimer::timeout, this, [=](){
        QPixmap pix;
        QString str = QString(":/res/Coin000%1").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()));
        if (this->min > this->max) {
            this->min = 1;
            timer1->stop();
        }
    });
    // 监听正面翻反面的信号 并且翻转金币
    connect(timer2, &QTimer::timeout, this, [=](){
        QPixmap pix;
        QString str = QString(":/res/Coin000%1").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()));
        if (this->max < this->min) {
            this->max = 8;
            timer2->stop();
        }
    });

5.9.3 禁用按钮

此时,确实已经可以执行翻辨转金币代码了,但是如果快速点击,会在金币还没有执行一个完整动作之后,又继续开始新的动画,我们应该在金币做动画期间,禁止再次点击,并在完成动画后,开启点击。
在MyCoin类中加入一个标志isAnimation代表是否正在做需专动画,默认isAnimation值为false。

// MyCoin.h
// 执行动画的标志
    bool isAnimation = false;

在MyCoin做动画期间加入

// MyCoin.cpp
isAnimation = true; // 开始做动画

也就是changeFlag函数中将标志设为true

加入位置如下

// 改变正反面标志的方法
void MyCoin::changeFlag() {
    // 如果是正面 翻成反面
    if (this->flag) {
        // 开始正面翻反面的定时器
        timer1->start(30);
        isAnimation = true; // 开始做动画
        this->flag = false;
    }
    else {
        // 反面翻正面的定时器
        timer2->start(30);
        isAnimation = true; // 开始做动画
        this->flag = true;
    }
}

并且在做完动画时,将标志改为false
在这里插入图片描述

重写按钮的按下事件,判断如果正在执行动画,那么直接return掉,不要执行后续代码。

代码如下

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

5.9.4 翻周围金币

解决快速点击的效果不好

  • 在MyCoin中加入了isAnimation判断是否正在做动画条件
  • 当按下MyCoin判断是否在做动画,如果做动画,直接return保证金币和银币动态切换的完整效果

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

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

并且记录每个按钮的位置

// playscene.cpp
// 将金币放入到 金币的二维数组里 以便以后期的维护
            coinBtn[i][j] = coin;

翻转周围硬币

// playscene.cpp
// 翻转周围硬币
                // 延时翻转
                QTimer::singleShot(300, this, [=](){
                    // 周围的右侧金币翻转的条件
                    if (coin->posX + 1 < 4) {
                        coinBtn[coin->posX + 1][coin->posY]->changeFlag();
                        this->gameArray[coin->posX + 1][coin->posY] = this->gameArray[coin->posX + 1][coin->posY] == 0 ? 1 : 0;
                    }
                    // 周围的左侧金币翻转的条件
                    if (coin->posX - 1 > -1) {
                        coinBtn[coin->posX - 1][coin->posY]->changeFlag();
                        this->gameArray[coin->posX - 1][coin->posY] = this->gameArray[coin->posX - 1][coin->posY] == 0 ? 1 : 0;
                    }
                    // 周围的下侧金币翻转的条件
                    if (coin->posY + 1 < 4) {
                        coinBtn[coin->posX][coin->posY + 1]->changeFlag();
                        this->gameArray[coin->posX][coin->posY + 1] = this->gameArray[coin->posX][coin->posY + 1] == 0 ? 1 : 0;
                    }
                    // 周围的上侧金币翻转的条件
                    if (coin->posY - 1 > -1) {
                        coinBtn[coin->posX][coin->posY - 1]->changeFlag();
                        this->gameArray[coin->posX][coin->posY - 1] = this->gameArray[coin->posX][coin->posY - 1] == 0 ? 1 : 0;
                    }
                });

5.10 判断是否胜利

  • PlayScene中添加isWin的标志来判断是否胜利
  • 如果胜利了,打印胜利信息
  • 将所有按钮屏蔽掉点击

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

// MyCoin.h
// 是否胜利的标志
    bool isWin;

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

// 判断游戏是否胜利
                    this->isWin = true; // 默认游戏胜利
                    for (int i = 0; i < 4; i++) {
                        for (int j = 0; j < 4; j++) {
                            if (!coinBtn[i][j]->flag) {
                                isWin = false;
                                break;
                            }
                        }
                    }
                    if (this->isWin) {
                        // 胜利了
                        // 将所有按钮的胜利标志改为true
                        for (int i = 0; i < 4; i++) {
                            for (int j = 0; j < 4; j++) {
                                coinBtn[i][j]->isWin = true;
                            }
                        }
                    }

5.11 胜利图片显示

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

// playscene.cpp
// 胜利图片显示
    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());

如果胜利了,将上面的图片移动下来

// 将胜利的图片移动下来
                        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()));
                        // 设置缓和曲线 38value
                        animation->setEasingCurve(QEasingCurve::OutBounce);
                        // 执行动画
                        animation->start();

5.12 胜利后禁用按钮

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

MyCoin.h中里添加:

// MyCoin.h
// 是否胜利的标志
    bool isWin = false; // 要设置false 否则系统随机分配可能无法触发鼠标事件

在鼠标按下事件中修改为

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

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

6 音效添加

Cmake添加模块

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

6.1 开始音效

// mainscene.cpp
// 准备开始按钮的音效
    QSoundEffect *startSound = new QSoundEffect(this);
    // 设置声音源文件的路径
    startSound->setSource(QUrl::fromLocalFile(":/res/TapButtonSound.wav"));
    // 音频循环的次数
    startSound->setLoopCount(1);
    // 音量
    startSound->setVolume(1);

点击开始按钮,播放音效

// mainscene.cpp
// 播放开始音效资源
        startSound->play();

6.2 选择关卡音效

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

// chooselevelscene.cpp
// 选择关卡音效
    QSoundEffect *chooseSound = new QSoundEffect(this);
    // 设置声音源文件的路径
    chooseSound->setSource(QUrl::fromLocalFile(":/res/TapButtonSound.wav"));
    // 音频循环的次数
    chooseSound->setLoopCount(1);
    // 音量
    chooseSound->setVolume(1);

选中关卡后,播放音效

// chooselevelscene.cpp
// 播放选择关卡音效
            chooseSound->play();

6.3 返回按钮音效

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

// chooselevelscene.cpp
// 返回按钮音效
    QSoundEffect *backSound = new QSoundEffect(this);
    // 设置声音源文件的路径
    backSound->setSource(QUrl::fromLocalFile(":/res/BackButtonSound.wav"));
    // 音频循环的次数
    backSound->setLoopCount(1);
    // 音量
    backSound->setVolume(1);

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

// chooselevelscene.cpp
// 播放返回按钮音效
        backSound->play();

6.4 翻金币与胜利音效

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

//  PlayScene.cpp
// 翻金币音效
    QSoundEffect *flipSound = new QSoundEffect(this);
    // 设置声音源文件的路径
    flipSound->setSource(QUrl::fromLocalFile(":/res/ConFlipSound.wav"));
    // 音频循环的次数
    flipSound->setLoopCount(1);
    // 音量
    flipSound->setVolume(1);

    // 胜利按钮音效
    QSoundEffect *winSound = new QSoundEffect(this);
    // 设置声音源文件的路径
    winSound->setSource(QUrl::fromLocalFile(":/res/LevelWinSound.wav"));
    // 音频循环的次数
    winSound->setLoopCount(1);
    // 音量
    winSound->setVolume(1);

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

//  PlayScene.cpp
// 播放翻金币的音效
                flipSound->play();

胜利时,播放胜利音效

//  PlayScene.cpp 
// 添加胜利的音效
                        winSound->play();

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

7 优化项目

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

MainScene中添加:

// MainScene.cpp
this->setGeometry(chooseScene->geometry());

chooseScene->setGeometry(this->geometry());

ChooseScene中添加

// ChooselevelScene.cpp
// 设置游戏场景的初始位置
            play->setGeometry(this->geometry());

this->setGeometry(play->geometry());

8 项目打包

**第一步 **

在这里插入图片描述

第二步

在这里插入图片描述

在这里插入图片描述

第三步

在这里插入图片描述

第四步 还有要确保第三步要加入到系统环境变量中否则无法执行第四步

在这里插入图片描述

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

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

相关文章

C++设计模式 #6 桥模式(Bridge)

动机 由于某些类型的固有的实现逻辑&#xff0c;使得它们具有两个变化的维度&#xff0c;乃至多个变化的维度。 如何应对这种“多维度的变化”&#xff1f;如何利用面向对象技术来使得类型可以轻松地沿着两个乃至多个方向变化&#xff0c;而不引入额外的复杂度 举个栗子 我们…

JavaOOP篇----第十五篇

系列文章目录 文章目录 系列文章目录前言一、有没有可能两个不相等的对象有相同的hashcode二、拷贝和浅拷贝的区别是什么?三、static都有哪些用法?前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通…

基于SSM的双减后初小教育课外学习生活活动平台的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

【零基础入门Docker】如何构建Web服务Dockerfile?

✍面向读者&#xff1a;所有人 ✍所属专栏&#xff1a;零基础入门Docker专栏https://blog.csdn.net/arthas777/category_12455882.html 目录 步骤1&#xff1a;第一步是构建我们的Docker文件&#xff0c;您可以使用vim编辑器。 步骤2&#xff1a;下一步是使用docker build命令…

学习stm32 模电数电需要学哪些?

学习stm32 模电数电需要学哪些&#xff1f; 在开始前我有一些资料&#xff0c;是我根据自己从业十年经验&#xff0c;熬夜搞了几个通宵&#xff0c;精心整理了一份「 stm32的资料从专业入门到高级教程工具包」&#xff0c;点个关注&#xff0c;全部无偿共享给大家&#xff01;&…

帧内预测器的设计:提升视频编码效率的关键技术

随着互联网的迅猛发展&#xff0c;视频应用成为人们日常生活中不可或缺的一部分。然而&#xff0c;视频文件的传输和存储所需要的带宽和空间成本巨大。为了解决这个问题&#xff0c;视频编码技术应运而生。在视频编码中&#xff0c;帧内预测器是一项关键技术&#xff0c;通过利…

Linux-Keepalived(VRRP协议)高可用集群搭建

Linux-Keepalived&#xff08;VRRP协议&#xff09;高可用集群搭建 一、VRRP简介1.1 什么是VRRP&#xff1f;1.2 keepalived是什么&#xff1f;1.3 keepalived工作原理 二、实操配置过程2.1 试验模型2.2. Keepalived监控和维护VRRP集群的步骤2.2.1 安装keepalived2.2.2 配置kee…

力扣算法-Day1

160. 相交链表 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点&#xff0c;返回 null 。 示例 1&#xff1a; 输入&#xff1a;intersectVal 8, listA [4,1,8,4,5], listB [5,6,1,8,4,5], skipA 2, s…

嵌入式开发必须学习qt吗?

嵌入式开发必须学习qt吗&#xff1f; 在开始前我有一些资料&#xff0c;是我根据自己从业十年经验&#xff0c;熬夜搞了几个通宵&#xff0c;精心整理了一份「 嵌入式的资料从专业入门到高级教程工具包」&#xff0c;点个关注&#xff0c;全部无偿共享给大家&#xff01;&#…

STM32位带

GPIO_SetBits(GPIOF,GPIO_Pin_9);修改为PFout(9)1; GPIO_ResetBits(GPIOF,GPIO_Pin_9);修改为PFout(9)0; 位带的定义&#xff1a; 支持了位带操作后&#xff0c;可以使用普通的加载/存储指令来对单一的比特进行读写。在CM3 中&#xff0c;有两个区中实现了位带。其中一个是S…

Flink系列之:Checkpoints 与 Savepoints

Flink系列之&#xff1a;Checkpoints 与 Savepoints 一、概述二、功能和限制 一、概述 从概念上讲&#xff0c;Flink 的 savepoints 与 checkpoints 的不同之处类似于传统数据库系统中的备份与恢复日志之间的差异。 Checkpoints 的主要目的是为意外失败的作业提供恢复机制。 …

python实现元旦多种炫酷高级倒计时_附源码【第19篇—python过元旦】

文章目录 &#x1f30d;python实现元旦倒计时 — 初级(控制台)⛅实现效果&#x1f30b;实现源码&#x1f31c;源码讲解 &#x1f30d;python实现元旦倒计时 — 中级(精美动态图)⛅实现效果&#x1f30b;实现源码&#x1f31c;源码讲解 &#x1f30d;python实现元旦倒计时 — 高…

中北大学 软件构造 U+

作业1 1.数据类型可分为两类:(原子类型) 、结构类型。 2.(数据结构)是计算机存储、组织数据的方式&#xff0c;是指相互之间存在一种或多种特定关系的数据元素的集合 3.代码重构指的是改变程序的(结构)而不改变其行为&#xff0c;以便提高代码的可读性、易修改性等。 4.软件实…

【经典LeetCode算法题目专栏分类】【第11期】递归问题:字母大小写全排列、括号生成

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能AI、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推荐--…

设计模式----解释器模式

一、简介 解释器模式使用频率并不高&#xff0c;通常用来构建一个简单语言的语法解释器&#xff0c;它只在一些非常特定的领域被用到&#xff0c;比如编译器、规则引擎、正则表达式、sql解析等。 解释器模式是行为型设计模式之一&#xff0c;它的原始定义为&#xff1a;用于定义…

GIT具体配置步骤详解

GIT配置具体步骤如下 SDK 使用 Repo 工具管理&#xff0c;拉取 SDK 需要配置安装 Repo 工具。 Repo is a tool built on top of Git. Repo helps manage many Git repositories, does the uploads to revision control systems, and automates parts of the development workf…

4.svn版本管理工具使用

1. 什么是SVN 版本控制 它可以记录每一次文件和目录的修改情况,这样就可以借此将数据恢复到以前的版本,并可以查看数据的更改细节! Subversion(简称SVN)是一个自由开源的版本控制系统。在Subversion管理下,文件和目录可以超越时空 SVN的优势 统一的版本号 Subversi…

MySQL子查询、WITH AS、LAG查询统计数据实战

需求 给出一个比较常见的统计类业务需求&#xff1a;统计App&#xff08;包括iOS和Android两大类&#xff09;每日新注册用户数、以及累计注册用户数。 数据库采用MySQL&#xff0c;根据上面的需求&#xff0c;不难设计表如下&#xff1a; create table os_day_count(stat_d…

【必读】从MII到RGMII,一文了解以太网PHY芯片不同传输接口信号时序!

1、概述 不管是使用FPGA还是ARM&#xff0c;想要实现以太网通信&#xff0c;都离不开以太网PHY芯片&#xff0c;其功能如下所示&#xff0c;FPGA或者ARM将以太网数据发送给PHY芯片&#xff0c;PHY会将接收数据转换成模拟的差分信号传输到RJ45座子&#xff0c;最后通过网线与CPU…

数据库之MySQL的介绍

操作系统&#xff1a; windows&#xff1a;win10、win11、win7、windows Server2016 Linux/Unix &#xff1a;红帽&#xff08;RedHat&#xff09;、Bebian、SUSE MacOS Linux系统&#xff1a;CantOS&#xff08;yum、dnf&#xff09;、Ubuntu&#xff08;apt、apt—get&am…