本篇将会根据前面讲解的关于Qt的相关内容进行一个实战项目,项目链接:翻金币案例
关于此项目比较好的博客地址如下:【QT】翻金币项目
整个项目的思维导图如下图所示:具体的XMind文件地址:
Qt翻金币项目XMind思维导图,见上。
![在这里插入图片描述](https://img-blog.csdnimg.cn/e584964fbec5478488878da59ec9a7df.png)
1 项目简介
翻金币项目是一款经典的益智类游戏,我们需要将金币都翻成同色,才视为胜利。
- 案例的一级页面如下图所示,采用
QMainWindow
,左上角的菜单栏中有 “开始” 和 “退出” 按钮,点击 “START” ,“START” 按钮上下跳动之后进入二级页面。
![在这里插入图片描述](https://img-blog.csdnimg.cn/05cf63f1918f44c9a97ad56fe352c4ed.png)
- 案例的二级页面如下图所示,采用
QMainWindow
,右下角的 “BACK” 按钮在按下和释放时会有不同的点击效果,“BACK”按钮按下之后会返回上一级页面,选择对应的关卡之后就会跳入对应的关卡页面。
- 下图为其中一个关卡的三级页面,以关卡1的页面为例,当进入页面后的初始页面如下,其翻转规则为:
点击一个按钮,其上下左右的按钮都会进行翻转
,因此选中下图中红色框选部分,红色框内及上下左右均会进行翻转,从银币翻转为金币,整个页面就均为金币,此时就代表了胜利。
![在这里插入图片描述](https://img-blog.csdnimg.cn/07acc17c822d42adbc7be2185d5e22d4.png)
下图中点击了箭头所指的金币,它和它上下左右的币都翻转为另一种类型,如下图所示
- 一旦胜利之后就会弹出如下按钮,除了弹出 “SUCCEED” ,还需要将金币的按钮禁掉,这样就不可以再点动了,但是 “BACK” 按钮还是可以点动的
2 项目基本配置
2.1 创建项目
打开Qt-Creator,创建项目:注意名称不要包含空格和回车,路径不要有中文
![在这里插入图片描述](https://img-blog.csdnimg.cn/ee3ee77a1ba24be3960fe0b449fd2e38.png)
类信息中,选择基类为QMainWindow,类名称为 MainScene,代表着主场景。
![在这里插入图片描述](https://img-blog.csdnimg.cn/92bde7368c4b405e903feed32ed2b7aa.png)
点击完成,创建出项目:
![在这里插入图片描述](https://img-blog.csdnimg.cn/0f68338090a44a97a72be605a1fad61a.png)
创建的项目结构如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/5077c649f1394565829ce5558e686d4f.png)
2.2 添加资源
将资源添加到当前项目下
![在这里插入图片描述](https://img-blog.csdnimg.cn/f95e1d9266254556a50b72d5a90b2c82.png)
然后创建.qrc文件
![在这里插入图片描述](https://img-blog.csdnimg.cn/d5f43def3861430e8bec22c12faac803.png)
进入编辑模式,添加前缀 “/” ,添加文件
![在这里插入图片描述](https://img-blog.csdnimg.cn/b1badbd250254a969ef5ffd944cbfbda.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/1d18a49326184632930391fb82bb5daa.png)
将所有资源文件进行添加,并进行编译
![在这里插入图片描述](https://img-blog.csdnimg.cn/28aa66d7bddd4eb3b40e769cf7b5dd3f.png)
至此将所有需要的资源添加到了本项目中。
3 主场景
3.1 设置游戏主场景配置
点击mainscene.ui文件,设计其菜单栏如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/ba5739b1b6124425bcae2f9b9f50a92d.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/fc47b22c3e3e457ca52ea9573e90b723.png)
设计“退出”菜单项,objectName为 actionQuit, text 为 退出;
移除自带的工具栏与状态栏
![在这里插入图片描述](https://img-blog.csdnimg.cn/308a6634e08b4015bc583eab26423e8f.png)
回到MainScene.cpp文件,进入构造函数中,进行场景的基本配置,代码如下:
//设置固定大小
this->setFixedSize(320,588);
//设置应用图片
this->setWindowIcon(QPixmap(":/res/Coin0001.png"));
//设置窗口标题
this->setWindowTitle("老帮主带你翻金币");
运行效果如图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/1c6eec4e7ffb4a969da87eae24b98bc9.png)
实现点击开始,退出游戏功能,代码如下:
//点击退出,退出程序
connect(ui->actionQuit,&QAction::triggered,[=](){this->close();});
3.2 设置背景图片
重写MainScene的PaintEvent事件,并添加一下代码,绘制背景图片。
另外一种方法是设置与一级页面大小相同的Label来绘制背景,但是很少使用这种方式来做。
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);
}
运行效果如图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/618a578a85cf4fa08b390a8c4ec5d237.png)
3.3 创建开始按钮(控件封装)
开始按钮点击后有弹跳和按下抬起的按钮图片不同的效果,这两个效果是我们利用自定义控件实现的(QPushButton不会自带这类特效),我们可以自己封装出一个按钮控件,来实现这些效果。
创建MyPushButton,继承与QPushButton
![在这里插入图片描述](https://img-blog.csdnimg.cn/45186e02ff974630a414c60a5dc4e7d8.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/b15844ec0cb04a86939fa2efd0c9c55b.png)
点击完成。
修改MyPushButton的父类
![在这里插入图片描述](https://img-blog.csdnimg.cn/1a83825acc5d4ca688beefb07eef76c3.png)
提供MyPushButton的构造的重载版本,可以让MyPushButton提供正常显示的图片以及按下后显示的图片
代码如下:
//normalImg 代表正常显示的图片
//pressImg 代表按下后显示的图片,默认为空 如果不传入值,按下之后按钮的图片就不会发生变化
MyPushButton(QString normalImg,QString pressImg = "");
QString normalImgPath; //默认显示图片路径
QString pressedImgPath; //按下后显示图片路径
实现的重载版本MyPushButton构造函数代码如下:
//QString pressImg在.h中已经有默认参数,在.cpp中就不需要再声明,.h和.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() );
//设置不规则图片的样式表 实现边框为0的不规则图片
this->setStyleSheet("QPushButton{border:0px;}");
//设置图标
this->setIcon(pixmap);
//设置图标大小
this->setIconSize(QSize(pixmap.width(),pixmap.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);
运行效果如图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/ab2b09d1a63a4abe9a0f694c457151c0.png)
不规则的开始按钮添加完成。
3.4 开始按钮跳跃特效实现
连接信号槽,监听开始按钮点击
//监听点击事件,执行特效
connect(startBtn,&MyPushButton::clicked,[=](){
startBtn->zoom1(); //向下跳跃
startBtn->zoom2(); //向上跳跃
});
zoom1与zoom2 为MyPushButton中扩展的特效代码,具体如下:
void MyPushButton::zoom1()
{
//创建动画对象
QPropertyAnimation * animation1 = new QPropertyAnimation(this,"geometry");
//设置时间间隔,单位毫秒
animation1->setDuration(200);
//创建起始位置 矩形的设置为起始的x,起始的y,起始的width和height
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()));
animation1->setEasingCurve(QEasingCurve::OutBounce);
animation1->start();
}
运行代码,点击按钮,测试弹跳效果。
3.5 创建选择关卡场景
点击开始按钮后,进入选择关卡场景。
首先我们先创建选择关卡场景,添加新的C++文件
![在这里插入图片描述](https://img-blog.csdnimg.cn/3ab3fac054024610ae4550495313c625.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/ba021cc4656547c8b2694264455c83ed.png)
类名为ChooseLevelScene 选择基类为QMainWindow,点击下一步,然后点击完成。
3.6 点击开始按钮进入选择关卡场景
目前点击主场景的开始按钮,只有弹跳特效,但是我们还需要有功能上的实现,特效结束后,我们应该进入选择关卡场景
在MainScene.h中 保存ChooseScene选择关卡场景对象
//选择关卡场景
ChooseLevelScene *chooseScene = new ChooseLevelScene;
我们在zoom1和zoom2特效后,延时0.5秒,进入选择关卡场景,代码如下:
//延时0.5秒后 进入选择场景 静态成员函数
QTimer::singleShot(500, this,[=](){
this->hide();
chooseScene->show();
});
![在这里插入图片描述](https://img-blog.csdnimg.cn/58151972cfa04bc993b4503eea258deb.png)
测试点击开始,执行特效后延时0.5秒进入选择关卡场景
4 选择关卡场景
4.1 场景基本设置
选择关卡构造函数如下:
//设置窗口固定大小
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();});
运行效果如图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/6da7e2bba37c442e88678afa2bc8cd50.png)
4.2 背景设置
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 创建返回按钮
//返回按钮
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
//鼠标事件
void MyPushButton::mousePressEvent(QMouseEvent *e)
{
//START不需要按钮图片的切换,使用pressedImgPath来进行判断是否需要切换
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);
}
4.4 返回按钮
在这里我们点击返回后,延时0.5后隐藏自身,并且发送自定义信号,告诉外界自身已经选择了返回按钮。
//返回按钮功能实现
connect(closeBtn,&MyPushButton::clicked,[=](){
QTimer::singleShot(500, this,[=](){
this->hide();
//触发自定义信号,关闭自身,该信号写到 signals下做声明
emit this->chooseSceneBack();
}
);
});
在主场景MainScene中 点击开始按钮显示选择关卡的同时,监听选择关卡的返回按钮消息
//监听选择场景的返回按钮
connect(chooseScene,&ChooseLevelScene::chooseSceneBack,[=](){
this->show();
});
测试主场景与选择关卡场景的切换功能。
4.5 创建选择关卡按钮
//创建选择关卡的按钮 每个按钮均创建一个对象 名字相同但是地址不同 将每个按钮对象与槽函数绑定
for(int i = 0 ; i < 20;i++)
{
MyPushButton * menuBtn = new MyPushButton(":/res/LevelIcon.png");
menuBtn->setParent(this);
//一层的循环做出二维矩阵,%取余定义X轴,/取整定义Y轴
menuBtn->move(25 + (i%4)*70 , 130+ (i/4)*70);
//按钮上显示的文字 使用设置数字直接显示很难看 利用QLabel实现
QLabel * label = new QLabel;
label->setParent(this);
label->setFixedSize(menuBtn->width(),menuBtn->height());
label->setText(QString::number(i+1));
//设置label上文字的对齐方式
label->setAlignment(Qt::AlignHCenter | Qt::AlignVCenter); //设置居中
label->move(25 + (i%4)*70 , 130+ (i/4)*70);
//label盖在了menuBtn上,鼠标点击事件作用在label上没有传递到按钮上,需要设置穿透传递给按钮
label->setAttribute(Qt::WA_TransparentForMouseEvents,true); //鼠标事件穿透
}
运行效果如果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/ce693c5c0fa7498980bb9bf6ebce864e.png)
4.6 创建翻金币场景
点击关卡按钮后,会进入游戏的核心场景,也就是翻金币的场景,首先先创建出该场景的.h和.cpp文件
创建PlayScene
![在这里插入图片描述](https://img-blog.csdnimg.cn/30d6f1a2e7b04d5fb0ab2ace6ddc41d0.png)
点击选择关卡按钮后会跳入到该场景
建立点击按钮,跳转场景的信号槽连接
在ChooseLevelScene.h 中声明
PlayScene *pScene = NULL;
//监听选择关卡按钮的信号槽
connect(menuBtn,&MyPushButton::clicked,[=](){
// qDebug() << "select: " << i;
if(pScene == NULL) //游戏场景最好不用复用,直接移除掉创建新的场景
{
this->hide();
pScene = new PlayScene(i+1); //将选择的关卡号 传入给PlayerScene
pScene->show();
}
});
这里pScene = new PlayScene(i+1); 将用户所选的关卡号发送给pScene,也就是翻金币场景,当然PlayScene 要提供重载的有参构造版本,来接受这个参数
5 翻金币场景
5.1 场景基本设置
PlayScene.h中 声明成员变量,用于记录当前用户选择的关卡
//成员变量 记录关卡索引
int levalIndex;
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();});
}
5.2 背景设置
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 返回按钮
//返回按钮
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();
}
);
});
在ChooseScene选择关卡场景中,监听PlayScene的返回信号
connect(pScene,&PlayScene::chooseSceneBack,[=](){
this->show();
delete pScene;
pScene = NULL;
});
![在这里插入图片描述](https://img-blog.csdnimg.cn/8d043694d3ae4aa29f681cc6f0101802.png)
5.4 显示当前关卡
//当前关卡标题
QLabel * label = new QLabel;
label->setParent(this);
QFont font;
font.setFamily("华文新魏");
font.setPointSize(20);
label->setFont(font);
//格式化字符串
QString str = QString("Leavel: %1").arg(this->levalIndex);
label->setText(str);
label->setGeometry(QRect(30, this->height() - 50,120, 50)); //设置大小和位置
假设我们选择了第15关卡,运行效果如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/a71ee61e58d24aae9df360bc2bcc3574.png)
5.5 创建金币背景图片
//创建金币的背景图片
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);
}
}
运行效果如图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/2309fa702c1a43b8b62ebdb829c1c462.png)
5.6 创建金币类
我们知道,金币是本游戏的核心对象,并且在游戏中可以利用二维数组进行维护,拥有支持点击,翻转特效等特殊性,因此不妨将金币单独封装到一个类中,完成金币所需的所有功能。
金币的动作与MPushButton类的动作相差太多,因此需要重新创建类
5.6.1 创建金币类 MyCoin
![在这里插入图片描述](https://img-blog.csdnimg.cn/63973be1095d4b21811e2dcd030ced19.png)
并修改MyCoin的基类为QPushButton
5.6.2 构造函数
在资源图片中,我们可以看到,金币翻转的效果原理是多张图片切换而形成的,而以下八张图片中,第一张与最后一张比较特殊,因此我们在给用户看的时候,无非是金币Coin0001或者是银币 Coin0008这两种图。
因此我们在创建一个金币对象时候,应该提供一个参数,代表着传入的是金币资源路径还是银币资源路径,根据路径我们创建不同样式的图案。
![在这里插入图片描述](https://img-blog.csdnimg.cn/9d092b640ed54b05a3df59bb09d146ce.png)
在MyCoin.h中声明:
MyCoin(QString butImg); //代表图片路径
在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()));
}
5.6.3 测试
在翻金币场景 PlayScene中,我们测试下封装的金币类是否可用,可以在创建好的金币背景代码后,添加如下代码:
//金币对象
MyCoin * coin = new MyCoin(":/res/Coin0001.png");
coin->setParent(this);
coin->move(59 + i*50,204+j*50);
运行效果如图
![在这里插入图片描述](https://img-blog.csdnimg.cn/fafba86480d24c1a9491a70c37702c57.png)
5.7 引入关卡数据
当然上述的测试只是为了让我们知道提供的对外接口可行,但是每个关卡的初始化界面并非如此,因此需要我们引用一个现有的关卡文件,文件中记录了各个关卡的金币排列情况,也就是二维数组的数值。
5.7.1 添加现有文件dataConfig
首先先将dataConfig.h 和 dataConfig.cpp文件放入到当前项目下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/79f3be2bf9cf4a4a8a3ed373eaa14bda.png)
5.7.2 添加现有文件
其次在Qt_Creator项目右键,点击添加现有文件
![在这里插入图片描述](https://img-blog.csdnimg.cn/c26b5e96756244f98d52ab774fb0f5bb.png)
5.7.3 完成添加
选择当前项目下的文件,并进行添加
![在这里插入图片描述](https://img-blog.csdnimg.cn/a33bf7e576ca45928aa7e3c3bb3a255b.png)
5.7.4 数据分析
我们可以看到,其实dataConfig.h中只有一个数据是对外提供的,如下图
![在这里插入图片描述](https://img-blog.csdnimg.cn/fcd68563164f4fb2be8ba9da56eae9ee.png)
在上图中,QMap<int,QVector<QVector>>mData;都记录着每个关卡中的数据。
其中,int代表对应的关卡 ,也就是QMap中的key值,而value值就是对应的二维数组,我们利用的是 QVector<QVector>来记录着其中的二维数组。
5.7.5 测试关卡数据
在Main函数可以测试第一关的数据,添加如下代码:
dataConfig config;
for(int i = 0 ; i < 4;i++)
{
for(int j = 0 ; j < 4; j++)
{
//打印第一关所有信息
qDebug() << config.mData[1][i][j];
}
qDebug()<< "";
}
输出结果如下图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/a1ea7243ed6f46fe9c8c2a7d1fd5e2d6.png)
对应着dataConfig.cpp中第一关数据来看,与之匹配成功,以后我们就可以用dataConfig中的数据来对关卡进行初始化了
![在这里插入图片描述](https://img-blog.csdnimg.cn/45e79f6d0d4a4ccf9debc30f92a31241.png)
5.8 初始化各个关卡
首先,可以在playScene中声明一个成员变量,用户记录当前关卡的二维数组
int gameArray[4][4]; //二维数组数据 维护每个数组的具体数据
之后,在.cpp文件中,初始化这个二维数组
//初始化二维数组
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的坐标,以及当前的正反标志。
int posX; //x坐标
int posY; //y坐标
bool flag; //正反标志
然后完成金币初始化,代码如下:
//金币对象
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(59 + i*50,204+j*50);
coin->posX = i; //记录x坐标
coin->posY = j; //记录y坐标
coin->flag =gameArray[i][j]; //记录正反标志
运行测试各个关卡初始化,例如第一关效果如图:
![在这里插入图片描述](https://img-blog.csdnimg.cn/18bb8facb64f4476afc75c45b6512763.png)
5.9 翻金币特效
5.9.1 MyCoin类扩展属性和行为
关卡的初始化完成后,下面就应该点击金币,进行翻转的效果了,那么首先我们先在MyCoin类中创建出该方法。
在MyCoin.h中声明:
void changeFlag();//改变标志,执行翻转效果
QTimer *timer1; //正面翻反面 定时器
QTimer *timer2; //反面翻正面 定时器
int min = 1; //最小图片
int max = 8; //最大图片
MyCoin.cpp中做实现
void MyCoin::changeFlag()
{
if(this->flag) //如果是正面,执行下列代码
{
timer1->start(30);
this->flag = false;
}
else //反面执行下列代码
{
timer2->start(30);
this->flag = true;
}
}
当然在构造函数中,记得创建出两个定时器
//初始化定时器
timer1 = new QTimer(this);
timer2 = new QTimer(this);
5.9.2 创建特效
当我们分别启动两个定时器时,需要在构造函数中做监听操作,并且做出响应,翻转金币,然后再结束定时器。
构造函数中 进行下列监听代码:
//监听正面翻转的信号槽
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();
}
});
5.9.3 测试
监听每个按钮的点击效果,并翻转金币
connect(coin,&MyCoin::clicked,[=](){
//qDebug() << "点击的位置: x = " << coin->posX << " y = " << coin->posY ;
coin->changeFlag();
gameArray[i][j] = gameArray[i][j] == 0 ? 1 : 0; //数组内部记录的标志同步修改
});
![在这里插入图片描述](https://img-blog.csdnimg.cn/b907b039347b457bbee4f471314336d3.png)
5.9.4 禁用按钮
此时,确实已经可以执行翻转金币代码了,但是如果快速点击,会在金币还没有执行一个完整动作之后 ,又继续开始新的动画,我们应该在金币做动画期间,禁止再次点击,并在完成动画后,开启点击。
在MyCoin类中加入一个标志 isAnimation 代表是否正在做翻转动画。
bool isAnimation = false; //做翻转动画的标志
在MyCoin做动画期间加入
this->isAnimation = true;
也就是changeFlag函数中将标志设为true
加入位置如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/3facff879159491daba3281d54a873ae.png)
并且在做完动画时,将标志改为false
![在这里插入图片描述](https://img-blog.csdnimg.cn/e17bc7f15a974e5a974ea2c69d76ca17.png)
重写按钮的按下事件,判断如果正在执行动画,那么直接return掉,不要执行后续代码。
代码如下:
void MyCoin::mousePressEvent(QMouseEvent *e)
{
if(this->isAnimation )
{
return;
}
else
{
return QPushButton::mousePressEvent(e);
}
}
5.10 翻周围金币
将用户点击的周围 上下左右4个金币也进行延时翻转,代码写到监听点击金币下。
此时我们发现还需要记录住每个按钮的内容,所以我们将所有金币按钮也放到一个二维数组中,在.h中声明
MyCoin * coinBtn[4][4]; //金币按钮数组
并且记录每个按钮的位置
coinBtn[i][j] = coin;
![在这里插入图片描述](https://img-blog.csdnimg.cn/8536f4380d914f638249ff45dfd9ae08.png)
延时翻动其他周围金币
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;
}
});
5.11 判断是否胜利
在MyCoin.h中加入 isWin标志,代表是否胜利。
bool isWin = true; //是否胜利
默认设置为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; //只要有一个false代表没有胜利
break;
}
}
}
如果isWin依然是true,代表胜利了!
if(this->isWin)
{
qDebug() << "胜利";
}
5.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());
如果胜利了,将上面的图片移动下来,使用动画实现
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();
}
5.13 胜利后禁用按钮
当胜利后,应该禁用所有按钮的点击状态,可以在每个按钮中加入标志位 isWin,如果isWin为true,MousePressEvent直接return掉即可
MyCoin中.h里添加:
bool isWin = false;//胜利标志
在鼠标按下事件中修改为
void MyCoin::mousePressEvent(QMouseEvent *e)
{
if(this->isAnimation|| isWin == true )
{
return;
}
else
{
return QPushButton::mousePressEvent(e);
}
}
//禁用所有按钮点击事件
for(int i = 0 ; i < 4;i++)
{
for(int j = 0 ; j < 4; j++)
{
coinBtn[i][j]->isWin = true;
}
}
测试,胜利后不可以点击任何的金币。
6 音效添加
6.1 开始音效
QSound *startSound = new QSound(":/res/TapButtonSound.wav",this);
点击开始按钮,播放音效
startSound->play(); //开始音效
6.2 选择关卡音效
在选择关卡场景中,添加音效
//选择关卡按钮音效
QSound *chooseSound = new QSound(":/res/TapButtonSound.wav",this);
选中关卡后,播放音效
chooseSound->play();
6.3 返回按钮音效
在选择关卡场景与翻金币游戏场景中,分别添加返回按钮音效如下:
//返回按钮音效
QSound *backSound = new QSound(":/res/BackButtonSound.wav",this);
分别在点击返回按钮后,播放该音效
backSound->play();
6.4 翻金币与胜利音效
在PlayScene中添加,翻金币的音效以及 胜利的音效
//翻金币音效
QSound *flipSound = new QSound(":/res/ConFlipSound.wav",this);
//胜利按钮音效
QSound *winSound = new QSound(":/res/LevelWinSound.wav",this);
在翻金币时播放 翻金币音效
flipSound->play();
胜利时,播放胜利音效
winSound->play();
测试音效,使音效正常播放。
7 优化项目
当我们移动场景后,如果进入下一个场景,发现场景还在中心位置,如果想设置场景的位置,需要添加如下下图中的代码:
MainScene中添加:
![在这里插入图片描述](https://img-blog.csdnimg.cn/cb4c4f710c544e47bb19810395e61b2b.png)
ChooseScene中添加:
![在这里插入图片描述](https://img-blog.csdnimg.cn/9763e09d21e54da38b36744bb06ed403.png)
测试切换三个场景的进入与返回都在同一个位置下,优化成功。
8 打包发布
8.1 项目打包
(1)设置程序发布状态:将Debug状态修改为程序发布状态
![在这里插入图片描述](https://img-blog.csdnimg.cn/4e57bbad9a9b479f95d912f010091388.png)
(2)重新编译项目
![在这里插入图片描述](https://img-blog.csdnimg.cn/3ed53d0b838c42e1a1c5f12e1ed557bd.png)
(3)打开项目所在文件,找到release版本的项目
![在这里插入图片描述](https://img-blog.csdnimg.cn/aae014843d184da6abd810bf3eb3c5c0.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/17a1bfc8ecc84279a75c7ee76e74f51a.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/8305f35478954a9c8ff0665a87534d06.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/f0a4a0fc9546416f9ccd30d19e28abf4.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/3ab7eb07103d4d838ef998d5747aa4e0.png)
8.2 游戏扩展
项目打包后生成的exe文件,要想使用,必须保持本机有qt的环境,因此为了扩展游戏,使得普通的电脑也可以运行程序,需要进行扩展。
(1)新建文件夹,并且将项目打包的exe文件复制到该文件夹内
![在这里插入图片描述](https://img-blog.csdnimg.cn/887a92930af74586b2312cbe4589f735.png)
(2)在文件夹内打开cmd命令窗口
![在这里插入图片描述](https://img-blog.csdnimg.cn/2dc75c7aaadf43d19a980540f7d5e1ed.png)
(3)首先需要保证Qt的安装位置有windeployqt.exe文件
![在这里插入图片描述](https://img-blog.csdnimg.cn/344b755f109b4d61b69718707ea45681.png)
使用windeployqt 命令运行exe程序:生成打包项目
![在这里插入图片描述](https://img-blog.csdnimg.cn/6651564ebc514e419c444e50e1d0d60d.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/fd2c0a2d0a4441178d1b99909d07e9a4.png)
8.3 游戏打包nsis安装包(更深层拓展)
将游戏更深入打包,生成压缩包:参考:参考链接
至此,本案例全部制作完成。
我自己按照博文写的项目源代码请见链接:Qt翻金币项目所有源代码