Qt学习总结——飞机大战小游戏制作
这篇文章写于2020年暑假,完成学校实训项目之后,对自己的项目实践做了一个总结,回顾整个项目的制作过程,同时也复习一下Qt的相关知识,总结项目制作过程中出现的不足之处。
如果有同学想尝试使用Qt制作飞机大战的小游戏,这里推荐两个教程(https://www.write-bug.com/article/1451.html)(https://baijiahao.baidu.com/s?id=1658135336435450610)。
第一个教程使用的是QGraphicsScene作为窗体制作,但是我对QWidget更加熟悉,所以只借用了其中的图片素材,如果有机会我还是愿意尝试用QGraphicsScene写这个程序,一些界面美化方面可能能做得更好
接下来开始对项目进行分析,飞机大战需要做到的基本要求有以下几点:
1.我方飞机和敌方飞机的显示和移动
2.我方飞机和敌方飞机可以发射子弹
3.子弹与飞机,飞机与飞机发生碰撞,飞机要受到伤害
以上是飞机大战的基本要求,而我在设计项目的时候再添加了几点特点。
1.游戏可以选择关卡和难度 2.我方飞机可以释放技能
3.随机掉落资源包
4.使用数据库制作排行榜供记录得分。
我们先实现飞机大战的基本需求,再去考虑后继增加的特殊需求。
在设计子弹类时,我觉得无论是敌机子弹还是我方飞机子弹,其共性是较多的,所以我就直接将两者合为一个类,如果以后需要添加其他种类飞机子弹,同样可以写进这个类中。因为只有子弹的图片和运动方向不同,所以我写了不同的update函数表示不同子弹的运动。isFree作为一个标志判断子弹是否空闲,即子弹是否在界面中,根据这个标志判断飞机发射哪颗子弹。
类的具体实现如下:`
我方飞机类的设计中,interval是飞机射击的间隔,recored是标记射击时间,比如初始recored=0;当recored = interval时才可以发射子弹,发射之后recored又要归零,重新叠加。并且interval是我方飞机独有的,为之后的技能设计做铺垫。飞机的弹匣myBullet[30]是一个子弹类的数组,每次发射子弹都是从数组中进行选择
类的具体实现如下
关于子弹位置的初始化不是直接在飞机的x,y位置下进行发射,因为飞机图片由长度宽度,对象坐标点并不在图片中心,所以需要调整使得子弹在我们需要的位置射出。
普通敌机类的设计基本思路跟我方飞机大致相同,但是我方飞机的移动是由玩家操控,而敌方飞机是自己运动,所以需要设置updatePosition函数,同时由于设计关卡的原因,飞机被摧毁后不可再生并且需要产生爆炸效果,所以添加了isDerstroyed标志判断是否被摧毁。
类的具体实现如下:
Boss类飞机相对于普通飞机而言更复杂,首先是设定了生命值为10,普通飞机的生命值默认为1,即被子弹打中就摧毁,而Boss可以抗十下。其次就在于Boss飞机的射击分为左中右三条线,所以设定了三个弹匣,在子弹类中我设计了三个敌机子弹位置的更新函数。
类的具体实现如下:
Boss飞机的射击函数与其他两个是差不多的,不过Boss飞机有三个弹匣,所以三个弹匣的循环都需要执行一遍。
实现爆炸效果动画实际上是用的七张图片在短时间内轮流播放而成的,所有爆炸图片存储在向量中,由updateInfo决定播放那张图片,当所有图片都播放完后,改变isPlayde的值(这里有拼写错误,我也是在总结的时候才发现的,所以后面的代码中这里都是isPlayde)。在updateInfo中,会对isPlayde进行判断,如果为false则播放,毕竟飞机只要炸一次就够了。
类的具体实现如下
做完了上述准备工作,我们可以进行游戏界面的设计了,我给这个界面命名为Easy,主要是我设置了几个关卡界面,这是其中之一,以这个为例来进行我们的游戏设计。
以上这些是我们在游戏界面设计中可能要用到的函数,当然现在还只是为了实现初级目标,有些东西还可以后续添加,最后四个int型函数检测距离的,实际编写的时候可以删去,这几个函数返回值都是两点之间的距离,但是之后有了一种更直观的判断两个对象是否接触的方法,待会可以再看。以上这些函数我将拆开一个个进行分析。
这两个的出场函数是不是很眼熟?与飞机发射子弹的shoot函数几乎一模一样,你可以理解为敌机就是游戏界面的子弹,这是游戏界面在发射敌机。不过子弹的生成位置必须在飞机上,而敌机的生成位置必须是x轴上的随机位置,并且由于图片的原因,还要保证生成的敌机不会跑到游戏界面之外。
之后我们需要编写一个地图更新函数updatePosition来不断刷新游戏中的元素
到这里为止,基本功能都已经实现了,我们接下来就是把游戏中的所有元素在界面中用paintEvent显示出来了。同时使用mouseMoveEvent实现鼠标拖拽飞机移动。
完成这些之后,飞机大战就已经基本做出来了,我们可以看一下效果图。
可以看出,我们所需要的基本元素都已经在图中显示出来了,包括我机和子弹,敌机Boss以及子弹,还有生命值和分数,爆炸效果没有在子弹击中效果虽然没有显示,但同样已经实现了,可以自己尝试。
基本操作都已经实现了,现在我们尝试添加一些创新型的内容
这个设计是游戏屏幕中随机掉落血包,如果飞机捡到就可以恢复一点血量,对于资源包,它所具备的性质和子弹或者飞机都是差不多的–从屏幕中随机出现,能和飞机发生碰撞。所以在设计的时候我是直接使用的子弹类来实现资源包的。具体做法如下:
在子弹类的头文件bullet.h中添加一个QPixmap变量
在Bullet的构造函数中添加图片加载
在关卡界面Easy.h中添加资源包声明,为子弹数组,并添加资源包的出场函数
接下来就是像实现敌机一样,来实现资源包的添加。
资源包的出场函数
将资源包的出场添加到与敌机出场同样的位置
接下来实现资源包的运动,在地图信息更新函数里,添加资源包的运动相关代码
然后就可以将它画出来了,在paintEvent函数中添加
接下来只要实现资源包和我机发生碰撞的事件就好了,可以参考敌机和我机碰撞函数情况,先对所有资源包进行遍历,判断是否空闲,如果不空闲则判断是否相撞,相撞则生命值回复一点,资源包空闲。
到此为止,资源包功能就完成实现了。
在飞机大战游戏中,我为自己的飞机简单添加了四个技能,Q技能是发射一枚导弹,W是回复一点生命值,E技能是增加攻速,R技能是造成全屏伤害。
既然设计了技能,那么就必须设置技能点,使用技能时消耗技能点,这样防止玩家无限使用技能。所以我们需要像设置生命值一样设置技能值,具体做法可以参考生命栏的设定。皇冠现金盘
这里需要注意一个地方,在updatePosition函数中,有两个语句设定了生命值和得分的修改`
技能点的修改使用同样的方法。
接下来设定技能。
使用技能是通过键盘操作的,所以我们需要一个keyEvent函数对键盘操作做出响应。
在关卡Easy的头文件里,添加keyEvent()函数声明。 cpp文件中进行实现
这段代码中我把四个技能直接添加进来了,接下来我再对每个具体的技能的实现思路进行以下说明。
Q技能,发射一枚导弹。其实所谓导弹与子弹性质是一样的,只不过与敌机碰撞时产生的效果不一样,并且导弹的发射是可控的。所以需要在飞机类中添加成员
而与普通子弹不同,BigBullet需要通过Q来释放,所以就在上面的代码中,按Q生成一个空闲的BigBullet。
但是,其他地方与普通子弹相同,BigBullet也需要通过paintEvent进行绘图,需要在updatePosition中进行运动的更新,以及colliecollisionDetetion中添加BigBullet与敌机发生碰撞时的事件,这些代码可以参考普通子弹,在这就不一一赘述了。
W技能,回复血量,技能值减少。这个技能实现比较简单,就不做说明了。
E技能是改变子弹的发射速度,也就是改变子弹的发射间隔,所以在之前我将子弹的发射间隔设置为一个变量interval。当我们按下E时,interval的值也会随之改变。但是,E技能是个状态类技能,所以有开有关,我们需要设置一个标志判断E技能处于何种状态,interval该怎么改变,在上面代码中,设置的标志就是Epressed,初始值为false。
而E技能同样需要消耗技能值,这个技能值的减少方式我设置的是随时间减少,所以在飞机的shoot函数中,需要根据interval判定是否需要减少技能值,当技能值为0的时候,即使处于增加攻速的状态,也应该改变为原来的攻速。
所以我们将Plane中的shoot()函数做如下修改:
R技能时造成清屏伤害,也就是让场中的飞机全部变为isDestroyed状态,所以代码也比较简单,直接修改敌机状态并增加分数。
考虑到技能点用完可能难以通关,为了增加游戏平衡性,我在每次飞机被摧毁时都添加了一行增加技能点的代码。
添加关卡是比较机械的一个事情,调整关卡的难度就是改变敌机的出场频率和敌机的数量,在做这件事情的时候有点后悔最开始没有把这些参数用宏定义,这样修改参数的时候就不用到处找,也不用担心漏掉一些地方没改。
多添加几个相似的类之后,再添加一个QWidget窗口作为关卡选择界面,并添加按钮,通过按钮打开不同的游戏界面,我的关卡选择界效果如下:
这个排版有点丑,1,2都是按钮,点击后打开一个游戏窗口,右上角的Ranking是排行榜按钮,关卡选择界面的代码如下:
先看一下排行榜的效果
排行榜有很多种方式进行实现,但是我们老师要求我们使用到数据库,所以我才不得已用的数据库来存储数据,如果没有特殊要求,使用本地文件存储数据也是可以的。这里使用的数据库是Qt自带的SQLite
排行榜的基本实现思路如下:在头文件中定义QSqlDatabase database;,之后建表都是关联这个datebase。在构造函数中,关联数据库并打开,新建一个表并初始化所有的值为0,再设定一个updateRanking函数用于更新排行榜中的数据,updateRanking是在游戏通关时才调用。
而updateRanking中,需要先连接表,再根据接收到的参数决定将分数插到哪个位置,排行榜的排序是通过插入实现的,我的方法是先将指定关卡的那一列数据拿出来存到一个数组中,再将新的数据插入到数组中,最后将这个数组中的数据出插入到表中。
直接上代码:
我的代码中标签的命名有点问题,太简单并且看不出作用,当时就是图个方便(千万不要学我)
游戏中有关卡的选择,那么就需要有通关界面和失败界面ÿqt游戏娱乐0c;在排行榜中有提到,只有通过关卡时才会将分数存入排行榜中。设计通关和失败,就需要对通关或者失败的条件做出判定。如果我方飞机生命值小于等于0,则判定为失败;当所有飞机出场且被摧毁时,则判定通关。
关于通关与否的判定应该设计在关卡中,而通关界面与失败界面需要在项目中添加两个新的类:endGame和winGame,具体代码就不贴了,无非就是几个功能按钮。
在游戏关卡类中,我添加了两个函数来调用这两个类
GameWin函数中,除了生成一个界面,同时需要将关卡信息和分数传递到排行榜类中,供记录数据。而其中的win标志表示游戏是否通关,在构造函数中初始化win为false,在碰撞检测函数的最后一部分中添加改变win值得代码。
同样的,这两个函数应该时刻处于检测状态,所以将其放到startGame的connect中。
除了这些,我还添加了游戏初始界面,其中有三个按钮:开始游戏,游戏帮助和退出游戏,点击开始游戏即进入关卡选择界面;并且为游戏添加了开场动画,代码比较简单
游戏初始界面代码:
效果图:
qt游戏娱乐qt游戏娱乐
改变应用程序图标得方法是将ico文件放到和代码同一目录,然后在项目的pro文件中添qt游戏娱乐加
=后面的即ico文件名称。