在此简单记录一下如何用Java实现单机版飞机大战模式。
一、实验环境。
实验用到Windows10操作系统,主要开发工具是IntelliJ IDEA 2021.3.2和Java 11。
二、系统功能分析。
1、飞机大战功能图如下:
2、类的继承关系分析:
抽象类AbstractFlyingObject是所有飞行物体的父类,抽象类AbstractAircraft是所有飞机的父类,抽象类AbstractEnemy是所有敌机的父类,抽象类BaseBullet是所有子弹的父类,抽象类AbstractProp是所有道具的父类。
其中,敌机与道具通过工厂产生。
三、设计模式的应用。
1、单例模式。
在设置英雄机时需要用到单例模式。在飞机大战中,我们只需要一架英雄机,即需要保证只有一个实例。
而此单例模式所设计的英雄机,可以确保同一时间只能有一架英雄机。
主要代码如下:
下面是相关UML类图:
类HeroAircraft用于构造和实现英雄机。
关键:
其构造函数HeroAircraft是私有的。
通过双重检查锁定getInstance()来构造唯一实例英雄机,返回英雄机实例instance。
2、工厂模式。
在创建敌机和掉落道具时,需要用到工厂模式。工厂模式是通过扩展来新增具体类,符合开闭原则,而且工厂模式的横向扩展很方便,不用去修改原有已经存在的代码。
下面仅提供加血道具的代码,其他的实现方式也是类似的:
下面是相关UML类图:
抽象类AbstractEnemy充当敌机角色,类MobEnemy、EliteEnemy、Boss为实现AbstractEnemy的实体类,充当具体敌机角色。
工厂接口EnemyFactory充当创建者角色,类MobFactory、EliteFactory、BossFactory为实现EnemyFactory的具体工厂类,充当具体创建者角色。
关键:
方法createEnemy()是具体创建各种敌机的方法。
———————————————————————————————————————————
抽象类AbstractProp充当道具角色,类PropBlood、PropBomb、PropBullet为实现AbstractProp的实体类,充当具体道具角色。
工厂接口PropFactory充当创建者角色,类BloodFactory、BombFactory、BulletFactory为实现PropFactory的具体工厂类,充当具体创建者角色。
关键:
方法function()是区别各种道具功能的方法。
方法createProp()是具体创建各种道具的方法。
3、策略模式。
在飞机大战游戏中,英雄机和各种类型的敌机发射子弹的图片、子弹数量、火力值和弹道都不同。所以需要采用策略模式实现不同机型的弹道发射和火力道具加成效果。
未采用策略模式前,不同机型的子弹发射方法具有很多重复,不利于维护。而采用策略模式后,如果想要改变或者添加子弹发射的方法,只需要在外部改变就可以了。符合开闭原则。
部分代码如下:
下面是相关UML类图:
接口StrategyShoot充当抽象策略角色,内含抽象方法fight为子弹发射的方法;
类Zhishoot和SanShoot分别实现直射和散射,充当具体策略角色;(散射的实现方式或许不太妥当,因为太多if-else了,但比较好理解,且能运行)
抽象类AbstractAircraft内含有类Context,在其子类HeroAircraft、MobAircraft、EliteAircraft、Boss中,使用Context来调用子弹发射方法。
4、数据对象访问模式。
飞机大战游戏中,在每局游戏结束后,记录玩家名字、得分、名次等内容时,需要用到数据访问对象模式。
数据访问对象模式用于把低级的数据访问API或操作从高级的业务服务中分离出来,便于提高代码的整洁度,也有利于维护,符合开闭原则。
部分代码如下:
相关UML类图如下:
类PlayerScore为数值对象VO实体类;接口PlayerScoreDao为数据访问对象DAO接口;类ScoreDaoImpl为实现了上述接口的DAO实现类,其中函数readFile()作用是将文件中的分数数据读取出来;clearFile()作用是清空文件内容;writeFile()作用是将分数重新写进文件;函数getAllScores()作用是得到所有历史分数排名记录,并将其重新按分数由大到小排序;函数doAdd()作用是新增分数记录;在Game中创建Print()函数实现数据访问,并打印成绩排名。
5、观察者模式。
在飞机大战中,道具炸弹的实现需要用到观察者模式。炸弹道具作为观察目标,而炸弹生效后,除boss机外的所有敌机和敌机子弹都会被清除,所以,除boss机外的所有敌机和敌机子弹作为观察者。
在未用此模式前,如果想要炸弹生效后对某一个对象多加一个效果,那么代码则需要改变很多东西。而用了观察者模式后,只需要添加一个观察者,然后重写观察者观测到目标后的反应函数,符合开闭原则。
相关部分代码如下:
对应UML类图如下:
抽象类AbstractAim为抽象目标,类AimBomb为具体目标。接口MyObverse为抽象观察者,类EnemyBulletList和EnemyList为具体观察者。类PropBomb测试程序。
其中,函数notice()的作用是给所有登记过的观察者发出通知;函数response()是实现不同观察者对目标的不同反应。
6、模板模式。
在飞机大战游戏中,不同难度的实现需要用到模板模式。模板模式允许子类在不修改结构的情况下重写算法的特定步骤。
未用此模式时,实现不同难度所创建的类中,含有大量的重复代码。而采用此模式后,只需在子类中重写某几个方法即可。
部分代码如下:
相关UML类图如下:
抽象类Game声明作为算法步骤的方法,以及依次调用它们实际的模板方法,其中模板方法为action()。
具体类EasyGame、NormalGame、HardGame重写自己需要的某些特定步骤,比如:敌机产生方法enemyProduce()、绘画图片方法paint(),但不能重写模板方法自身。
在EasyGame中,不能产生boss机,同一时间存在的敌机最大数量为3,而且难度不会随时间变化;
在NormalGame中,能够产生boss机,但每一次产生的boss机的血量不变,另外,敌机的速度、生命值会随着时间增加,同一时间存在的敌机最大数量为4;
在HardGame中,能产生boss机,且boss机产生的得分阈值更小,每次产生boss机,boss机的生命值都会增加,另外,初始敌机最大数量为4,敌机的最大数量、速度、生命值都会随着时间增加,且精英敌机产生概率比其他两个模式更大。
四、线程实现。
在音效设计或者游戏难度选择方面,用到了线程,下面简单说一下我是怎么搞的。
1、音效。
对于音效,分为普通背景音乐以及需要条件触发的特殊音乐(包括子弹击中敌机的声音、道具生效的声音、boss机出现后拥有专属背景音乐以及其被击败后恢复到普通背景音乐等等)。
我的思路是开始游戏时,就先让普通背景音乐循环播放,然后当特殊条件达到时,就在相应代码的位置调用线程。
部分代码如下:
2、游戏难度选择。
在开始界面,用户选择游戏难度后,就调用线程,跳转到main类的游戏创建。
部分代码如下:
五、总结。
这门课主要是让大家初步接触、熟悉并运用设计模式,当大家熟练后,自己写个单机版的飞机大战其实也不用这么麻烦(身边大佬说的,不是我说的)。
总之,这篇文章就仅供参考吧(反正你们也抄袭不了,嘿嘿嘿)。
另外,可以运行的整个项目文件我也上传了,链接附上:
面向对象的软件构造导论实验课---IntelliJIDEA软件实现---Java语言编写---单机版飞机大战-其它文档类资源-CSDN文库
最后祝大家都能过!!!