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

文章目录

  • 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 项目打包

**第一步 **

在这里插入图片描述

第二步

在这里插入图片描述

在这里插入图片描述

第三步

在这里插入图片描述

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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java面向对象(初级)

面向对象编程(基础) 面向对象编程&#xff08;OOP&#xff09;是一种编程范式&#xff0c;它强调程序设计是围绕对象、类和方法构建的。在面向对象编程中&#xff0c;程序被组织为一组对象&#xff0c;这些对象可以互相传递消息。面向对象编程的核心概念包括封装、继承和多态。…

2023.12.21 关于 Redis 常用数据结构 和 单线程模型

目录 各数据结构具体编码方式 查看 key 对应 value 的编码方式 Reids 单线程模型 经典面试题 IO 多路复用 Redis 常用数据结构 Redis 中所有的 key 均为 String 类型&#xff0c;而不同的是 value 的数据类型却有很多种以下介绍 5 种 value 常见的数据类型 注意&#xff1…

阿里云 ACK One 新特性:多集群网关,帮您快速构建同城容灾系统

云布道师 近日&#xff0c;阿里云分布式云容器平台 ACK One[1]发布“多集群网关”[2]&#xff08;ACK One Multi-cluster Gateways&#xff09;新特性&#xff0c;这是 ACK One 面向多云、多集群场景提供的云原生网关&#xff0c;用于对多集群南北向流量进行统一管理。 基于 …

虚拟机的下载、安装(模拟出服务器)

下载 vmware workstation&#xff08;收费的虚拟机&#xff09; 下载vbox 网址&#xff1a;Oracle VM VirtualBox&#xff08;免费的虚拟机&#xff09; 以下选择一个下载即可&#xff0c;建议下载vbox&#xff0c;因为是免费的。安装的时候默认下一步即可&#xff08;路径最好…

hiveserver负载均衡配置

一.安装nginx 参数我的另一篇文章&#xff1a;https://mp.csdn.net/mp_blog/creation/editor/135152478 二.配置nginx服务参数 worker_processes 1; events { worker_connections 1024; } stream { upstream hiveserver2 { # least_conn; # 使用最少连接路由…

八大排序算法@直接插入排序(C语言版本)

目录 直接插入排序概念算法思想代码实现核心算法&#xff1a;直接插入排序的算法实现&#xff1a; 特性总结 直接插入排序 概念 算法思想 把待排序的记录按其关键码值的大小逐个插入到一个已经排好序的有序序列中&#xff0c;直到所有的记录插入完为止&#xff0c;得到一个新…

【Spring实战】配置多数据源

文章目录 1. 配置数据源信息2. 创建第一个数据源3. 创建第二个数据源4. 创建启动类及查询方法5. 启动服务6. 创建表及做数据7. 查询验证8. 详细代码总结 通过上一节的介绍&#xff0c;我们已经知道了如何使用 Spring 进行数据源的配置以及应用。在一些复杂的应用中&#xff0c;…

文档 - - - Docsify文档创建

目录 1. Docsify 介绍2. 创建 Docsify 项目2.1 安装 Node.js2.1 安装 docsfiy-cli2.3 初始化项目2.4 运行项目2.5 使用 Python 运行项目&#xff08;扩展&#xff0c;不推荐有bug&#xff09; 3. 配置 Docsify 项目3.1 修改等待加载文字3.2 添加网站 ico 图标3.3 创建新页面写文…

python 用OpenCV 将图片转视频

import os import cv2 import numpy as npcv2.VideoWriter&#xff08;&#xff09;参数 cv2.VideoWriter() 是 OpenCV 中用于创建视频文件的类。它的参数如下&#xff1a; filename&#xff1a;保存视频的文件名。 fourcc&#xff1a;指定视频编解码器的 FourCC 代码&#xf…

SVM —— 代码实现

SMO 算法的实现步骤&#xff1a; 代码如下&#xff1a; import numpy as np import matplotlib.pyplot as plt import seaborn as sns import random# 设置中文字体为宋体&#xff0c;英文字体为 times new roman sns.set(font"SimSun", style"ticks", fo…

webpack学习-7.创建库

webpack学习-7.创建库 1.暴露库1.1概念1.2验证1.2.1 不导出方法1.2.2 导出方法 2.外部化 lodash3.外部化的限制4.最终步骤5.使用自己的库5.1坑 6.总结 1.暴露库 这个模块学习有点坑。看名字就是把自己写的个包传到npm&#xff0c;而且还要在项目中使用到它&#xff0c;支持各种…

java类和对象的思想概述

0.面向对象Object OOP——名人名言&#xff1a;类是写出来的&#xff0c;对象是new出来的 **> 学习面向对象的三条路线 java类以及类成员&#xff1a;&#xff08;重点&#xff09;类成员——属性、方法、构造器、&#xff08;熟悉&#xff09;代码块、内部类面向对象特征&…

在Next.js和React中搭建Cesium项目

在Next.js和React中搭建Cesium项目&#xff0c;需要确保Cesium能够与服务端渲染(SSR)兼容&#xff0c;因为Next.js默认是SSR的。Cesium是一个基于WebGL的地理信息可视化库&#xff0c;通常用于在网页中展示三维地球或地图。下面是一个基本的步骤&#xff0c;用于在Next.js项目中…

VideoPoet: Google的一种用于零样本视频生成的大型语言模型

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

SpringCloud Alibaba(itheima)

SpringCloud Alibaba 第一章 微服务介绍1.1系统架构演变1.1.1单体应用架构1.1.2垂直应用架构1.1.3分布式架构1.1.4 SOA架构1.1.5微服务架构 1.2微服务架构介绍1.2.1微服务架构的常见问题1.2.2微服务架构的常见概念1.2.3微服务架构的常见解决方案 1.3 SpringCloud Alibaba介绍1.…

【clickhouse】在CentOS中离线安装clickhouse

一、下载地址 通过以下链接进行rpm安装包的下载 https://packages.clickhouse.com/rpm/stable/ 根据需求下载对应版本 注意&#xff1a;ClickHouse 20.8.2.3版本新增加了 MaterializeMySQL 的 database 引擎&#xff0c;该 database 能映射到 MySQL 中的某个 database&#…

iOS 开发设计 App 上架符合要求的截图

1. 真机运行截屏 2. 可以在 Apple developer 官网 Design 下找到 iPhone 边框 https://developer.apple.com/design/resources/ 不用这个边框也行&#xff0c;可以参考已上架 App 的图片框 3. 使用 Procreate&#xff08;PhotoShop&#xff09;创建符合要求的画布大小 4. 导入…

编译原理----算符优先级的分析(自底向上)

自底向上分析的分类如下所示&#xff1a; 算符优先分析 算符优先分析只规定算符之间的优先关系&#xff0c;也就是只考虑终结符之间的优先关系。 &#xff08;一&#xff09;若有文法G&#xff0c;如果G没有形如A->..BC..的产生式&#xff0c;其中B和C为非终结符&#xff…

Docker——微服务的部署

Docker——微服务的部署 文章目录 Docker——微服务的部署初识DockerDocker与虚拟机Docker架构安装DockerCentOS安装Docker卸载&#xff08;可选&#xff09;安装docker启动docker配置镜像加速 Docker的基本操作Docker的基本操作——镜像Docker基本操作——容器Docker基本操作—…

【【C++11特性篇】【强制/禁止 】生成默认函数的关键字default&delete(代码演示)

前言 大家好吖&#xff0c;欢迎来到 YY 滴C系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的《Linux》…