基于Qt的连连看小游戏(总结)
自学Qt也有一段时间了,最近用Qt写了一个连连看的小游戏。总结一下在这个小程序中用到的技术。
为了把游戏的消除逻辑和界面分开,分别设计了两个类来进行封装,此外还设计了一个类用于设定关卡和计算得分。
-消除类
游戏中要消除的方块使用的是QPushButton,可以根据isCheck()判断按钮是否被选中。为了标志两个按钮的图案(图标)是否相同,继承了QPushButton类实现了自己的自定义按钮,并在按钮中添加了成员变量flag,把具有相同图标的按钮设定为相同flag。
用QVector类保存在游戏中要用到的button,因为QVector是变长的,可以很方便的从里面增加或者删除元素,而且可以用下标访问里面的元素。
进行消除判定时,首先需要找到两个被选中的按钮。找到相同按钮有几种思路:
1).用一个循环不断遍历Vector中的元素,以找到两个被选中的按钮。
这种方法十分耗时,并且容易造成界面卡死。舍弃。
(如果在Qt中进行比较耗时的操作时,可以创建一个线程单独运行耗时操作)
2).不是连续不断遍历数组,而是创建一个定时器,每隔一定时间间隔(比如100ms)遍历一次Vector,找到两个被选中的按钮。
这种思路可行,但是同样可能会对Vector进行一些不必要的访问
3).通过查找帮助手册,QObject类有一个sender()成员函数,可以获得发出信号的对象的指针。按钮被点击时会发出clicked()信号,因此先用一个循环将Vector中的所有按钮的clicked()信号和查找按钮的槽函数连接起来,在槽函数中调用sender函数,这样每当有按钮被点击时,就可以得到被点击的按钮的指针。
该程序中使用的是方案三。代码如下:
//连接按钮的信号和槽
for( int i=0; i<m_hor*m_ver; i++ )
connect( m_block[i], SIGNAL(clicked()),this,SLOT(runGame()));
//在槽中调用sender()函数得到被点击的按钮的指针
MyPushButton* btn = qobject_cast<MyPushButton*>(QObject::sender());
找到两个按钮后,需要判断这两个按钮是否可以消除。思路是从当前按钮出发,向上、下、左、右四个方向寻找目标按钮,如果没有找到,就以下一个按钮作为当前按钮。用递归来实现。在递归中要注意的两个问题:
1).为了避免无限递归,该按钮不应向它的上一个按钮进行递归调用。
2).连连看的规则要求两个消除方块间的连线不能超过三条,即按钮的转弯次数不能超过两次。所以在递归函数中增加一个参数传入转弯次数。此外,转弯次数不超过两次也可以避免递归调用回到原始位置。
递归的部分代码如下:
if(dir != DOWN)
{
//如果转弯,转弯次数+1
if(dir==LEFT || dir==RIGHT)
turn++;
if(current > current%m_hor)
{
if(current-m_hor == dest && turn <= 2)
return true;
else if(!m_block[current-m_hor]->isVisible())
{
if(judgement(current-m_hor,dest,UP,turn))
return true;
}
}
//如果没有找到目标按钮,恢复转弯次数
if(dir == LEFT || dir == RIGHT)
turn--;
}
-界面类
-多界面切换
游戏共有四个界面:主界面、游戏界面、规则说明界面及最高分界面。使用Qt的QStackWidget类添加界面,用setCurrentIndex(int)函数切换界面。
-绘制背景图片
重写QPaintEvent()函数
QPainter painter (this);
//绘制主界面
QRect rcMain = rect();
painter.setClipRect(rcMain);
for (int m = rcMain.left (); m < rcMain.right ();
m += m_mainBackground.width ())
for (int n = rcMain.top ();
n < rcMain.bottom ();
n += m_mainBackground.height ())
painter.drawImage(m,n,m_mainBackground);
-设置按钮在不同状态下的显示
通过样式表设定按钮的不同显示状态。
可以直接在按钮上右键点击,选择改变样式表,设置样式表:
也可以在代码中设置:
btnStart->setStyleSheet(
"QPushButton{border-image:url(:image/Image/red2.png)};"
"QPushButton:hover{border-image:url(":image/Image/red2clicked.png")};");
设置好的按钮:
正常态:
悬停态(鼠标停留在按钮上时):
-设置时间槽样式
游戏中的时间槽用的QSlider类。为了让游戏的时间槽更好看一点,也可以通过样式表实现。
设置好的时间槽:
在设置样式表时需要注意的一个问题是,如果给当前窗口设置了样式表,该窗口的所有子部件也会被设置为相同的样式表
-背景音乐
最开始用QSound类的对象播放背景音乐,该类中有一个setLoops(int)成员函数,当参数为-1时,表示无限循环播放。该类只支持WAV格式的音频。
在该程序中,采用Phonon多媒体框架来播放音频。
使用Phonon需要在项目文件中添加 QT+=Phonon。
音频/或者视频不能放在资源文件中,只能以外部文件形式存在。
//添加并播放背景音乐
mediaObject = new Phonon::MediaObject(this);
mediaObject->setCurrentSource(Phonon::MediaSource("../LinkGame1.1/music/music.wav"));
Phonon::AudioOutput *audioOutput = new Phonon::AudioOutput(Phonon::MusicCategory, this);
Phonon::createPath(mediaObject, audioOutput);
mediaObject->play();
怎样实现循环播放呢?查找帮助手册,MediaObject类中有一个aboutToFinish()信号,当播放将要结束时该信号会被发出,于是通过这个信号连接一个槽函数,在槽函数中再次播放音频。
connect(mediaObject,SIGNAL(aboutToFinish()),this,SLOT(playMusic()));
void LinkWidget::playMusic()
{
mediaObject->setCurrentSource(Phonon::MediaSource("../LinkGame1.1/music/music.wav"));
mediaObject->play();
}
这样即可实现背景音乐的循环播放
-音量调节
volumeSlider = new Phonon::VolumeSlider(this);
volumeSlider->setAudioOutput(audioOutput);
volumeSlider->setSizePolicy(QSizePolicy::Fixed,QSizePolicy::Fixed);
volumeSlider->resize(150,8);
ui->horLayout->addWidget(volumeSlider);
-得分类
该类中定义计分规则,包括连击数的计算,时间得分等,并根据消除的情况进行计分
-更新最高分
利用QFile读写文件
QFile file("../LinkGame1.1/hs.dat");
file.open(QIODevice::ReadOnly);
if(file.error() !=0 )
{
qDebug() << "open file failed!";
return m_highScore;
}
QByteArray byte(20,'0');
byte = file.readAll();
if(file.error())
{
qDebug() << "read error!\n";
file.close();
return m_highScore;
}
bool ok;
m_highScore = byte.toInt(&ok);
file.close();
-生成和发布
生成release版本,将release文件夹中生成的exe可执行文件放入一个单独的文件夹,从Qt安装目录的bin目录下复制mingwm10.dll、lib_gcc_s_dw2-1.dll、QtCore4.dll、QtGui4.dll这四个文件到exe所在的目录,因为程序中使用了Phonon,所以还要复制phonon.dll文件和用到的音频/视频文件。此外,因为在程序中使用了非PNG格式的图片以及Phonon,所以还要复制Qt/plugins下的imageformats文件夹和phonon_backend文件夹。复制完打包之后就可以在别人的电脑上运行了。
本人第一次写博客,非常希望能和各位大牛们一起探讨学习。
附:游戏截图(图片素材来自于百度)
PS:自己P的界面,自己先吐槽一下。。。毕竟不是美工