目录
前言
一、游戏整体分析
二、创建项目
三、添加资源
四、主界面实现
1、设置游戏主场景基本配置
2、设置背景图片
3、创建开始按钮并设置动画
4、创建关卡选择界面并实现主界面与其的切换
五、关卡选择界面实现
1、设置关卡选择场景基本配置
2、设置关卡选择场景的背景图片
3、返回按钮的相关功能设置
4、创建选择关卡按钮
5、实现点击关卡按钮跳转到对应的翻金币场景功能
六、翻金币场景实现
1、设置游戏场景基本配置
2、设置游戏场景的背景图片
3、设置返回按钮及其功能
4、在左下角显示当前关卡号
5、创建游戏界面中间的金币背景
6、创建金币类
7、引入关卡数据
8、初始化各个关卡
9、实现翻金币特效
10、实现周围金币跟着翻转功能
11、判断是否胜利
12、显示胜利图片
13、胜利后禁用按钮
七、添加音效
八、优化项目
1、切换三个场景的进入与返回都在同一个位置下
2、在游戏胜利瞬间点了其他金币使得未胜利(速度过快)
九、打包项目
前言
对QT的相关知识与控件进行简单的学习之后,通过实现“翻金币游戏”来巩固与实践所学的QT知识。在制作过程中是根据以下视频的教程进行制作的。感兴趣的可以移步视频。
02 案例简介_哔哩哔哩_bilibili
制作翻金币游戏的图片及音频都是直接利用课程提供的的现有资料,如果在课程没有找到的话,可以通过以下链接下载对应的资料。
链接:https://pan.baidu.com/s/1IxWIa47V0L_WuLu41pu7Tw
提取码:lje1
本文将从头到尾循序渐进地对“CoinFlip”游戏的实现进行介绍,会相对详细一点,主要从整体分析、项目创建、项目实现、项目打包这几个方面进行介绍。
打包好的游戏链接如下,可以直接下载后运行就可以玩了。
链接:https://pan.baidu.com/s/1Nfzstm1AbsmwfKPL_ac4_Q
提取码:rqa6
添加自己创建的头文件使用以下形式添加
#include "xxxx.h"
一、游戏整体分析
这个小游戏总的来看需要我们实现的有三个界面还有两个自定义控件。
一是游戏开始界面,对应图1;其中,开始界面需要一个开始按钮,能够进行页面切换,因而我们需要自定义一个控件,也就是MyPushButton,父类为QPushButton,进而实现这一按钮的设计与动作实现。
二是关卡选择界面,对应图2;其中,关卡按钮还有返回按钮back也是利用我们自定义的控件来实现。
三是关卡界面,对应图3。其中,因为游戏规则是点击银币然后上下左右的硬币跟着翻转,需要利用多个图片来实现金币翻转,因此我们需要创建一个金币类来实现金币所需要进行的对应操作。
而图4只是在玩家胜利之后在关卡界面的基础上弹出成功图片,因而不需要另外创建界面。三个界面就对应三个类,分别是mainscene、chooselevelscene、playscene;自定义的类有两个,分别是mypushbutton、mycoin类。
二、创建项目
打开QT Creator(我用的是5.12.9版本)-->创建新项目-->选择Qt Widgets Application-->下一步
设置项目名称“CoinFlip”-->选择存放路径-->下一步
从主界面开始实现,设置类名为“MainScene”-->选择父类为“QMainWindow”-->勾选“Generate form”-->下一步
然后项目创建成功,项目结构如下:
三、添加资源
将以下资源添加到项目中
将前言中准备好的资料下载保存到电脑桌面-->点击项目右键选择“Add New”-->选择“QT”-->选择“QT Resource File”-->下一步-->设置名称“res”-->下一步-->完成
点击完成后会出现如下界面,点击“Add Prefix”-->将前缀设置为“/”即可。
(test是为了方便截图添加资源等操作,不用在意这个)
然后将前面保存的资料里面的res文件夹,复制粘贴到CoinFlip项目中(也就是前面创建项目时你选择的路径)【忽略test,不是重点】
回到项目界面,点击“Add Files”-->选择刚才添加的"res"文件-->选中文件夹中所有文件-->点击打开-->成功添加资源
添加成功后如下:
四、主界面实现
1、设置游戏主场景基本配置
首先我们利用ui界面设计来实现菜单栏的设计——双击“mainscene.ui”打开界面设计——界面如下
设计菜单栏——在中间上方“在这里输入”这里输入“开始”,然后点击“开始”,在下方的“在这里输入”,输入“Quit”,然后再找到其text属性将其改成退出。
【之所以先输入Quit是因为控件名称是自动生成的,总不能生成"action退出"】
然后移除自带的状态栏,然后就可以回到"mainscene.cpp",在构造函数进行代码的编写
实现场景的基本配置,设置窗口大小、应用的图标、还有窗口的标题,然后实现退出的功能实现,利用信号和lambda表达式来实现
代码如下:
//设置固定大小
this->setFixedSize(350,550);
//设置应用图片
this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
//设置窗口标题
this->setWindowTitle("一起来翻金币吧");
//点击退出,退出程序
connect(ui->actionQuit,&QAction::triggered,[=](){
this->close();
});
运行界面结果如下:
2、设置背景图片
需要重写MainScene的PaintEvent事件,先在“mainscene.h”中添加声明,然后在“mainscene.cpp”中实现背景图片的添加。
【注:PaintEvent函数在项目运行时会自动调用生成,因此只需要重写而不需要在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.width(),pix.height(),pix);
}
运行界面结果如下:
3、创建开始按钮并设置动画
需要实现以下场景——开始按钮点击后有弹跳效果
这个效果我们利用自定义控件实现(因为QPushButton不会自带这类特效),我们可以自己封装出一个按钮控件,来实现这些效果。此时需要我们创建一个新的类——MyPushButton
选择项目右键,选择“Add New”-->选择“C++”-->选择“C++ Class”-->下一步-->设置类名“MyPushButton”-->选择父类“QWidget”-->下一步-->完成
创建成功后项目结构如下:
分别打开“mypushbutton.h”和“mypushbutton.cpp”修改其继承的父类为QPushButton
修改后如下:
我们希望点击按钮之后能够看到按钮的变化,即可以让MyPushButton提供正常显示的图片以及按下后显示的图片,比如上下跳跃或者变成被按下的样子,因此我们需要重写MyPushButton类的构造函数,使得它能够接受两个参数,分别是正常显示的图片的路径以及按下后要显示的图片路径。
先在mypushbutton.h文件中对重载的构造函数进行声明以及定义两个成员变量,分别用来存放对应图片的路径。
代码如下:
//normalImg 代表正常显示的图片
//pressImg 代表按下后显示的图片,默认为空
MyPushButton(QString normalImg,QString pressImg = "");
private:
QString normalImgPath; //默认显示图片路径
QString pressedImgPath; //按下后显示图片路径
然后就是在“mypushbutton.cpp”中重写构造函数的具体实现代码
代码如下:
MyPushButton::MyPushButton(QString normalImg,QString pressImg){
//成员变量normalImgPath保存正常显示图片路径
normalImgPath=normalImg;
//成员变量pressedImgPath保存按下后显示的图片
pressedImgPath=pressImg;
//创建QPixmap对象
QPixmap pixmap;
//判断是否能够加载正常显示的图片,若不能提示加载失败
bool ret=pixmap.load(normalImgPath);
if(!ret){
qDebug()<<normalImg<<"加载图片失败!";
}
//设置图片的固定尺寸
this->setFixedSize(pixmap.width(),pixmap.height());
//设置不规则图片的样式表
this->setStyleSheet("QPushButton{border:0px;}");
//设置图标
this->setIcon(pixmap);
//设置图标大小
this->setIconSize(QSize(pixmap.width(),pixmap.height()));
}
前面两步做好之后,我们就可以在主界面上添加开始按钮,打开“mainscene.cpp”
代码如下:
//利用自定义控件定义开始按钮,同时设置其图标 这里就运用到了重载的构造函数
MyPushButton * startBtn = new MyPushButton(":/res/MenuSceneStartButton.png");
//将按钮的父亲设置为this,也就是添加到对象树上,这样当窗口析构时其也会被析构
startBtn->setParent(this);
//设置开始按钮在主界面的位置 也就是x,y
startBtn->move(this->width()*0.5-startBtn->width()*0.5,this->height()*0.7);
运行界面结果如下:
此时的开始按钮点击没有任何特效,因此我们需要添加点击后上下跳跃的特效,我们定义两个函数zoom1()和zoom2(),分别用来实现向下跳跃和向上跳跃,这两个函数放在mypushbutton类中实现。
先在mypushbutton.h中添加声明
在mypushbutton.cpp中添加实现代码,两个函数代码基本一致,只有位置有不同
代码如下:
//向下跳跃
void MyPushButton::zoom1()
{
//创建动画对象
QPropertyAnimation * animation1 = new QPropertyAnimation(this,"geometry");
//设置时间间隔,单位毫秒
animation1->setDuration(200);
//创建起始位置
animation1->setStartValue(QRect(this->x(),this->y(),this->width(),this->height()));
//创建结束位置
animation1->setEndValue(QRect(this->x(),this->y()+10,this->width(),this->height()));
//设置缓和曲线,QEasingCurve::OutBounce 为弹跳效果
animation1->setEasingCurve(QEasingCurve::OutBounce);
//开始执行动画
animation1->start();
}
//向上跳跃
void MyPushButton::zoom2()
{
//创建动画对象
QPropertyAnimation * animation1 = new QPropertyAnimation(this,"geometry");
//设置时间间隔,单位毫秒
animation1->setDuration(200);
//创建起始位置
animation1->setStartValue(QRect(this->x(),this->y()+10,this->width(),this->height()));
//创建结束位置
animation1->setEndValue(QRect(this->x(),this->y(),this->width(),this->height()));
//设置缓和曲线,QEasingCurve::OutBounce 为弹跳效果
animation1->setEasingCurve(QEasingCurve::OutBounce);
//开始执行动画
animation1->start();
}
然后到“mainscene.cpp”中连接信号槽,实现点击开始按钮后按钮上下跳动的效果
代码如下:
//监听点击事件,执行特效
connect(startBtn,&MyPushButton::clicked,[=](){
startBtn->zoom1(); //向下跳跃
startBtn->zoom2(); //向上跳跃
});
此时点击运行项目,点击开始按钮就可以看到上下跳跃的效果。
【到这一步主场景的创建已经基本实现了,接下来就是创建第二个界面——关卡选择,同时实现点击开始按钮后跳转到关卡选择界面】
4、创建关卡选择界面并实现主界面与其的切换
同创建自定义控件一样,我们创建关卡选择界面需要创建一个新的类——“ChooseLevelScene”
选择项目右键,选择“Add New”-->选择“C++”-->选择“C++ Class”-->下一步-->设置类名“ChooseLevelScene”-->选择父类“QMainWindow”-->下一步-->完成
创建成功后项目结构如下:
然后就是实现点击开始按钮进入选择关卡场景的功能。因此我们需要在“mainscene.h”中添加关卡场景的对象,以此来实现在点击按钮之后,通过"chooseScene->show()"来实现界面切换。
代码如下:
//选择关卡场景
ChooseLevelScene *chooseScene = new ChooseLevelScene;
然后就是在“mainscene.cpp”中点击开始按钮后的lambda表达式中添加代码实现界面切换。在调用zoom2()后面添加即可。
代码如下:
//延时0.5秒后 进入选择场景
QTimer::singleShot(500, this,[=](){
this->hide();
chooseScene->show();
});
运行程序,执行特效后延时0.5秒进入选择关卡场景,结果如下:(因为关卡选择界面还未进行设置,因此是空白界面)
五、关卡选择界面实现
1、设置关卡选择场景基本配置
直接在“chooselevelscene.cpp”中的构造函数中去设置界面的配置,如大小、菜单栏等等
代码如下:
//设置窗口固定大小
this->setFixedSize(350,550);
//设置图标
this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
//设置标题
this->setWindowTitle("选择关卡");
//创建菜单栏
QMenuBar * bar = this->menuBar();
this->setMenuBar(bar);
//创建开始菜单
QMenu * startMenu = bar->addMenu("开始");
//创建按钮菜单项
QAction * quitAction = startMenu->addAction("退出");
//点击退出 退出游戏
connect(quitAction,&QAction::triggered,[=](){this->close();});
此时运行结果如下:
2、设置关卡选择场景的背景图片
跟主场景一样,需要重写paintEvent函数,先在“chooselevelscene.h”中进行声明,然后在“chooselevelscene.cpp”中进行重写
代码如下:
void ChooseLevelScene::paintEvent(QPaintEvent *)
{
//创建画家,制定绘图设备
QPainter painter(this);
//创建QPixmap对象
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);
}
背景图片设置完成后,我们来设置右下角的返回按钮,在“chooselevelscene.cpp”中运用自定义控件“mypushbutton”来创建返回按钮。
代码如下:
//返回按钮
MyPushButton * closeBtn = new MyPushButton(":/res/BackButton.png",":/res/BackButtonSelected.png");
closeBtn->setParent(this);
//设置按钮位置
closeBtn->move(this->width()-closeBtn->width(),this->height()-closeBtn->height());
因为返回按钮是有正常显示图片和点击后显示图片的两种模式,所以我们需要重写MyPushButton中的 MousePressEvent和MouseReleaseEvent,从而实现不同的显示效果。
先在“mypushbutton.h”中进行声明,然后在“mypushbutton.cpp”中实现
代码如下:
//鼠标事件
void MyPushButton::mousePressEvent(QMouseEvent *e)
{
if(pressedImgPath != "") //选中路径不为空,显示选中图片
{
QPixmap pixmap;
bool ret = pixmap.load(pressedImgPath);
if(!ret)
{
qDebug() << pressedImgPath << "加载图片失败!";
}
this->setFixedSize( pixmap.width(), pixmap.height() );
this->setStyleSheet("QPushButton{border:0px;}");
this->setIcon(pixmap);
this->setIconSize(QSize(pixmap.width(),pixmap.height()));
}
//交给父类执行按下事件
return QPushButton::mousePressEvent(e);
}
void MyPushButton::mouseReleaseEvent(QMouseEvent *e)
{
if(normalImgPath != "") //选中路径不为空,显示选中图片
{
QPixmap pixmap;
bool ret = pixmap.load(normalImgPath);
if(!ret)
{
qDebug() << normalImgPath << "加载图片失败!";
}
this->setFixedSize( pixmap.width(), pixmap.height() );
this->setStyleSheet("QPushButton{border:0px;}");
this->setIcon(pixmap);
this->setIconSize(QSize(pixmap.width(),pixmap.height()));
}
//交给父类执行 释放事件
return QPushButton::mouseReleaseEvent(e);
}
运行界面,结果如下:
3、返回按钮的相关功能设置
我们点击返回按钮后,设置延时0.5秒后隐藏自身,并且发送自定义的信号“chooseSceneBack()”,告诉外界自身已经选择了返回按钮。
我们先在“chooselevelscene.h”中声明自定义信号(但不需要实现)
然后在“chooselevelscene.cpp”中添加返回按钮的功能:
代码如下:
//返回按钮功能实现
connect(closeBtn,&MyPushButton::clicked,[=](){
QTimer::singleShot(500, this,[=](){
this->hide();
//触发自定义信号,关闭自身
emit this->chooseSceneBack();
}
);
});
在主场景MainScene中我们添加功能——点击开始按钮显示选择关卡的同时,监听选择关卡的返回按钮消息,接受到返回按钮消息的话则展示主场景界面。在“mainscene.cpp”中添加代码
代码如下:
//监听选择场景的返回按钮
connect(chooseScene,&ChooseLevelScene::chooseSceneBack,[=](){
this->show();
});
此时运行项目,点击开始按钮切换到关卡选择场景,点击back按钮返回主场景。
4、创建选择关卡按钮
现在我们需要实现下图中20个关卡的按钮,直接在“chooselevelscene.cpp”中添加代码,代码还是添加在构造函数中。
代码如下:
//创建关卡按钮
for(int i = 0 ; i < 20;i++)
{
//运用自定义控件来生成关卡按钮
MyPushButton * menuBtn = new MyPushButton(":/res/LevelIcon.png");
menuBtn->setParent(this);
//设置每个关卡按钮的位置 根据个人喜好还有前面设置的场景大小来设置
menuBtn->move(40 + (i%4)*70 , 135+ (i/4)*70);
//按钮上显示的文字
QLabel * label = new QLabel;
label->setParent(this);
label->setFixedSize(menuBtn->width(),menuBtn->height());
label->setText(QString::number(i+1));//显示关卡数字
label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); //设置居中
label->move(40 + (i%4)*70 , 135+ (i/4)*70);//让数字在按钮上显示
label->setAttribute(Qt::WA_TransparentForMouseEvents,true); //鼠标事件穿透
}
5、实现点击关卡按钮跳转到对应的翻金币场景功能
同前面的场景创建一样,我们需要重新定义一个类——PlayScene,用来创建翻金币场景,总共有20关,但不需要创建20个类,只需要一个即可,不同关卡利用代码实现即可。
选择项目右键,选择“Add New”-->选择“C++”-->选择“C++ Class”-->下一步-->设置类名“PlayScene”-->选择父类“QMainWindow”-->下一步-->完成
创建成功够项目结构如下:
因为我们需要在关卡选择场景点击对应按钮然后跳转到对应的游戏界面,因此我们可以通过在关卡选择场景“chooselevelscene.h”中添加成员变量pScene用来指向不同关卡对应的游戏界面。
然后在关卡选择场景“chooselevelscene.cpp”中利用信号和槽,监听所选择的关卡并使得上面定义的场景指针指向对应的场景。此时new PlayScene(i+1)会报错,因为我们没有重写创建场景的构造函数(下一部分实现)。
六、翻金币场景实现
1、设置游戏场景基本配置
创建对应的关卡场景,需要接受关卡选择界面传递过来的关卡号,因此我们在“playscene.h”中定义变量接收关卡号,同时声明重载构造函数
然后在“playscene.cpp”中重写构造函数来实现游戏场景的搭建,比如窗口大小、菜单栏等基础功能实现
代码如下:
PlayScene::PlayScene(int index)
{
//qDebug() << "当前关卡为"<< index;
this->levalIndex = index;
//设置窗口固定大小
this->setFixedSize(320,588);
//设置图标
this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
//设置标题
this->setWindowTitle("翻金币");
//创建菜单栏
QMenuBar * bar = this->menuBar();
this->setMenuBar(bar);
//创建开始菜单
QMenu * startMenu = bar->addMenu("开始");
//创建按钮菜单项
QAction * quitAction = startMenu->addAction("退出");
//点击退出 退出游戏
connect(quitAction,&QAction::triggered,[=](){this->close();});
}
2、设置游戏场景的背景图片
跟前面一样,需要重写paintEvent函数,先在“playscene.h”中进行声明,然后在“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);
}
此时运行项目,点击任意关卡进入,游戏场景如下:
3、设置返回按钮及其功能
与关卡选择场景一样,我们需要设置其按下的特效,还有就是自定义返回信号给关卡选择场景用于捕捉,一旦捕捉到则返回关卡选择场景。
先在“playscene.h”中声明自定义信号,然后在“playscene.cpp”中创建返回按钮并监听返回按钮
代码如下:
//返回按钮
MyPushButton * closeBtn = new MyPushButton(":/res/BackButton.png",":/res/BackButtonSelected.png");
closeBtn->setParent(this);
closeBtn->move(this->width()-closeBtn->width(),this->height()-closeBtn->height());
//返回按钮功能实现
connect(closeBtn,&MyPushButton::clicked,[=](){
QTimer::singleShot(500, this,[=](){
this->hide();
//触发自定义信号,关闭自身,该信号写到 signals下做声明
emit this->chooseSceneBack();
}
);
});
然后在关卡选择场景中设置监听palyscene的返回信号,在“chooselevelscene.cpp”中添加代码
代码如下:
//PlayScene的返回按钮监听,删除该scene并将指针置为空
connect(pScene,&PlayScene::chooseSceneBack,[=](){
this->setGeometry(pScene->geometry());
this->show();
delete pScene;
pScene = NULL;
});
4、在左下角显示当前关卡号
在“palyscene.cpp”中添加代码来显示关卡号
代码如下:
//当前关卡标题
QLabel * label = new QLabel;
label->setParent(this);
QFont font;
font.setFamily("华文新魏");//设置字体
font.setPointSize(20);
label->setFont(font);
QString str = QString("Level: %1").arg(this->levalIndex);
label->setText(str);
label->setGeometry(QRect(15, this->height() - 50,170, 50)); //设置大小和位置
此时运行程序,随意选择关卡可看到左下角关卡号,结果如下:
5、创建游戏界面中间的金币背景
在“playscene.cpp”中添加代码
代码如下:
//创建金币的背景图片
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(1).png"));
label->setParent(this);
label->move(70 + i*50,200+j*50);//所在位置,对应x,y
}
}
此时运行项目,选择任意关卡,游戏界面如下:
6、创建金币类
我们在创建好金币背景之后,则需要把金币添加到对应位置上。而金币它需要能被点击,然后点击后还有翻转特效,因此我们可以单独将其封装成一个金币类方便使用与创建。
同前面的场景创建一样,我们需要重新定义一个类——MyCoin
选择项目右键,选择“Add New”-->选择“C++”-->选择“C++ Class”-->下一步-->设置类名“MyCoin”-->选择父类“QWidget”-->下一步-->完成
创建好后项目结构如下:
然后修改MyCoin类的父类为QPushButton
在资源图片中,我们可以看到,金币翻转的效果是利用多张图片切换而形成的,而以下八张图片中,正常显示的图片为金币Coin0001或者是银币 Coin0008这两种图。因此我们在创建一个金币对象时候,应该提供一个参数,代表着传入的是金币资源路径还是银币资源路径,根据路径我们创建不同样式的图案。所以我们要重写金币的构造函数。
先在“mycoin.h”中声明重载构造函数,然后在“mycoin.cpp”中实现重载构造函数
代码如下:
MyCoin::MyCoin(QString butImg)
{
QPixmap pixmap;
bool ret = pixmap.load(butImg);
if(!ret)
{
qDebug() << butImg << "加载图片失败!";
}
this->setFixedSize( pixmap.width(), pixmap.height() );
this->setStyleSheet("QPushButton{border:0px;}");
this->setIcon(pixmap);
this->setIconSize(QSize(pixmap.width(),pixmap.height()));
}
然后我们去游戏场景中添加金币
代码如下:
//金币对象
MyCoin * coin = new MyCoin(":/res/Coin0001.png");
coin->setParent(this);
coin->move(72 + i*50,204+j*50);
此时运行项目,选择任意关卡,游戏界面如下:
7、引入关卡数据
每个关卡的初始化界面都不一样,因此需要我们引用一个现有的关卡文件,文件中记录了各个关卡的金币排列清空,也就是二维数组的数值。(文件在前言中有提供,就是dataconfig.h和dataconfig.cpp文件)
首先先将dataConfig.h 和 dataConfig.cpp文件放入到当前项目下:
选择项目右键,选择“添加现有文件”-->分别选择两个文件添加
添加成功后项目结构如下:
8、初始化各个关卡
首先,我们在playscene.h中声明一个成员变量,用于记录当前关卡的二维数组。然后在playscene.cpp中初始化这个二维数组,记得添加头文件“dataconfig.h”。添加代码的位置可以自己选择,可以放在前面,也可以跟我一样放
代码如下:
//初始化二维数组
dataConfig config;
for(int i = 0 ; i < 4;i++){
for(int j = 0 ; j < 4; j++)
{
gameArray[i][j] = config.mData[this->levalIndex][i][j];
}
}
初始化成功后,我们需要在MyCoin类中,定义属性 posX,posY,以及flag,这三个属性分别代表了,该金币在二维数组中 x的坐标,y的坐标,以及当前的正反标志。(正反标志是用于判断该图片是金币还是银币)
然后对金币进一步进行初始化,前面的添加金币仅仅只是添加金币,看金币是否创建成功,现在就是对关卡进行初始化,有金币也有银币
代码如下:
//金币对象
QString img;
if(gameArray[i][j] == 1)
{
img = ":/res/Coin0001.png";
}
else
{
img = ":/res/Coin0008.png";
}
MyCoin * coin = new MyCoin(img);
coin->setParent(this);
coin->move(72 + i*50,204+j*50);
coin->posX = i; //记录x坐标
coin->posY = j; //记录y坐标
coin->flag =gameArray[i][j]; //记录正反标志
此时运行项目,任意点击关卡,可以看到游戏界面已经初始化成功,结果如下:
9、实现翻金币特效
关卡的初始化完成后,下面就实现点击金币进行翻转的效果,我们在MyCoin类中实现该方法。先在“mycoin.h”中进行声明,然后在“mycoin.cpp”中实现翻转方法,同时在构造函数中创建定时器
代码如下:
//初始化定时器
timer1 = new QTimer(this);
timer2 = new QTimer(this);
void MyCoin::changeFlag()
{
if(this->flag) //如果是正面,执行下列代码
{
timer1->start(30);
this->flag = false;
}
else //反面执行下列代码
{
timer2->start(30);
this->flag = true;
}
}
设置好了之后我们就要在构造函数中实现金币和银币的翻转效果,代码添加在刚才设置的定时器后面就可以了
代码如下:
//监听正面翻转的信号槽
connect(timer1,&QTimer::timeout,[=](){
QPixmap pixmap;
QString str = QString(":/res/Coin000%1.png").arg(this->min++);
pixmap.load(str);
this->setFixedSize(pixmap.width(),pixmap.height() );
this->setStyleSheet("QPushButton{border:0px;}");
this->setIcon(pixmap);
this->setIconSize(QSize(pixmap.width(),pixmap.height()));
if(this->min > this->max) //如果大于最大值,重置最小值,并停止定时器
{
this->min = 1;
timer1->stop();
}
});
connect(timer2,&QTimer::timeout,[=](){
QPixmap pixmap;
QString str = QString(":/res/Coin000%1.png").arg((this->max)-- );
pixmap.load(str);
this->setFixedSize(pixmap.width(),pixmap.height() );
this->setStyleSheet("QPushButton{border:0px;}");
this->setIcon(pixmap);
this->setIconSize(QSize(pixmap.width(),pixmap.height()));
if(this->max < this->min) //如果小于最小值,重置最大值,并停止定时器
{
this->max = 8;
timer2->stop();
}
});
接着我们监听每个按钮的点击效果,并翻转金币,查看翻转特效是否成功(此时只是测试,因此只有点击的金币会进行翻转)
代码如下:
connect(coin,&MyCoin::clicked,[=](){
//qDebug() << "点击的位置: x = " << coin->posX << " y = " << coin->posY ;
coin->changeFlag();
gameArray[i][j] = gameArray[i][j] == 0 ? 1 : 0; //数组内部记录的标志同步修改
});
此时运行项目,任意选择关卡,点击任意金币,查看特效:
此时翻转金币特效已经实现了,但是如果快速点击,会在金币还没有执行一个完整动作之后 ,又继续开始新的动画,我们应该在金币做动画期间,禁止再次点击,并在完成动画后,开启点击。
我们可以在MyCoin类中加入一个标志 isAnimation 代表是否正在做翻转动画。并且在changFlag()函数中将标志设为true,在做完动画后修改为false
同时我们重写按钮的按下事件,判断如果正在执行动画,那么直接return掉,不要执行后续代码。先在“mycoin.h”声明,然后在“mycoin.cpp”实现
代码如下:
void MyCoin::mousePressEvent(QMouseEvent *e)
{
if(this->isAnimation )
{
return;
}
else
{
return QPushButton::mousePressEvent(e);
}
}
10、实现周围金币跟着翻转功能
在用户点击金币翻转的同时,我们让其上下左右的4个金币也进行延时翻转,代码写到监听点击金币下。此时我们还需要记录每个按钮的内容,所以我们将所有金币按钮也放到一个二维数组中,在playscene.h中声明
然后设置延时翻动其他周围金币,代码添加在“playscene.cpp”中
代码如下:
//翻转周围硬币
QTimer::singleShot(300, this,[=](){
//周围右侧的硬币翻转条件
if(coin->posX+1 <=3)
{
coinBtn[coin->posX+1][coin->posY]->changeFlag();
gameArray[coin->posX+1][coin->posY] = gameArray[coin->posX+1][coin->posY]== 0 ? 1 : 0;
}
//周围左侧的硬币翻转条件
if(coin->posX-1>=0)
{
coinBtn[coin->posX-1][coin->posY]->changeFlag();
gameArray[coin->posX-1][coin->posY] = gameArray[coin->posX-1][coin->posY]== 0 ? 1 : 0;
}
//周围上侧的硬币翻转条件
if(coin->posY+1<=3)
{
coinBtn[coin->posX][coin->posY+1]->changeFlag();
gameArray[coin->posX][coin->posY+1] = gameArray[coin->posX+1][coin->posY]== 0 ? 1 : 0;
}
//周围下侧硬币翻转条件
if(coin->posY-1>=0)
{
coinBtn[coin->posX][coin->posY-1]->changeFlag();
gameArray[coin->posX][coin->posY-1] = gameArray[coin->posX+1][coin->posY]== 0 ? 1 : 0;
}
});
11、判断是否胜利
我们在在playscene.h中加入 isWin标志,代表是否胜利。
默认设置为true,只要有一个银币,就将该值改为false,视为未成功。代码写到延时翻金币后 进行判断。
代码如下:
//判断是否胜利
this->isWin = true;
for(int i = 0 ; i < 4;i++)
{
for(int j = 0 ; j < 4; j++)
{
//qDebug() << coinBtn[i][j]->flag ;
if( coinBtn[i][j]->flag == false)
{
this->isWin = false;
break;
}
}
}
然后进行总的判断
12、显示胜利图片
我们将胜利的图片提前创建好,如果胜利触发了,将图片弹下来即可。创建胜利图片的代码在前面选个位置添加就可以了
代码如下:
//创建胜利图片
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());
然后就是在判断胜利之后弹出此图片
代码如下:
//如果isWin依然是true,代表胜利了!
if(this->isWin)
{
qDebug() << "胜利";
QPropertyAnimation * animation1 = new QPropertyAnimation(winLabel,"geometry");
animation1->setDuration(1000);
animation1->setStartValue(QRect(winLabel->x(),winLabel->y(),winLabel->width(),winLabel->height()));
animation1->setEndValue(QRect(winLabel->x(),winLabel->y()+114,winLabel->width(),winLabel->height()));
animation1->setEasingCurve(QEasingCurve::OutBounce);
animation1->start();
}
13、胜利后禁用按钮
当胜利后,应该禁用所有按钮的点击状态,可以在每个按钮中加入标志位 isWin,如果isWin为true,MousePressEvent直接return掉即可。
修改MousePressEvent中的代码
在判断胜利后禁用所有金币
代码如下:
//将所有按钮的胜利标志改为true,即禁用
for(int i = 0 ; i < 4;i++)
{
for(int j = 0 ; j < 4; j++)
{
coinBtn[i][j]->isWin = true;
}
}
七、添加音效
在“mainscene.cpp”中添加开始音效
QSound *startSound = new QSound(":/res/TapButtonSound.wav",this);
在后面添加上“multimedia”,然后重新构建一下即可
播放音效
在“chooselevelscene.cpp”中添加选择关卡音效
//选择关卡按钮音效
QSound *chooseSound = new QSound(":/res/TapButtonSound.wav",this);
在“chooselevelscene.cpp”还有“playscene.cpp”中分别添加返回按钮音效
//返回按钮音效
QSound *backSound = new QSound(":/res/BackButtonSound.wav",this);
在playscene.cpp中添加,翻金币的音效以及胜利的音效
//翻金币音效
QSound *flipSound = new QSound(":/res/ConFlipSound.wav",this);
//胜利按钮音效
QSound *winSound = new QSound(":/res/LevelWinSound.wav",this);
八、优化项目
1、切换三个场景的进入与返回都在同一个位置下
当我们移动场景后,如果进入下一个场景,发现场景还在中心位置,如果想设置场景的位置,需要添加如下下图中的代码:
在MainScene.cpp中添加:
在ChooseScene.cpp中添加:
2、在游戏胜利瞬间点了其他金币使得未胜利(速度过快)
解决这一bug,只需要我们设置在点击翻转的那一瞬间把所有按钮都禁用,在周围的金币都翻转之后再恢复
九、打包项目
点击左边的“电脑”-->选中项目-->选择“release”-->然后等待构建成功,可以再运行一遍
打开项目所在路径-->找到对应结尾为Release的文件夹-->点击“release”文件夹-->复制一份exe文件
在桌面任意创建一个文件夹-->将刚才复制的CoinFlip.exe复制粘贴到里面
在开始那里找到命令窗口打开-->输入cd 文件目录 转到桌面文件夹目录下-->输入
“windeployqt CoinFlip.exe”按下回车键等待完成
这样就是完成了,此时桌面的文件夹也会多很多文件
这样可执行程序有很多依赖选项,如果需要把程序给其他人使用就需要发整个文件夹,很麻烦
因此我们把整个文件夹打包为一个单一的可执行.exe文件。
安装Enigma virtual box工具(安装过程一直next就行)
Enigma virtual box官方链接:https://enigmaprotector.com/en/downloads.html
点击Add-->Add Folder Recursive-->选中文件夹-->OK
点击Files Options-->Compress Files-->OK-->点击右下角Process生成即可
然后打开文件夹,所生成的CoinFlip_boxed.exe就是打包好的程序了,想要发给别人的就只用发这一个文件就可以了