做这个项目记录的一些笔记。 说明:翻金币项目是一款经典的益智类游戏,我们需要将金币都翻成同色,才视为胜利。首先,开始、选关、游戏、胜利界面如下: 游戏的代码和软件资源请点击自取[资源在此](提取码: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、主场景的搭建
前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 有需要的小伙伴可以自行去了解一下这两款软件。
总算是写完了,可以看到制作一个这么小的小游戏也没有想象中的那么简单,一个人要弄的话也要弄好久。麻雀虽小,五脏俱全。 能看到这也不容易,希望你也有所收获,最后,如果觉得本文对你有所帮助的话,希望能点赞收藏,您的鼓励是对我最大的支持!
|