文章目录
- 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
中的MousePressEvent
和 MouseReleaseEvent
// 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
并修改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 项目打包
**第一步 **
第二步
第三步
第四步 还有要确保第三步要加入到系统环境变量中否则无法执行第四步