植物父类、子弹父类就同理可得了。

上面说到子类共有的方法需要抽到父类中,那么部分子类共有的方法该如何处理呢?比如,豌豆射手、寒冰射手可以发射子弹,坚果墙就没有射击的这个行为。所以这里就需要用到接口(Interface)。

publicinterfaceShoot{// 射击接口 - 将部分子类共有的行为抽取到接口中// 接口中的方法默认是public abstract的,规范的编码应该将该字段舍去publicabstractBullet[] shoot( );}

到此为止,游戏对象的属性、方法基本都定义完了,至于图片的显示以及如何将图片画出来,只需要使用相应的API即可,这里就不做描述了。工作一年回过来看看,这里能优化的地方还有很多,比如对象的血量、攻击力、移动等都可以统统写入到配置文件中,这样在做游戏参数的调整时,不需要去修改代码相关的内容,只需要修改配置文件里面的参数即可。

游戏内容

现在我们有了游戏的对象,该开始让对象加入到游戏中来,接着让他们动起来,最后还得让他们打起来。首先,让对象加入到游戏中来我是这么做的,这里还是以僵尸为例:

// 首先要有一个僵尸的集合// 僵尸集合privateList zombies = newArrayList;// 接着定义随机生成僵尸方法publicZombie nextOneZombie( ){Random rand = newRandom;// 控制不同种类僵尸出现的概率inttype = rand.nextInt( 20);if(type< 5) {returnnewZombie0;} elseif(type< 10) {returnnewZombie1;} elseif(type< 15) {returnnewZombie2;} else{returnnewZombie3;}}// 僵尸入场// 设置进场间隔/** 这里补充一下为什么要设置进场的间隔* 因为游戏的运行是基于定时器的,* 每隔一段时间定时器就会执行一次你所加入定时器的方法,* 所以这里需要设置进场间隔来控制游戏的速度。*/intzombieEnterTime = 0;publicvoidzombieEnterAction( ){zombieEnterTime++;// 对自增量zombieEnterTime进行取余计算if(zombieEnterTime% 300== 0) {// 满足条件就调用随机生成僵尸方法,并将生成的僵尸加入到僵尸的集合中zombies. add(nextOneZombie);}}

最早时候我用的数据结构是数组,但在后续的编码中发现,对僵尸对象有很多的遍历以及增删操作,数组的增删操作是十分麻烦复杂的,所以我就换成了集合。在工作中也一样,先思考在编码,选择正确的数据结构往往能起到事半功倍的效果。

植物入场的设计,是我当时自认为很精妙的一个点。先说一下当时在编码中发现的问题。首先植物入场时是在滚轮机上的,滚轮机上的移动就会涉及到追击和停止的问题。追击的方式当然是追前一个植物卡牌,但当第一个植物卡牌被选中放置到草地上后,那该如何追击呢?

最开始我的做法是给植物多加几个状态来解决这个问题,但是发现状态过多会导致if判断中的条件将大大增加,并且在尝试后还是没有实现想要的效果,于是我就将植物集合一分为二,在后面的游戏功能设计中,回头过来看才发现将植物集合分为滚轮机上的集合和战场上的集合实在是太精妙了。请听我娓娓道来:

// 滚轮机上的植物,状态为stop和waitprivateList plants = newArrayList;// 战场上的植物,状态为life和move -move为被鼠标选中移动的状态,这里设计不合理,会引发后面的一个BUGprivateList plantsLife = newArrayList;// 植物在滚轮机上的碰撞判定publicvoidplantBangAction( ){// 遍历滚轮机上植物集合,从第二个开始for( inti= 1;i 0&&plants. get( 0).isStop) {plants. get( 0).goWait;}// 如果第i个植物y小于i-1个植物的y+height,则说明碰到了,改变i的状态为stopif((plants. get(i).isStop||plants. get(i).isWait)&&(plants. get(i -1).isStop||plants. get(i -1).isWait)&&plants. get(i).getY<=plants. get(i -1).getY+plants. get(i -1).getHeight) {plants. get(i).goStop;}/** 如果第i个植物y大于于i-1个植物的y+height,则说明还没碰到或者第i-1个* 植物被移走了,改变i的状态为wait,可以继续往上走*/if(plants. get(i).isStop&&plants. get(i).getY>plants. get(i -1).getY+plants. get(i -1).getHeight) {plants. get(i).goWait;}}}// 检测滚轮机上的植物状态publicvoidcheckPlantAction1( ){// 迭代器Iterator it = plants.iterator;while(it.hasNext) {Plant p = it.next;/** 如果滚轮机集合里有move或者life状态的植物* 则添加到战场植物的集合中,并从原数组中删除*//** 现在发现把滚轮机上move状态的植物添加到* 战场上植物集合的最佳操作时间点应该是* 等植物状态变为life后再添加。* /if(p.isMove||p.isLife) {plantsLife.add(p);it.remove;}}}

当然,滚轮机上的对植物状态判断的代码还是显得生涩,也正是自己想优化这段代码时萌生了分享游戏设计过程和游戏代码的念头。那么下面就说说,这段代码该如何优化:

// 先对状态做下说明// wait - 植物卡牌在滚轮机上移动状态,因为是等着被鼠标选中,所以取名为wait// stop - 植物卡牌在滚轮机上停止状态,有两种情况,1 - 到顶了 2 - 撞到上一个卡牌了// 开始对以下代码进行优化// 如果第i个植物y小于i-1个植物的y+height,则说明碰到了,改变i的状态为stop// if((plants.get(i).isStop||plants.get(i).isWait)&&// (plants.get(i-1).isStop||plants.get(i-1).isWait)&&// plants.get(i).getY<=plants.get(i-1).getY+plants.get(i-1).getHeight// ) {// plants.get(i).goStop;// }// 优化后的代码是这样的// 将一个复杂的boolean拆成多个if条件if(!(plants. get(i).isStop||plants. get(i).isWait) {break;}if(!(plants. get(i- 1).isStop||plants. get(i- 1).isWait)) {break;}if(!(plants. get(i).getY<=plants. get(i- 1).getY+plants. get(i- 1).getHeight)) {break;}plants. get(i).goStop;

boolean条件当然也可以进行优化,甚至还可以简化一下植物的状态。这里因为游戏的规则,僵尸只能攻击在草坪上的植物,所以把带放置的植物和草坪上的植物分为两个集合,是十分合理精妙的。在判断僵尸是否攻击植物,只需要去遍历草坪上的植物集合即可。如果不拆分,当要判断僵尸是否攻击植物的时候,需要遍历的集合将是所有的植物集合,并且需要增加至少2个状态来区分植物是在草坪上还是在滚轮机上,这段代码想想就是又臭又长。

接下来该让对象们都动起来了。之前说到在父类中的移动方法是抽象方法,在各自的子类中都进行重写后,不同的对象移动方式就是各式各样的了。

// 子弹移动publicvoidBulletStepAction( ){for(Bullet b:bullets) {b.step;}}//僵尸移动//设置移动间隔intzombieStepTime = 0;publicvoidzombieStepAction( ){if(zombieStepTime++% 3== 0) {for(Zombie z:zombies) {//只有活着的僵尸会移动if(z.isLife) {z.step;}}}}

看着代码中对集合复杂的遍历,不得不感概lambda表达式真是个好东西:

// 子弹移动publicvoidBulletStepAction( ){bullets.forEach((b)->b.step);....}

这里好像还是没法展示lambda表达式强大的功能,请看下面的例子:

// 为了应对产品不断变更的需求,前辈们总结经验得出的设计模式已经能在一定程度上应对此问题// 设计模式,声明策略接口,在实现类中完成过滤逻辑publicList filterStudentByStrategy(List students, SimpleStrategy strategy){List filterStudents = newArrayList<>;for(Student student : filterStudents) {if(strategy.operate(student)){filterStudents.add(student);}}returnfilterStudents;}// 当需求变更时,只需要在策略接口的实现类中,变更判断逻辑即可publicinterfaceSimpleStrategy< T>{publicbooleanoperate(T t);}

但好像还是有点麻烦,又要写接口,又要写实现类,后续的维护也是个头疼问题,这个时候救世主lambda表达式就出现了:

// 无需接口便可实现需求的快速变更List lambdaStudents =students.stream.filter(student -> student.getGender== 1).collect(Collectors.toList);

让我们看看上面到底发生了啥。首先将数据的集合流化,接着调用过滤方法,强大lambda表达式让代码变得简洁,并且判断条件的修改可在代码中直接维护无需在策略接口的实现类维护。最后在转成集合,返回一个满足产品需求的集合。

回到正题,如何让对象们打起来呢?下面以僵尸攻击植物为例:

// 僵尸的超类中定义了僵尸的攻击方法,// 由于僵尸们的攻击行为是相同,所以这里是普通方法// 僵尸攻击植物publicbooleanzombieHit(Plant p){intx1 = this.x-p.getWidth;intx2 = this.x+ this.width;inty1 = this.y-p.getHeight;inty2 = this.y+ this.width;intx = p.getX;inty = p.getY;returnx>=x1 && x<=x2 && y>=y1 && y<=y2;}

结合图片来看,上述代码应该就更好理解。黑框P代表植物,黑框Z代表植物,虚线是指两者接触的极限距离,当僵尸进入虚线内,就保证可以攻击到植物。

// 僵尸攻击// 设置攻击间隔intzombieHitTime = 0;publicvoidzombieHitAction{if(zombieHitTime++% 100== 0) {for(Zombie z:zombies) {// 如果战场上没有植物,则把所有僵尸的状态改为life/** 这里补充一下为什么要先将所有的僵尸的状态先改成life状态,也就是移动状态* 因为下面对僵尸是否攻击的植物的判断,是从遍历战场上的植物集合开始的* 假如有只僵尸在吃植物,把战场上唯一的一个植物吃掉了,* 那么僵尸的状态将从攻击改成移动呢?* 所以这里运用了逆向的思想,先将所有的僵尸改为移动状态* 如果符合攻击的条件,那么再改为攻击状态,* 即便是战场上没有植物,那么僵尸还依然是移动的状态*/if(!z.isDead) {z.goLife;}// 这里应该有个对战场上植物集合的判断在进行遍历for(Plant p:plantsLife) {// 如果僵尸是活的,并且植物是活的,并且僵尸进入攻击植物的范围/** 这里有个BUG,僵尸竟然会攻击鼠标选中还未放下的植物,* 所以下面的判断条件中应该还需要移除被鼠标选中状态下植物*/if(z.isLife&&!p.isDead&&z.zombieHit(p)&&!(p instanceofSpikerock)) {// 僵尸状态改为攻击状态z.goAttack;// 植物掉血p.loseLive;}}}}}

如果出现了一些效果的偏移,造成的原因是图片大小不一造成的坐标偏移,因为图片都是网上找的,所以效果不是太理想。

至此,游戏的基本功能基本实现了。Java是一门面向对象的语言,万物皆对象,特征皆属性,行为皆方法。肉眼能看到的僵尸、植物、草坪都是对象,对象的特性比如血量、移动速度都是属性,对象的行为比如移动、攻击、死亡都是方法。

下面说说对游戏功能的优化。

游戏优化

1.放置植物的优化

已经放置过植物的草地不能再放置植物了。之前是将草地设计成empty和hold两种状态,现在来看其实只需要返回一个true和false就行了,将整个植物集合定义成一个虚拟的boolean集合即可。

2.移除植物的优化

设计思路是新增一个铲子对象:

// 铲子集合privateList shovels = newArrayList;// 铲子入场publicvoidshovelEnterAction( ){// 铲子只有一把if(shovels.size== 0) {shovels. add( newShovel);}}// 使用铲子Iterator it = shovels.iterator;Iterator it2 = plantsLife.iterator;while(it.hasNext) {Shovel s = it.next;// 如果铲子是移动状态,就遍历植物集合if(s.isMove) {while(it2.hasNext) {Plant p = it2.next;intx1 = p.getX;intx2 = p.getX+p.getWidth;inty1 = p.getY;inty2 = p.getY+p.getHeight;if((p.isLife||((Blover) p).isClick)&&Mx>x1&&Mxy1&&My

看着这极其复杂好像很厉害的代码,我又萌生了痛下狠手的想法,但为了保持原生,我忍住。于是乎还发现了一个BUG。如果选中铲子后,战场上唯一的植物被僵尸吃掉了,那么这个铲子将一直跟随着鼠标无法达到使用后消除的效果了。解决方案当然也很简单,当战场上植物集合的size为0时,清空铲子集合即可。

3.游戏可玩性的优化

上文在游戏设计中提到的击杀僵尸后可能随机获得奖励类型是这样实现的。还是从设计分析开始,并非击杀任何类型的僵尸都可以获得奖励,所以奖励应该放在接口中:

publicinterfaceAward{// 奖励接口/** 这里还是存在代码不规范的问题* 接口的方式默认是public abstract* 接口中的变量默认是public static final* 这些默认的字段应该舍去*/// 全屏静止publicstaticfinalintCLEAR = 0;// 全屏清除publicstaticfinalintSTOP = 1;publicabstractintgetAwardType;}

当僵尸死亡时,需要去判断该僵尸是否有奖励接口,如果有则执行相应奖励的方法:

// 检测僵尸状态publicvoidcheckZombieAction( ){// 迭代器Iterator it = zombies.iterator;while(it.hasNext) {Zombie z = it.next;// 僵尸血量小于0则死亡,死亡的僵尸从集合中删除if(z.getLive<= 0) {// 判断僵尸是否有奖励的接口if(z instanceof Award) {Award a = (Award)z;inttype = a.getAwardType;switch(type) {caseAward.CLEAR:for(Zombie zo:zombies) {zo.goDead;}break;caseAward.STOP:for(Zombie zom:zombies) {zom.goStop;timeStop = 1;//zombieGoLife;}break;}}z.goDead;it. remove;}// 僵尸跑进房子,而游戏生命减一,并删除僵尸if(z.OutOfBound) {gameLife--;it. remove;}}}

4.添加游戏背景音乐

bgm是一个游戏的灵魂之一。这里给游戏添加背景音乐,我的选择是新建一条线程专门用来执行音乐的解析和播放:

// 启动线程加载音乐Runnable r = newzombieAubio( "bgm.wav");Thread t = newThread(r);t.start;publicclasszombieAubioimplementsRunnable{// 读音频WAV格式专用线程privateString filename;publiczombieAubio(String wavfile){filename=wavfile;}......

这里需要注意的是,Java中解析音乐的API只支持WAV格式的文件,文件格式的转换大多数音乐播放器都可以做到。

后续优化

1.植物种类的扩充及对应功能的实现

比如杀伤力最大的玉米加农炮。需要4个小玉米进行合成,那么在判断是否能够合成玉米加农炮时,需要对植物集合进行遍历来做坐标的判断,所以这边建议最好把可合成的植物单独放在一个集合中,这样在做合成判断的时候会简单很多,当集合的size小于4时,就可以提示合成失败了。冰冻西瓜的设计思路也是如此。

2.动作类僵尸的加入,如撑杆跳僵尸、跳舞僵尸等

说一下撑杆跳僵尸的设计思路,此类僵尸和其他僵尸相比,多了一种跳的行为,所以会有一个单独的方法和单独的状态。并且,跳只能触发一次,所以撑杆跳僵尸的状态变化应该是行走->遇到植物跳过去->再遇到植物就开始攻击,在执行状态变化的时候,应该要去考虑当前的状态是否还可跳跃。

3.当植物攻击范围内不存在僵尸时,植物停止攻击

这个就简单拉,在植物执行攻击方法时,校验一下是否有Y坐标相同的僵尸即可。返回搜狐,查看更多

java植物大战僵尸游戏下载安装_用 Java 写一个植物大战僵尸简易版相关推荐

  1. java开源游戏下载安装_开源java扫雷游戏,Swing版。

    开源java扫雷游戏,Swing版. xC6JnAIE.zip (60.07 KB) 开源java扫雷游戏,Swing版. 程序文件说明 文件 内容 备注 AboutFrame.java 程序&quo ...

  2. java动物移动游戏下载安装_疯狂动物园正版下载安装-疯狂动物园游戏下载v1.29.0 安卓官方最新版-2265手游网...

    跑酷游戏.全新游戏背景.各种萌萌动物形象更加吸引玩家眼球.带你感受最刺激的游戏体验.丰富多彩的道具可供使用.抓取更多动物进行动物园喂养.喜欢就来下载体验吧! 疯狂动物园游戏安卓版介绍: 1.在游戏中你 ...

  3. ios系统玩java游戏下载安装_苹果系统也能玩windowsPC游戏?方法看这里!

    苹果笔记本轻薄漂亮的外观,续航强以及安全可靠,系统流畅等优点拥有众多忠实的用户.用户规模也是windows系统的四分之一左右,可以说非常可观了.不过mac os也有不足的地方,那就是软件和游戏的内容上 ...

  4. 基于java的五子棋游戏的设计_基于Java的五子棋游戏的设计

    摘  要 五子棋作为一个棋类竞技运动,在民间十分流行,为了熟悉五子棋规则及技巧,以及研究简单的人工智能,决定用JAVA开发五子棋游戏.主要完成了人机对战和玩家之间联网对战2个功能.网络连接部分为Soc ...

  5. 关于python小游戏的毕业论文_使用Python写一个小游戏

    引言 最近python语言大火,除了在科学计算领域python有用武之地之外,在游戏.后台等方面,python也大放异彩,本篇博文将按照正规的项目开发流程,手把手教大家写个python小游戏,来感受下 ...

  6. 企鹅java游戏下载安装_企鹅企鹅生活安卓版游戏下载|企鹅企鹅生活手游下载v1.6.1-乐游网安卓下载...

    <企鹅企鹅生活手游>这是一款玩法非常轻松有趣的点击类养成手游,游戏采用卡通风格设计非常的清新美观,游戏中,玩家将能体验到养育企鹅的各种乐趣,从孵化企鹅蛋到看着企鹅一点一点长大,非常的有意思 ...

  7. java jdk 1.8 下载地址_【Java jdk1.8下载】Java jdk1.8下载安装 32/64 官方版-开心电玩...

    Java jdk1.8介绍 Java jdk1.8是一款专门为开发人员打造的开发环境,用于构建应用程序.applet程序,和使用java编程语言的组成部分.本站为您带来Java jdk1.8 64位官 ...

  8. 我的世界1.7.10java下载安装_我的世界1.7.10正式版

    游戏简介 <我的世界1.7.10正式版>是由官方带来的一个全新版本,加入了大量新内容,包含了道具.武器装备.建筑等等,此外本作为官方纯净版本,玩家可以自行添加各种mod,欢迎下载体验. 更 ...

  9. java程序设计雷电游戏设计步骤_基于Java的飞机雷电射击游戏的设计任务书

    主要参考文献(资料): [1] Java 2游戏编程[M]. 清华大学出版社 , (美)ThomasPetchel著, 2005 [2] Java游戏引擎的开发与实现[J]. 何依林. 无线互联科技. ...

最新文章

  1. 新的理念、 新的解决方案、 新的Azure Stack技术预览
  2. Vue菜鸟之路: 封装上传组件
  3. JZOJ 3943. 【GDOI2015模拟11.29】环游世界
  4. CodeChef March Lunchtime 2018 div2
  5. 多GPU运行Deep Learning 和 并行Deep Learning(待续)
  6. 带有NetBeans 10的Java EE
  7. iOS Runtime特性之关联对象
  8. Kafka从上手到实践 - Kafka集群:启动Kafka集群 | 凌云时刻
  9. 今日头条、抖音推荐算法原理全文详解!
  10. LiveZilla 3.2.0.2的汉化包
  11. 蚂蚁金服CTO程立:创新发展数字时代金融关键技术
  12. 【视频开发】【电子电路技术】监控球机PTZ的功能介绍
  13. 微信小程序手机号-springboot
  14. win10与xp/win7 局域网不通的修改方法
  15. An error occurred while filtering resources
  16. Nginx学习(四)
  17. Truffle 初始化项目 truffle init
  18. HaaS UI小程序解决方案基础教学之二: 搭建第一个UI页面
  19. MEM/MBA数学强化(07)几何
  20. 《配电网自动化技术》第一章

热门文章

  1. 【Dubbo3高级特性】「提升系统安全性」通过令牌进行服务验证及服务鉴权控制实战指南
  2. cad线段总和lisp_CAD中数字求和
  3. DELL服务器,CPU一直会提示温度超过阈值。针对CPU temperature is greater than the upper crit
  4. [C++]完美立方(枚举)
  5. 浅析人脸识别中的活体检测算法的几种类型
  6. C#导出CSV后首位数字0没了,大神们进来吧
  7. 介个森田玻尿酸复合面膜也太好用了叭
  8. 机器学习模型的生命周期
  9. Wireshark,Scapy等出现failed to set hardware filter to promiscuous mode解决办法
  10. shell脚本实现对网卡流量监控