学习视频链接
项目资料,提取码 6666
目录

- /* 配置主场景 */
- // 设置固定大小
- setFixedSize(320, 500);
- // 设置图标
- setWindowIcon(QIcon(":/res/Coin0001.png"));
- // 设置标题
- setWindowTitle("翻金币主场景");





图片缩放

- void MainScene::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); // 宽高变成原来的0.5
- painter.drawPixmap(10, 30, pix);
- }




- #ifndef MYPUSHBUTTON_H
- #define MYPUSHBUTTON_H
-
- #include
- #include
-
- class MyPushButton : public QPushButton
- {
- Q_OBJECT
- public:
- // 构造函数 参数1:正常显示的图片路径 参数2:按下后显示的图片路径
- MyPushButton(QString normalImg, QString pressImg = "");
-
- // 成员属性 保存用户传入的默认显示路径以及按下后显示的图片路径
- QString normalImgPath;
- QString pressImgPath;
-
- signals:
-
- };
-
- #endif // MYPUSHBUTTON_H

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


如果不设置不规则样式就会有这种情况




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


右边就有了一个抖动的效果
添加新的场景类 ChooseLevelScene,把这个场景类放到主场景中

构造函数中加入申请内存的代码,点击后隐藏自身并且显示选择关卡场景

直接跳转太僵硬了,所以加入延时和优化的效果
加入头文件 #include

添加头文件 #include

- #include "chooselevelscene.h"
-
- ChooseLevelScene::ChooseLevelScene(QWidget *parent) : QMainWindow(parent)
- {
- /* 选择关卡场景 */
- // 设置固定大小
- this->setFixedSize(320, 500);
- // 设置图标
- this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
- // 设置标题
- this->setWindowTitle("选择关卡场景");
-
- // 创建菜单栏
- QMenuBar *menubar= menuBar();
- setMenuBar(menubar);
- // 创建开始菜单
- QMenu *menuStart = menubar->addMenu("开始");
- // 创建退出菜单项
- QAction *actionQuit = menuStart->addAction("退出");
- // 点击退出实现退出游戏
- connect(actionQuit, &QAction::triggered, [=](){
- this->close();
- });
- }


- 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);
- }
使用我们封装好的按钮,我们包含一下按钮的头文件 #include "mypushbutton.h",

- // 返回按钮
- MyPushButton *backBtn = new MyPushButton(":/res/BackButton.png", ":/res/BackButtonSelected.png");
- backBtn->setParent(this);
- backBtn->move(this->width() - backBtn->width(), this->height() - backBtn->height());
-
- // 点击返回
- connect(backBtn, &MyPushButton::clicked, [=](){
- qDebug() << "点击了返回按钮";
- });
重写按钮按下和释放事件


- 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);
- }
现在按钮点击和释放就有不同的样子了
写一个自定义信号,点击按钮后发出信号,主场景接受


- // 点击返回
- connect(backBtn, &MyPushButton::clicked, [=](){
- qDebug() << "点击了返回按钮";
- // 告诉主场景 我返回了,主场景监听chooseLevelScene的返回按钮
- emit this->chooseSceneBack();
- });

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

在构造函数里面添加这些代码
- // 创建选择关卡的按钮
- 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->move(25 + i % 4 * 70, 130 + i / 4 * 70);
-
- // 设置label上的文字对齐方式 水平居中和垂直居中
- label->setAlignment(Qt::AlignCenter | Qt::AlignVCenter);
- // 设置让鼠标进行穿透51号属性
- label->setAttribute(Qt::WA_TransparentForMouseEvents);
- }



重新写一个构造函数


添加头文件 #include

- #include "playscene.h"
-
- PlayScene::PlayScene(int levelNum)
- {
- this->levelIndex = levelNum;
-
- // 初始化游戏场景
- // 设置固定大小
- this->setFixedSize (320,588) ;
- //设置图标
- this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
- //设置标题
- this->setWindowTitle("翻金币场景");
-
- // 创建菜单栏
- QMenuBar *menubar= menuBar();
- setMenuBar(menubar);
- // 创建开始菜单
- QMenu *menuStart = menubar->addMenu("开始");
- // 创建退出菜单项
- QAction *actionQuit = menuStart->addAction("退出");
- // 点击退出实现退出游戏
- connect(actionQuit, &QAction::triggered, [=](){
- this->close();
- });
- }
重写绘图事件,添加头文件 #include

- 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); // 宽高变成原来的0.5
- painter.drawPixmap(10, 30, pix.width(), pix.height(), pix);
- }
先添加自定义按钮的头文件 #include "mypushbutton.h"
添加信号:

点击后发送信号

在游戏选择场景中监听发送的信号,如果返回到这个场景就删除创建的游戏

- // 创建选择关卡的按钮
- 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;
-
- // 进入到游戏场景
- this->hide(); // 将选关场景隐藏掉
- play = new PlayScene(i + 1); // 创建游戏场景
- play->show(); // 显示游戏场景
-
- connect(play, &PlayScene::chooseSceneBack, [=](){
- this->show();
- delete play;
- play = nullptr;
- });
- });
-
- QLabel *label = new QLabel;
- label->setParent(this);
- label->setFixedSize(menuBtn->width(), menuBtn->height());
- label->setText(QString::number(i + 1));
- label->move(25 + i % 4 * 70, 130 + i / 4 * 70);
-
- // 设置label上的文字对齐方式 水平居中和垂直居中
- label->setAlignment(Qt::AlignCenter | Qt::AlignVCenter);
- // 设置让鼠标进行穿透51号属性
- label->setAttribute(Qt::WA_TransparentForMouseEvents);
- }
添加头文件 #include

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

- // 显示金币背景图案
- 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);
- }
- }



- #include "mycoin.h"
-
- 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()));
- }


把配置文件弄进来

右键添加现有文件

包含头文件,然后新加一个二维数组

创建游戏界面的时候初始化二维数组

初始化金币

- 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];
- }
- }
-
- // 显示金币背景图案
- 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, 157 + j * 50);
-
- // 创建金币
- 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(60 + i * 50, 160 + j * 50);
- }
- }
添加金币属性


金币来回翻转的效果(类似于 gif 一帧一帧播放)


- #include "mycoin.h"
-
- 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()));
-
- // 初始化定时器对象
- timer1 = new QTimer(this);
- timer2 = new QTimer(this);
-
- // 监听金币翻银币的信号,并且翻转金币
- 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();
- }
- });
- }
-
- // 改变金银币标志的方法
- void MyCoin::changeFlag()
- {
- // 如果是正面 翻成反面
- if(this->flag)
- {
- // 开始正面翻方面的定时器
- timer1->start(30);
- this->flag = false;
- }
- else // 银币翻金币
- {
- timer2->start(30);
- this->flag = true;
- }
-
- }

中间有一些bug,例如动画没有播放完就点了,我们可以限制一下






将创建好的金币放入数组用于维护,并作翻转
- // 显示金币背景图案
- 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, 157 + j * 50);
-
- // 创建金币
- 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(60 + i * 50, 160 + j * 50);
-
- // 给金币属性赋值
- coin->posX = i;
- coin->posY = j;
- coin->flag = this->gameArray[i][j]; // 1金币 0银币
-
- // 将金币放入到二维数组 以便后期的维护
- coinBtn[i][j] = coin;
-
- // 点击金、银币进行翻转
- connect(coin, &MyCoin::clicked, [=](){
- coin->changeFlag();
- this->gameArray[i][j] = !this->gameArray[i][j];
-
- // 翻转周围硬币
- if(coin->posX + 1 <= 3) // 金币的右侧金币翻转条件
- {
- coinBtn[coin->posX + 1][coin->posY]->changeFlag();
- this->gameArray[coin->posX + 1][coin->posY] = !this->gameArray[coin->posX + 1][coin->posY];
- }
- if(coin->posX - 1 >= 0) // 金币的左侧金币翻转条件
- {
- coinBtn[coin->posX - 1][coin->posY]->changeFlag();
- this->gameArray[coin->posX - 1][coin->posY] = !this->gameArray[coin->posX - 1][coin->posY];
- }
- if(coin->posY + 1 <= 3) // 金币的上侧金币翻转条件
- {
- coinBtn[coin->posX][coin->posY + 1]->changeFlag();
- this->gameArray[coin->posX][coin->posY + 1] = !this->gameArray[coin->posX][coin->posY + 1];
- }
- if(coin->posY - 1 >= 0) // 金币的下侧金币翻转条件
- {
- coinBtn[coin->posX][coin->posY - 1]->changeFlag();
- this->gameArray[coin->posX][coin->posY - 1] = !this->gameArray[coin->posX][coin->posY - 1];
- }
- });
- }
- }




如果胜利了,所有的银币按钮的标志位都改成游戏胜利

- // 翻转周围硬币
- // 周围延时翻转
- QTimer::singleShot(300, this, [=](){
- if(coin->posX + 1 <= 3) // 金币的右侧金币翻转条件
- {
- coinBtn[coin->posX + 1][coin->posY]->changeFlag();
- this->gameArray[coin->posX + 1][coin->posY] = !this->gameArray[coin->posX + 1][coin->posY];
- }
- if(coin->posX - 1 >= 0) // 金币的左侧金币翻转条件
- {
- coinBtn[coin->posX - 1][coin->posY]->changeFlag();
- this->gameArray[coin->posX - 1][coin->posY] = !this->gameArray[coin->posX - 1][coin->posY];
- }
- if(coin->posY + 1 <= 3) // 金币的上侧金币翻转条件
- {
- coinBtn[coin->posX][coin->posY + 1]->changeFlag();
- this->gameArray[coin->posX][coin->posY + 1] = !this->gameArray[coin->posX][coin->posY + 1];
- }
- if(coin->posY - 1 >= 0) // 金币的下侧金币翻转条件
- {
- coinBtn[coin->posX][coin->posY - 1]->changeFlag();
- this->gameArray[coin->posX][coin->posY - 1] = !this->gameArray[coin->posX][coin->posY - 1];
- }
-
- // 判断是否胜利
- this->isWin = true;
- for(int i = 0; i < 4;i ++)
- {
- for(int j = 0; j < 4; j++)
- {
- if(coinBtn[i][j]->flag == false) { // 只要有一个是反面,那就算失败
- this->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;
- }
- }
- }
- });
已经胜利就屏蔽点击,下图已经是点击不了了

已经创建了图片,但是在屏幕的上方,我们在后面写一个函数,用于这个胜利的图片掉下来的效果i哦

- // 胜利图像绘制
- 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());
加入头文件 #include

- // 将胜利的图片从上往下移动
- 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() + 134, winLabel->width(), winLabel->height()));
- // 设置曲线
- animation->setEasingCurve(QEasingCurve::OutBounce);
- // 执行动画
- animation->start();
1、通关后不能再点击,这是因为硬币 isWin 含有初始值为 true,我们可以在构造函数的时候设置硬币的游戏是否胜利为 false

2、手速快出问题了,因为只禁用了一个按钮

解决方案是先把所有按钮禁用了

等完成动画后再开启所有按钮

增加多媒体模块

包含头文件




后面的和前面类似处理,
选择关卡音效


游戏关卡音效



音乐可以循环播放, Qsound 对象里面有 setLoops(10) 这个方法,设置为循环 10 次,也可以 setLoops(-1) 无限循环
因为每次切换场景,如果移动了窗口,新的窗口所在的位置和原来的窗口可能不同








后面可以用其他的打包软件(就是变成压缩包,别人下载后解压用的)