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
中的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) {// 胜利了// 将所有按钮的胜利标志改为truefor (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()));// 设置缓和曲线 38valueanimation->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 项目打包
**第一步 **
第二步
第三步
第四步 还有要确保第三步要加入到系统环境变量中否则无法执行第四步
相关文章:

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.…...
SpringMVC之文件的下载
系列文章目录 提示:这里可以添加系列文章的所有文章的目录,目录需要自己手动添加 SpringMVC之文件的下载 提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 系列文章目录前言一、文件下载实现…...

计算机组成原理第6章-(算术运算)【下】
移位运算 对于有符号数的移位称为算术移位,对于无符号数的移位称为逻辑移位。 算术移位规则【极其重要】 对于正数的算术移位,且不管是何种机器数【原码、反码、补码】,移位后出现的空位全部填0。 而对于负数的算术移位,机器数不同,移位后的规则也不同。 对于负数的原…...
【开题报告】基于微信小程序的校园资讯平台的设计与实现
1.选题背景与意义 随着移动互联网的快速发展,微信成为了人们日常生活中不可或缺的工具之一。在校园生活中,学生们对于校园资讯的获取和交流需求也越来越高。然而,传统的校园资讯发布方式存在信息不及时、传播范围有限等问题,无法…...
VUE前端导出文件之file-saver插件
VUE前端导出文件之file-saver插件 安装 npm install file-saver --save # 如使用TS开发,可安装file-saver的TypeScript类型定义 npm install types/file-saver --save-dev如果需要保存大于 blob 大小限制的非常大的文件,或者没有 足够的 RAM࿰…...

【Earth Engine】协同Sentinel-1/2使用随机森林回归实现高分辨率相对财富(贫困)制图
目录 1 简介与摘要2 思路3 效果预览4 代码思路5 完整代码6 后记 1 简介与摘要 最近在做一些课题,需要使用Sentinel-1/2进行机器学习制图。 然后想着总结一下相关数据和方法,就花半小时写了个代码。 然后再花半小时写下这篇博客记录一下。 因为基于多次拍…...

C++ 检测 是不是 com组件 的办法 已解决
在日常开发中,遇到动态库和 com组件库的调用 无法区分。检测是否com组件的办法 在头部文件,引入文件 如果能编译成功说明是 com组件,至于动态库如何引入,还在观察中 最简单办法 regsvr32 TerraExplorerX.dll 是com 组件 regs…...

linux buffer的回写的触发链路
mark_buffer_dirty中除了会标记dirty到buffer_head->state、page.flag、folio->mapping->i_pages外,还会调用inode所在文件系统的dirty方法(inode->i_sb->s_op->dirty_inode)。然后为inode创建一个它所在memory group的wri…...

Lambda表达式超详解
目录 背景 Lambda表达式的用法 函数式接口 Lambda表达式的基本使用 语法精简 变量捕获 匿名内部类 匿名内部类中的变量捕获 Lambda的变量捕获 Lambda表达式在类集中的使用 Collection接口 List接口 Map接口 总结 背景 Lambda表达式是Java SE 8中的一个重要的新特性.…...

西门子博途与菲尼克斯无线蓝牙模块通讯
菲尼克斯无线蓝牙模块 正常运行时,可以使用基站控制字0发送00E0(得到错误代码命令) 正常运行时,可以使用基站控制字0发送00E0(得到错误代码命令)得到各个无线I/O是否连 接的信号(状态字IN word 1的第2、6、10位) 小车1连接状态 小车2连接状态 小车3连接状态 1#小车自…...

vue2 之 实现pdf电子签章
一、前情提要 1. 需求 仿照e签宝,实现pdf电子签章 > 拿到pdf链接,移动章的位置,获取章的坐标 技术 : 使用fabric pdfjs-dist vuedraggable 2. 借鉴 一位大佬的代码仓亏 : 地址 一位大佬写的文章 :地址 3. 优化 在大佬的代码…...

什么是MVC?MVC框架的优势和特点
目录 一、什么是MVC 二、MVC模式的组成部分和工作原理 1、模型(Model) 2、视图(View) 3、控制器(Controller) 三、MVC模式的工作过程如下: 用户发送请求,请求由控制器处理。 …...
主从复制mysql-replication | Replication故障排除
主从复制mysql-replication 准备环境 #防火墙 selinux systemctl stop firewalld --now &&setenforce 0 #修改主机名:hostnamectl set-hostname 名字 tip:vim /etc/sysconfig/network-scripts/ifcfg-ens33 BOOTPRTOTstatic IPADDR192.168.100.…...

基于Java SSM框架实现教学质量评价评教系统项目【项目源码+论文说明】计算机毕业设计
基于java的SSM框架实现教学质量评价评教系统演示 摘要 随着科学技术的飞速发展,社会的方方面面、各行各业都在努力与现代的先进技术接轨,通过科技手段来提高自身的优势,教学质量评价系统当然也不能排除在外。教学质量评价系统是以实际运用为…...

03|模型I/O:输入提示、调用模型、解析输出
03|模型I/O:输入提示、调用模型、解析输出 从这节课开始,我们将对 LangChain 中的六大核心组件一一进行详细的剖析。 模型,位于 LangChain 框架的最底层,它是基于语言模型构建的应用的核心元素,因为所谓 …...

springcloud-gateway-2-鉴权
目录 一、跨域安全设置 二、GlobalFilter实现全局的过滤与拦截。 三、GatewayFilter单个服务过滤器 1、原理-官方内置过滤器 2、自定义过滤器-TokenAuthGatewayFilterFactory 3、完善TokenAuthGatewayFilterFactory的功能 4、每一个服务编写一个或多个过滤器,…...

实现一个最简单的内核
更好的阅读体验,请点击 YinKai s Blog | 实现一个最简单的内核。 这篇文章带大家实现一个最简单的操作系统内核—— Hello OS。 PC 机的引导流程 我们这里将借助 Ubuntu Linux 操纵系统上的 GRUB 引导程序来引导我们的 Hello OS。 首先我们得了解一下&a…...

2024华为OD机试真题指南宝典—持续更新(JAVAPythonC++JS)【彻底搞懂算法和数据结构—算法之翼】
PC端可直接搜索关键词 快捷键:CtrlF 年份关键字、题目关键字等等 注意看本文目录-快速了解本专栏 文章目录 🐱2024年华为OD机试真题(马上更新)🐹2023年华为OD机试真题(更新中)🐶新…...

【12.23】转行小白历险记-算法02
不会算法的小白不是好小白,可恶还有什么可以难倒我这个美女的,不做花瓶第二天! 一、螺旋矩阵 59. 螺旋矩阵 II - 力扣(LeetCode) 1.核心思路:确定循环的路线,左闭右开循环,思路简…...
k8s部署nginx-ingress服务
k8s部署nginx-ingress服务 经过大佬的拷打,终于把这块的内容配置完成了。 首先去 nginx-ingress官网查看相关内容。 核心就是这个: kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.2/deploy/static/prov…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...

Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
AtCoder 第409场初级竞赛 A~E题解
A Conflict 【题目链接】 原题链接:A - Conflict 【考点】 枚举 【题目大意】 找到是否有两人都想要的物品。 【解析】 遍历两端字符串,只有在同时为 o 时输出 Yes 并结束程序,否则输出 No。 【难度】 GESP三级 【代码参考】 #i…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...

dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...

10-Oracle 23 ai Vector Search 概述和参数
一、Oracle AI Vector Search 概述 企业和个人都在尝试各种AI,使用客户端或是内部自己搭建集成大模型的终端,加速与大型语言模型(LLM)的结合,同时使用检索增强生成(Retrieval Augmented Generation &#…...

搭建DNS域名解析服务器(正向解析资源文件)
正向解析资源文件 1)准备工作 服务端及客户端都关闭安全软件 [rootlocalhost ~]# systemctl stop firewalld [rootlocalhost ~]# setenforce 0 2)服务端安装软件:bind 1.配置yum源 [rootlocalhost ~]# cat /etc/yum.repos.d/base.repo [Base…...

逻辑回归暴力训练预测金融欺诈
简述 「使用逻辑回归暴力预测金融欺诈,并不断增加特征维度持续测试」的做法,体现了一种逐步建模与迭代验证的实验思路,在金融欺诈检测中非常有价值,本文作为一篇回顾性记录了早年间公司给某行做反欺诈预测用到的技术和思路。百度…...

永磁同步电机无速度算法--基于卡尔曼滤波器的滑模观测器
一、原理介绍 传统滑模观测器采用如下结构: 传统SMO中LPF会带来相位延迟和幅值衰减,并且需要额外的相位补偿。 采用扩展卡尔曼滤波器代替常用低通滤波器(LPF),可以去除高次谐波,并且不用相位补偿就可以获得一个误差较小的转子位…...