做这个项目记录的一些笔记。 说明:翻金币项目是一款经典的益智类游戏,我们需要将金币都翻成同色,才视为胜利。首先,开始、选关、游戏、胜利界面如下: 游戏的代码和软件资源请点击自取[资源在此] (提取码:7758,制作不易,希望点个赞,非常感谢)
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 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) ;
}
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 :
MyPushButton ( QString normalImg, QString pressImg= " " ) ;
QString normalPath;
QString pressPath;
signals:
public slots:
} ;
#endif
(3)mypushbutton.cpp中实现,代码如下:
#include "mypushbutton.h"
#include <QDebug>
MyPushButton:: MyPushButton ( QString normalImg, QString pressImg)
{
this - > normalPath = normalImg;
this - > pressPath = pressImg;
QPixmap pix;
bool ret = pix. load ( this - > normalPath) ;
if ( ! ret)
{
QString str = QString ( "图片加载失败,失败的路径是: %1" ) . arg ( this - > normalPath) ;
qDebug ( ) << str;
}
this - > setFixedSize ( pix. width ( ) , pix. height ( ) ) ;
this - > setStyleSheet ( "QPushButton{border:0px;}" ) ;
this - > setIcon ( pix) ;
this - > setIconSize ( QSize ( pix. width ( ) , pix. height ( ) ) ) ;
}
在mainscene.cpp中,显示了开始按钮,效果如下
5、开始按钮跳跃效果实现
在MyPushButton中封装zoom1和zoom2函数实现跳跃
(1)在mypushbutton.h中添加了下图3所示的代码
(2)在mypushbutton.cpp中添加了下图4所示的代码,值得注意的是,在创建动画这一行代码中,动画的方式别打错字,否则可能出现以下报错QPropertyAnimation: you're trying to animate a non-existing property grometry of your QObject
,导致点击按钮没反应。
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 ( QAbstractAnimation:: DeleteWhenStopped) ;
}
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 ( QAbstractAnimation:: DeleteWhenStopped) ;
}
(3)在mainscene中调用这两个函数进行测试
6、选择关卡场景的基本配置
(1)创建ChooseLevelScene 选择关卡场景
(2)在主场景中维护了第二个场景的指针
(3)在主场景的cpp中去创建第二个场景,并且点击开始按钮进行跳转(有一个延时进入)
在chooselevelscene.cpp中用代码编写出了菜单栏 ,并且编写了背景的绘图事件
7、返回按钮的创建
(1)在选关场景中创建返回按钮,重写按钮的鼠标按下和鼠标释放事件。具体效果为:当鼠标按下的时候显示另外一张图片,当鼠标松开时恢复出原来的效果。
(2)在自定义的mypushbutton.h中重写了鼠标按下 和鼠标释放 事件
(3)在mypushbutton.cpp中实现了鼠标按下 和鼠标释放 事件
8、返回按钮的功能实现
功能描述:点击返回按钮时,能够回到主场景。 实现手段:利用信号和槽。 (1)选择关卡场景的.h中定义了返回按钮的信号
(2)选择关卡场景的.cpp文件中实现了返回按钮的信号
在主场景的.cpp文件中,监听了返回按钮的信号。
9、创建选择关卡的小按钮
由于是按钮,故直接可通过自己编写的mypushbutton类来创建。 因此,在选择关卡chooselevelscene.cpp中直接加入以下代码即可:
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 ) ;
connect ( menuBtn, & MyPushButton:: clicked, [ = ] ( ) {
qDebug ( ) << "您选择的是第" << i+ 1 << "关" ;
} ) ;
}
效果演示如下:
核心代码说明 : 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型数据转化成字符串型。 具体效果如下:会发现该方法无法得到理想的效果,因为原先的按钮是矩形的,我们将其改为了圆形,因此文本也无法对应。
实现思路2: 利用QLabel标签。 具体代码如下:
QLabel * label = new QLabel;
label- > setParent ( this ) ;
label- > move ( 25 + ( i% 4 ) * 70 , 130 + ( i/ 4 ) * 70 ) ;
label- > setFixedSize ( menuBtn- > width ( ) , menuBtn- > height ( ) ) ;
label- > setText ( QString:: number ( i+ 1 ) ) ;
label- > setAlignment ( Qt:: AlignHCenter| Qt:: AlignVCenter) ;
label- > setAttribute ( Qt:: WA_TransparentForMouseEvents) ;
11、翻金币场景的创建及基本搭建
(1)创建翻金币场景:创建PlayScene.h和PlayScene.cpp文件(父类为QMainWindow) (2)在选择关卡场景chooselevelscene.h中维护 第三个场景的指针。
(3)当用户点击选关按钮,创建第三个场景。
(4)游戏场景的基本搭建 即绘图事件,在playscene.h和playscene.cpp中的代码分别为:
12、翻金币场景的返回按钮实现
(1)创建返回按钮。 (2)点击时发送自定义信号。 (3)在选关场景(第2场景)中监听翻金币游戏场景的信号,并作响应。
在playscene.h中设置了一个返回按钮的信号
在playscene.cpp中实现了信号的发射
在ChooseLevelScene.cpp中监听了游戏场景发送回的信号(特别要注意监听信号的代码所在的范围,这种bug极难发现 )
具体新增代码如下图所示
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));
14、金币背景图加载
(1)在playscene.cpp中新增了如下代码,注意 ,在新增代码中用到了两层for循环,这是为了在游戏规则处理时更加方便的选择硬币上下左右的关系。
效果如图28所示:
15、金币类创建
我们知道,金币是本游戏的核心对象,并且在游戏中可以利用二维数组进行维护,拥有支持点击,翻转特效等特殊性,因此不妨将金币单独封装到一个类中,完成金币所需的所有功能。 (1)创建一个MyCoin类,父类为QWidget,创建成功以后,将其构造函数的定义与实现删除,并将其父类改成QPushButton(头文件和继承都要改) (2)MyCoin.h中的代码
(3)MyCoin.cpp中的代码
(4)playscene.cpp中的代码
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;
signals:
public slots:
} ;
#endif
下面给出了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中新增了以下代码,有用于给每个关卡的金币、银币进行图案排版:
效果图如下,最后一关是全银币的。
17、翻转银币的功能实现(核心:切图)
(1)如下图所示,为了方便翻转硬币,在金币类中,拓展了属性posX、posY和flag。这三个属性分别代表了,该金币在二维数组中的x坐标,y坐标以及当前的正反标志(也就是是金币还是银币)
int posX;
int posY;
bool flag;
(2)关卡的初始化完成后,就应该点击金币,进行翻转的效果了,那么首先在MyCoin类中创建出该方法。 在MyCoin.h中声明:
void changeFlage ( ) ;
QTimer * timer1;
QTimer * timer2;
int min = 1 ;
int max = 8 ;
(3)在MyCoin.cpp中实现:
(4)在playscene.cpp中新增的测试代码如下
18、快速点击导致的翻转效果不完整
问题说明 ,当鼠标快速点击金币时,会出现金币翻银币,银币翻金币,然后又是银币翻金币。说白了就是金币翻银币的动作还没做,银币翻金币的动作就开始了。 解决方法 ,要将整个动作做完才能执行下一个动作(要做一些限制)。 加一个标志如isAnimation,用于表示当前金币是否做完了动画,默认为false,即做完了。 实现过程,在MyCoin.h中加入
bool isAnimation = false ;
实现过程,在MyCoin.cpp中加入
19、翻转周围硬币
说明:点击硬币时,还需要将当前硬币周围上下左右4个硬币也进行延时翻转,代码写到监听硬币下。 此外,翻转硬币时还要注意,最左侧、右侧、上侧、下侧的几个位置的规则是不一样的。 (1)在playscene.h中,新增了如下代码:
(2)在playscene.cpp中,新增了如下代码:
20、游戏胜利的检测以及禁用硬币点击
(1)检测游戏胜利 在playscene.h中新增以下代码:
在playscene.cpp中新增以下代码:
(2)禁用硬币翻转 在mycoin.h中新增以下代码
在mycoin.cpp中新增以下代码
21、游戏胜利后手速过快导致的bug
描述: 当手速过快时可能会出现下一步所示的bug,明明点击一步就赢了,但是在0.3s内又点了另外一个硬币,就会出现下图所示的情况。 原因: 这是因为周围的硬币是在0.3s后才翻转的,胜利检测也是如此,但是这时候玩家又点击了下一张个硬币,就会出现当前的bug。
解决方法: 在playscene.cpp中加入以下代码
22、胜利图片显示
(1)在playscene.cpp的构造函数中准备好胜利图片
(2)当游戏胜利后,将图片以动画的效果显示出来
23、音效的添加
(1)添加音效要用到#include < QSound > 头文件,在这之前需要在.pro文件中添加multimedia 模块,如下图所示:
(2)分别在三个场景中添加音效
mainscene.cpp中添加开始音效
在chooselevelscene.cpp中添加选择关卡 和返回音效
在playscene.cpp中添加翻转金币 、返回 和游戏胜利 音效
24、bug解决—场景位置统一
该游戏有三个场景,但是如果在不同场景下去移动位置,之后点击返回按钮,这三个场景的位置会不在一起,因此最好固定三个场景的位置。 (1)开始场景和选关场景的解决
25、游戏辅助玩法介绍
(1)存档功能 (2)倒计时功能 (3)提示功能 (4)逐关解锁功能
26、项目打包发布
(1)首先将项目从debug 模式改成release 模式,点击左下方的电脑图标。
(2)点击运行 (3)之后就可以在资源路径下找到release文件夹,找到相应的exe文件
双击运行,会发现有错误提示(如图60所示),这是因为没有配置环境变量,需要图61的方法加入环境变量。但是如果我们想让其他人也玩这个游戏,那岂不是得让别人也安装QT,也配置环境变量 。(当然不是)
那么如何真正的打包发布呢? 首先在开始菜单中找到MinnGW软件,然后再看看自己的安装路径下有没有w开头的exe文程序,如图63所示,有的话说明具备打包发布的功能。
将刚release生成的CoinFlip.exe 单独放在一个文件夹,哪里都行。我放在了桌面的111文件夹中
双击启动开始菜单中的MinnGW软件,然后输入以下代码:
//windeployqt 加 刚刚release出来的exe软件全部路径名称(我放在桌面了)
windeployqt C:\Users\Lenovo\Desktop\111\CoinFlip.exe
执行完毕后就能看到,文件夹中多出了许多文件,并且这时候点击运行发现也能正常玩了,如果要发给别人,需要将这里面的所有文件一起发过去。
最后优化 :可以看到上述游戏要发给别人玩会发现附带了许多配置文件,很冗余的感觉,但是像在应用商店里面下载的游戏都是给出一个安装包,然后选择路径一步一步安装,最后不想玩了还可以卸载。那么QT能不能做到呢?当然能,但是这就需要第三方工具。 nisedit2.0.3.exe 和nsis-3.05-setup.exe 有需要的小伙伴可以自行去了解一下这两款软件。
总算是写完了,可以看到制作一个这么小的小游戏也没有想象中的那么简单,一个人要弄的话也要弄好久。麻雀虽小,五脏俱全。 能看到这也不容易,希望你也有所收获,最后,如果觉得本文对你有所帮助的话,希望能点赞收藏,您的鼓励是对我最大的支持!