做这个项目记录的一些笔记。
说明:翻金币项目是一款经典的益智类游戏,我们需要将金币都翻成同色,才视为胜利。首先,开始、选关、游戏、胜利界面如下:
游戏的代码和软件资源请点击自取[资源在此](提取码:7758,制作不易,希望点个赞,非常感谢)
文章目录
- 1、创建项目
- 2、导入资源实现退出
- 3、主场景的搭建
- 4、开始按钮封装
- 5、开始按钮跳跃效果实现
- 6、选择关卡场景的基本配置
- 7、返回按钮的创建
- 8、返回按钮的功能实现
- 9、创建选择关卡的小按钮
- 10、显示关卡按钮上面的数字实现
- 11、翻金币场景的创建及基本搭建
- 12、翻金币场景的返回按钮实现
- 13、当前关卡号功能实现
- 14、金币背景图加载
- 15、金币类创建
- 16、初始化关卡
- 17、翻转银币的功能实现(核心:切图)
- 18、快速点击导致的翻转效果不完整
- 19、翻转周围硬币
- 20、游戏胜利的检测以及禁用硬币点击
- 21、游戏胜利后手速过快导致的bug
- 22、胜利图片显示
- 23、音效的添加
- 24、bug解决---场景位置统一
- 25、游戏辅助玩法介绍
- 26、项目打包发布
1、创建项目
- 创建QT widgets Application,项目名任意,选择基类QMainWindow,类名MainScene,代表主场景。(注意项目所在路径千万不要出现中文,否则可能会出现点击运行,没有窗口弹出的情况)
- 选择MinGW作为编译器
2、导入资源实现退出
(1)导入资源
- 将资源文件复制到当前项目下
- 右击项目名—添加新文件—QT—QT Resource File—文件名为res
- 添加前缀(/res)—添加文件(把所有文件都添加进去)
- 点击小锤子按钮,将资源文件导入
(2)实现退出
- 打开ui界面,删除工具栏和状态栏
- 编辑界面左上角的菜单名称:开始—退出
- 在mainscene.cpp文件中输入10-13行所示的代码:
#include "mainscene.h"
#include "ui_mainscene.h"
MainScene::MainScene(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainScene)
{
ui->setupUi(this);
//点击退出菜单项 实现退出
connect(ui->actionQuit,&QAction::triggered,[=](){
this->close();
});
//设置固定尺寸
this->setFixedSize(320,588);
}
MainScene::~MainScene()
{
delete ui;
}
3、主场景的搭建
- 1设置固定尺寸
- 2设置标题
- 3设置图标
- 4添加背景
前3步的代码如下:
//设置固定尺寸
this->setFixedSize(320,588);
//设置标题
this->setWindowTitle("翻金币主场景");
//设置图标
this->setWindowIcon(QIcon(":/res/Coin0001.png"));
添加背景需要重写绘图事件,其主要代码和效果如下:
//绘图事件
void mainscene::paintEvent(QPaintEvent *)
{
//创建画家对象
QPainter painter(this);
//创建QPixmap对象
QPixmap pix;
pix.load(":/res/PlayLevelSceneBg.png"); //加载第一个场景的背景图
//绘制背景
painter.drawPixmap(0,0,this->width(),this->height(),pix); //参数3和4表示将图片与屏幕进行适配
//加载图片左上角的标题图片
pix.load(":/res/Title.png"); //复用一下pix对象
//缩放图片
pix = pix.scaled(pix.width()*0.5,pix.height()*0.5);
//绘制标题
painter.drawPixmap(10,30,pix);
}

| 图1 |
|---|
4、开始按钮封装
- (1)新建一个名为MyPushButton的C++ Class,继承于QObject(创建完毕后在代码中改成QPushButton)
- (2)在mypushbutton.h中重新写了一种构造方式,代码如下
#ifndef MYPUSHBUTTON_H
#define MYPUSHBUTTON_H
#include <QPushButton>
class MyPushButton : public QPushButton
{
Q_OBJECT
public:
//参数1 正常显示图片路径 参数2 按下后切换图片路径
MyPushButton(QString normalImg,QString pressImg=" ");
QString normalPath;
QString pressPath;
signals:
public slots:
};
#endif // MYPUSHBUTTON_H
- (3)mypushbutton.cpp中实现,代码如下:
#include "mypushbutton.h"
#include <QDebug>
//参数1 正常显示图片路径 参数2 按下后切换图片路径
MyPushButton::MyPushButton(QString normalImg,QString pressImg)
{
this->normalPath = normalImg;
this->pressPath = pressImg;
//创建QPixmap对象
QPixmap pix;
bool ret = pix.load(this->normalPath); //ret用于报错加载成功与否的结果
if(!ret)
{
QString str = QString("图片加载失败,失败的路径是: %1").arg(this->normalPath);
qDebug()<<str;
}
//如果成功
//设置按钮固定尺寸
this->setFixedSize(pix.width(),pix.height());
//设置不规则样式
this->setStyleSheet("QPushButton{border:0px;}"); //有点类似于网页设计中的CSS语法
//设置图标
this->setIcon(pix);
//设置图标大小
this->setIconSize(QSize(pix.width(),pix.height()));
}
- 在mainscene.cpp中,显示了开始按钮,效果如下

| 图2 |
|---|
5、开始按钮跳跃效果实现
- 在MyPushButton中封装zoom1和zoom2函数实现跳跃
- (1)在mypushbutton.h中添加了下图3所示的代码

| 图3 |
|---|
- (2)在mypushbutton.cpp中添加了下图4所示的代码,值得注意的是,在创建动画这一行代码中,动画的方式别打错字,否则可能出现以下报错
QPropertyAnimation: you're trying to animate a non-existing property grometry of your QObject,导致点击按钮没反应。

| 图4 |
|---|
//让按钮向下跳跃
void MyPushButton::zoom1()
{
//创建动画对象
QPropertyAnimation * animation = new QPropertyAnimation(this,"geometry"); //参数1:让谁去做这个动画,参数2:动画的方式
//设置动画间隔
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())); //因为是向下跳跃,所以只有y坐标会增加
//设置动画曲线(加速、减速、变速)
animation->setEasingCurve(QEasingCurve::OutBounce);
//执行动画
animation->start(QAbstractAnimation::DeleteWhenStopped); //DeleteWhenStopped表示动画执行完毕后就释放对象,KeepWhenStopped则表示保存对象
}
void MyPushButton::zoom2()
{
//创建动画对象
QPropertyAnimation * animation = new QPropertyAnimation(this,"geometry"); //参数1:让谁去做这个动画,参数2:动画的方式
//设置动画间隔
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(QAbstractAnimation::DeleteWhenStopped); //DeleteWhenStopped表示动画执行完毕后就释放对象,KeepWhenStopped则表示保存对象
}
- (3)在mainscene中调用这两个函数进行测试

| 图5 |
|---|
6、选择关卡场景的基本配置
- (1)创建ChooseLevelScene 选择关卡场景
- (2)在主场景中维护了第二个场景的指针
- (3)在主场景的cpp中去创建第二个场景,并且点击开始按钮进行跳转(有一个延时进入)
在chooselevelscene.cpp中用代码编写出了菜单栏,并且编写了背景的绘图事件

| 图6 |
|---|
7、返回按钮的创建
(1)在选关场景中创建返回按钮,重写按钮的鼠标按下和鼠标释放事件。具体效果为:当鼠标按下的时候显示另外一张图片,当鼠标松开时恢复出原来的效果。

| 图7 |
|---|
(2)在自定义的mypushbutton.h中重写了鼠标按下和鼠标释放事件
| 图8 |
|---|
(3)在mypushbutton.cpp中实现了鼠标按下和鼠标释放事件
| 图9 |
|---|

| 图10 |
|---|
8、返回按钮的功能实现
功能描述:点击返回按钮时,能够回到主场景。
实现手段:利用信号和槽。
(1)选择关卡场景的.h中定义了返回按钮的信号
| 图11 |
|---|
(2)选择关卡场景的.cpp文件中实现了返回按钮的信号

| 图12 |
|---|
在主场景的.cpp文件中,监听了返回按钮的信号。
| 图13 |
|---|
9、创建选择关卡的小按钮
由于是按钮,故直接可通过自己编写的mypushbutton类来创建。
因此,在选择关卡chooselevelscene.cpp中直接加入以下代码即可:
//--------创建20个具体关卡的按钮------------
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); //通过取模、除法运算,使得一个for循环实现4行5列的矩阵
connect(menuBtn,&MyPushButton::clicked,[=](){ //监听每一个按钮的信号
qDebug()<<"您选择的是第"<<i+1<<"关";
});
}
效果演示如下:

| 图14 |
|---|
核心代码说明: menuBtn->move(25+(i%4)*70,130+(i/4)*70);
可以看到上图,关卡按钮是4行5列的。
- 当i为0、1、2、3时,25+(i%4)*70所得到的值分别为:25 95 165 235,对应按钮的横坐标;
- 当i为0、4、8、12、16时,130+(i/4)*70所得到的值对应按钮的纵坐标。
10、显示关卡按钮上面的数字实现
在按钮上显示关卡数字。
实现思路1: 按钮底层本身封装了一个函数—setText,可以在按钮上直接显示内容。
具体代码:
menuBtn->setText(QString::number(i+1)); //因为setText要的是字符串,而QString::number表示将int型数据转化成字符串型。
具体效果如下:会发现该方法无法得到理想的效果,因为原先的按钮是矩形的,我们将其改为了圆形,因此文本也无法对应。

| 图15 |
|---|
实现思路2: 利用QLabel标签。
具体代码如下:
//显示按钮上面的数字
QLabel * label = new QLabel;
label->setParent(this);
label->move(25+(i%4)*70,130+(i/4)*70); //让QLabel移动到和按钮一样的位置
//设置尺寸
label->setFixedSize(menuBtn->width(),menuBtn->height()); //让QLabel的大小和按钮一样大
//设置文本
label->setText(QString::number(i+1));
//设置对齐方式
label->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter); //QT中的或相当于让两个条件都成立(水平和垂直居中)
//设置属性 鼠标穿透属性
label->setAttribute(Qt::WA_TransparentForMouseEvents); //不设置这一代码点击按钮时会没有反应,因为Qlabel标签位于按钮的上一层,文字会吸收掉点击事件

| 图16 |
|---|

| 图17 |
|---|
11、翻金币场景的创建及基本搭建
(1)创建翻金币场景:创建PlayScene.h和PlayScene.cpp文件(父类为QMainWindow)
(2)在选择关卡场景chooselevelscene.h中维护 第三个场景的指针。
| 图18 |
|---|
(3)当用户点击选关按钮,创建第三个场景。
| 图19 |
|---|
(4)游戏场景的基本搭建
即绘图事件,在playscene.h和playscene.cpp中的代码分别为:

| 图20 |
|---|

| 图21 |
|---|
12、翻金币场景的返回按钮实现
(1)创建返回按钮。
(2)点击时发送自定义信号。
(3)在选关场景(第2场景)中监听翻金币游戏场景的信号,并作响应。
- 在playscene.h中设置了一个返回按钮的信号
- 在playscene.cpp中实现了信号的发射
- 在ChooseLevelScene.cpp中监听了游戏场景发送回的信号(特别要注意监听信号的代码所在的范围,这种bug极难发现)
- 具体新增代码如下图所示

| 图22 |
|---|

| 图23 |
|---|

| 图24 |
|---|
13、当前关卡号功能实现
在playscene.cpp文件中添加了如下代码,这个过程和第十章的内容差不多,只不过新增了两种技巧:
1: QString str = QString(“Level:%1”).arg(this->levelIndex); //arg的作用是拼接
2:QFont font(“华文新魏”,20); //设置标签的字体属性
label->setFont(font);
3: //设置标签的大小和位置
label->setGeometry(QRect(30,this->height()-50,this->width(),50));

| 图25 |
|---|

| 图26 |
|---|
14、金币背景图加载
(1)在playscene.cpp中新增了如下代码,注意,在新增代码中用到了两层for循环,这是为了在游戏规则处理时更加方便的选择硬币上下左右的关系。
| 图27 |
|---|
效果如图28所示:
| 图28 |
|---|
15、金币类创建
我们知道,金币是本游戏的核心对象,并且在游戏中可以利用二维数组进行维护,拥有支持点击,翻转特效等特殊性,因此不妨将金币单独封装到一个类中,完成金币所需的所有功能。
(1)创建一个MyCoin类,父类为QWidget,创建成功以后,将其构造函数的定义与实现删除,并将其父类改成QPushButton(头文件和继承都要改)
(2)MyCoin.h中的代码

| 图29 |
|---|
(3)MyCoin.cpp中的代码

| 图30 |
|---|
(4)playscene.cpp中的代码

| 图31 |
|---|
16、初始化关卡
首先将dataconfig.cpp和dataconfig.h文件导入项目中。
以dataconfig.h为例,该类只有一个成员,表示关卡号对应的金币规则
#ifndef DATACONFIG_H
#define DATACONFIG_H
#include <QObject>
#include <QMap>
#include <QVector>
class dataConfig : public QObject
{
Q_OBJECT
public:
explicit dataConfig(QObject *parent = 0);
public:
QMap<int, QVector< QVector<int> > >mData; //起到键值对的作用,参数1为key,代表关卡编号;参数2为value,其中value又是一个嵌套的容器,为一个二维数组
signals:
public slots:
};
#endif // DATACONFIG_H
下面给出了1-3关的dataconfig.cpp文件中的代码,其中1为金币,2为银币。
#include "dataconfig.h"
#include <QDebug>
dataConfig::dataConfig(QObject *parent) : QObject(parent)
{
int array1[4][4] = {{1, 1, 1, 1},
{1, 1, 0, 1},
{1, 0, 0, 0},
{1, 1, 0, 1} } ;
QVector< QVector<int>> v;
for(int i = 0 ; i < 4;i++)
{
QVector<int>v1;
for(int j = 0 ; j < 4;j++)
{
v1.push_back(array1[i][j]);
}
v.push_back(v1);
}
mData.insert(1,v);
int array2[4][4] = { {1, 0, 1, 1},
{0, 0, 1, 1},
{1, 1, 0, 0},
{1, 1, 0, 1}} ;
v.clear();
for(int i = 0 ; i < 4;i++)
{
QVector<int>v1;
for(int j = 0 ; j < 4;j++)
{
v1.push_back(array2[i][j]);
}
v.push_back(v1);
}
mData.insert(2,v);
int array3[4][4] = { {0, 0, 0, 0},
{0, 1, 1, 0},
{0, 1, 1, 0},
{0, 0, 0, 0}} ;
v.clear();
for(int i = 0 ; i < 4;i++)
{
QVector<int>v1;
for(int j = 0 ; j < 4;j++)
{
v1.push_back(array3[i][j]);
}
v.push_back(v1);
}
mData.insert(3,v);
然后分别在playscene.h和playscene.cpp中新增了以下代码,有用于给每个关卡的金币、银币进行图案排版:
| 图32 |
|---|

| 图33 |
|---|
效果图如下,最后一关是全银币的。
| 图34 |
|---|
17、翻转银币的功能实现(核心:切图)
(1)如下图所示,为了方便翻转硬币,在金币类中,拓展了属性posX、posY和flag。这三个属性分别代表了,该金币在二维数组中的x坐标,y坐标以及当前的正反标志(也就是是金币还是银币)
int posX; //x坐标
int posY; //y坐标
bool flag; //正反标志

| 图35 |
|---|
(2)关卡的初始化完成后,就应该点击金币,进行翻转的效果了,那么首先在MyCoin类中创建出该方法。
在MyCoin.h中声明:
void changeFlage(); //改变标志,执行翻转效果(只要点了,就会执行)
QTimer *timer1; //正面翻反面 定时器(其实这两个用一个就行了,写两个是便于理解)
QTimer *timer2; //反面翻正面 定时器
int min = 1; //最小图片下标
int max = 8; //最大图片下标

| 图36 |
|---|
(3)在MyCoin.cpp中实现:
| 图37 |
|---|

| 图38 |
|---|
(4)在playscene.cpp中新增的测试代码如下
| 图39 |
|---|
18、快速点击导致的翻转效果不完整
问题说明,当鼠标快速点击金币时,会出现金币翻银币,银币翻金币,然后又是银币翻金币。说白了就是金币翻银币的动作还没做,银币翻金币的动作就开始了。
解决方法,要将整个动作做完才能执行下一个动作(要做一些限制)。
加一个标志如isAnimation,用于表示当前金币是否做完了动画,默认为false,即做完了。
实现过程,在MyCoin.h中加入
bool isAnimation = false; //做翻转动画的标志
实现过程,在MyCoin.cpp中加入

| 图40 |
|---|
19、翻转周围硬币
说明:点击硬币时,还需要将当前硬币周围上下左右4个硬币也进行延时翻转,代码写到监听硬币下。
此外,翻转硬币时还要注意,最左侧、右侧、上侧、下侧的几个位置的规则是不一样的。
(1)在playscene.h中,新增了如下代码:

| 图41 |
|---|
(2)在playscene.cpp中,新增了如下代码:

| 图42 |
|---|

| 图43 |
|---|
20、游戏胜利的检测以及禁用硬币点击
(1)检测游戏胜利
在playscene.h中新增以下代码:

| 图44 |
|---|
在playscene.cpp中新增以下代码:

| 图45 |
|---|
(2)禁用硬币翻转
在mycoin.h中新增以下代码

| 图46 |
|---|
在mycoin.cpp中新增以下代码

| 图47 |
|---|
21、游戏胜利后手速过快导致的bug
描述: 当手速过快时可能会出现下一步所示的bug,明明点击一步就赢了,但是在0.3s内又点了另外一个硬币,就会出现下图所示的情况。
原因: 这是因为周围的硬币是在0.3s后才翻转的,胜利检测也是如此,但是这时候玩家又点击了下一张个硬币,就会出现当前的bug。

| 图48 |
|---|
解决方法: 在playscene.cpp中加入以下代码
| 图49 |
|---|
22、胜利图片显示
(1)在playscene.cpp的构造函数中准备好胜利图片

| 图50 |
|---|
(2)当游戏胜利后,将图片以动画的效果显示出来

| 图51 |
|---|
23、音效的添加
(1)添加音效要用到#include < QSound > 头文件,在这之前需要在.pro文件中添加multimedia模块,如下图所示:

| 图52 |
|---|
(2)分别在三个场景中添加音效
- mainscene.cpp中添加开始音效

| 图53 |
|---|
- 在chooselevelscene.cpp中添加选择关卡和返回音效

| 图54 |
|---|
- 在playscene.cpp中添加翻转金币、返回和游戏胜利音效

| 图55 |
|---|
24、bug解决—场景位置统一
该游戏有三个场景,但是如果在不同场景下去移动位置,之后点击返回按钮,这三个场景的位置会不在一起,因此最好固定三个场景的位置。
(1)开始场景和选关场景的解决
| 图56 |
|---|

| 图57 |
|---|
25、游戏辅助玩法介绍
(1)存档功能
(2)倒计时功能
(3)提示功能
(4)逐关解锁功能
26、项目打包发布
(1)首先将项目从debug模式改成release模式,点击左下方的电脑图标。
| 图58 |
|---|
(2)点击运行
(3)之后就可以在资源路径下找到release文件夹,找到相应的exe文件
| 图59 |
|---|
双击运行,会发现有错误提示(如图60所示),这是因为没有配置环境变量,需要图61的方法加入环境变量。但是如果我们想让其他人也玩这个游戏,那岂不是得让别人也安装QT,也配置环境变量。(当然不是)
| 图60 |
|---|

| 图61 |
|---|
那么如何真正的打包发布呢?
首先在开始菜单中找到MinnGW软件,然后再看看自己的安装路径下有没有w开头的exe文程序,如图63所示,有的话说明具备打包发布的功能。
| 图62 |
|---|

| 图63 |
|---|
将刚release生成的CoinFlip.exe单独放在一个文件夹,哪里都行。我放在了桌面的111文件夹中

| 图64 |
|---|
双击启动开始菜单中的MinnGW软件,然后输入以下代码:
//windeployqt 加 刚刚release出来的exe软件全部路径名称(我放在桌面了)
windeployqt C:\Users\Lenovo\Desktop\111\CoinFlip.exe

| 图65 |
|---|
执行完毕后就能看到,文件夹中多出了许多文件,并且这时候点击运行发现也能正常玩了,如果要发给别人,需要将这里面的所有文件一起发过去。

| 图66 |
|---|
最后优化:可以看到上述游戏要发给别人玩会发现附带了许多配置文件,很冗余的感觉,但是像在应用商店里面下载的游戏都是给出一个安装包,然后选择路径一步一步安装,最后不想玩了还可以卸载。那么QT能不能做到呢?当然能,但是这就需要第三方工具。
nisedit2.0.3.exe和nsis-3.05-setup.exe
有需要的小伙伴可以自行去了解一下这两款软件。
总算是写完了,可以看到制作一个这么小的小游戏也没有想象中的那么简单,一个人要弄的话也要弄好久。麻雀虽小,五脏俱全。
能看到这也不容易,希望你也有所收获,最后,如果觉得本文对你有所帮助的话,希望能点赞收藏,您的鼓励是对我最大的支持!
&spm=1001.2101.3001.5002&articleId=123178966&d=1&t=3&u=8400ce55c6964d34835196fc666c78f8)
9779

被折叠的 条评论
为什么被折叠?



