前言
最近为了学习C++和qt5,跟着教程写了一个翻金币的小游戏,源码和资源文件的链接在这里:翻金币小游戏源码和资源文件的下载链接。里面有自己写的超多代码注释,只要2个C币,去秒。
第四章我们分析了这个小游戏的游戏机制,现在我们开始想办法去实现他们。
初始界面的数据结构
第一关的初始界面如上图所示。
经过上一章的分析,我们认为应该用二维数组保存初始游戏阵列。另外,每一关都对应一个特定的二维数组,这个“一一对应”的关系应当如何实现呢?我们可以立刻想到哈希表,也就是依靠键值对的映射关系来确定每个关卡和二维数组的对应关系。
于是我们按照这个思路,可以创建一个如下所示的哈希表:
QMap<int, QVector< QVector<int> > >mData;
//该成员变量的索引方式为 mData[int][int][int],依次为关卡数、横坐标、纵坐标
在QT中尽量使用QT框架提供的数据类型,因此这里使用了QMap
和QVector
分别代替了map
和vector
。此外,该变量的索引方式也值得注意。mData[int]
索引得到是对应关卡的二维数组的名称,当具体到二维数组里面的坐标时应该使用mData[int][int][int]
。
我们以第一关为例,看看第一个键值对如何构建:
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);//将关卡数与初始分布组成一个键值对 插入到map中
这里每次都新建一个普通的int
型二维数组,再将该int
型二维数组的值都赋给一个QVector< QVector<int>>
的二维数组。
二维数组的赋值方式是多样化的,除了上面的方法外,还可以直接给QVector< QVector<int>>
类型的二维数组赋值:
QVector< QVector<int>> v(4,QVector<int>(4));
v[0] = {
1, 1, 1, 1};
v[1] = {
1, 1, 0, 1};
v[2] = {
1, 0, 0, 0};
v[3] = {
1, 1, 0, 1};
剩下的关卡对应的二维数组如法炮制即可。
核心元素金币类
金币类的设计和实现是整个工程中最复杂的一部分,因此我们拆分其功能再做设计。
目前来看金币类完成了以下的功能:金币正反面的区分、金币图片的加载、金币翻转的功能,金币翻转的动画。
首先是正反面的区分,这个比较简单,使用一个标志位即可。
bool m_flag;//钱币正与反的标志位 1是金币(正面) 0是银币(反面)
金币图片的加载写在构造函数中即可。这里我们想到,需要在游戏运行的源文件里加载关卡排列的二维数组,分辨出每个位置应当放置金币还是银币,因此我们不在构造函数中去区分金币银币,只在构造函数中加载图片就好了。
MyCoin::MyCoin(QString btnImg){
QPixmap pix;
bool ret = pix.load(btnImg);
if(!ret){
qDebug()<<"loading error!";
return;
}
}
金币翻转的本质其实就是对标志位的改变,本质上也很简单:
void MyCoin::changeFlag(){
if(this->m_flag==true){
this->m_flag = false;
}else{
this->m_flag = true;
}
}
但是金币翻转的动画就比较复杂了。一方面,翻转的动画效果涉及多种工具,比较复杂;另一方面,翻转的动画效果需要一段时间去实现,在游戏的运行过程中,这个“一段时间”是不容忽视的存在,会连带程序中的其他代码进行更改。因此我们将金币翻转的动画单独作为一节来记录。
金币翻转动画实现
我们回想一下真正的“动画”是怎么实现的。当前主流的动画片(也未必非得是动画片,大部分影视作品)是24帧率,也就是说在一秒钟内,以相同的间隔播放了24张图片。也就是说这世上本无视频,图片连续放的快了就是视频。
那么我们的金币翻转动画也是一样的实现方式:准备一定数量的图片,当然这些图片在动作内容上应该是连续的,不然播的效果就没效果。随后设定一个固定的时间间隔,每次都播放一张图片即可。
这里我们的设计是:每次翻转使用8张图片,两张图片之间的间隔为30ms。
30ms的间隔使用定时器可以轻松实现,在8张图片的处理上我们使用一点小技巧。
我们以正面翻到反面为例,我们先在构造函数中实例化一个定时器,显然,在切换的时候打开这个定时器:
if(this->m_flag==true){
timer1->start(30);//每幅30ms
}
以下是控制金币翻转动画的定时器触发函数:
connect(timer1,&QTimer::timeout,[=](){
//正面翻到反面,金币翻成银币
QPixmap pix;
QString str = QString(":/res/Coin000%1.png").arg(this->min++);//从第一张图片一直播放到第八张图片
pix.load(str);
this->setFixedSize(pix.width(),pix.height());
this->setStyleSheet("QPushButton{border:0px;}");//设定轮廓,不然还是矩形按钮
this->setIcon(pix);//设定图标
this->setIconSize(QSize(pix.width(),pix.height()));//设定图标的大小
if(this->min > this->max){
//播放结束
timer1->stop();//定时器关掉
this->min = 1;//因为这次操作将min改变了,所以需要复位
}
});
除了耳熟能详的图片加载代码外,出现了很特别的图片切换代码:
QPixmap pix;
QString str = QString(":/res/Coin000%1.png").arg(this->min++);//从第一张图片一直播放到第八张图片
pix.load(str);
if(this->min > this->max){
//播放结束
timer1->stop();//定时器关掉
this->min = 1;//因为这次操作将min改变了,所以需要复位
}
结合金币翻转的图片命名,很容易就能看出端倪:
我们在这里为每个图片的名字表上序号,通过对序号的自增操作实现对图片的选取。
转载:https://blog.csdn.net/m0_37872216/article/details/117525495