前言
飞机大战是一个非常经典的案例,因为它包含了多种新手需要掌握的概念,是一个非常契合面向对象思想的入门练习案例
程序分析:
在此游戏中共有六个对象:
小敌机Airplane,大敌机BigAirplane,小蜜蜂Bee,天空Sky,英雄机Hero,子弹Bullet
其次我们还需要三个类:
超类Flyer,图片类Images,测试类World
还需:
英雄机2张,小敌机,大敌机,小蜜蜂,子弹,天空各1张,爆炸图4张,游戏开始,暂停,游戏结束各1张,共14张图片放入与图片类Images同包中
超类Flyer:
此类是用来封装所有对象共有的行为及属性的
不管是写什么程序,都建议遵循两点:数据私有化,行为公开化
import java.util.Random; import java.awt.image.BufferedImage; public abstract class Flyer { //所有对象都有三种状态:活着的,死了的,及删除的 //这里之所以选择用常量表示状态是因为首先状态是一个不需要去修改的值 //其次状态需要反复使用所以结合这两个特点,我选择了使用常量表示 //state是用来表示当前状态的,每个对象都有一个实时的状态,此状态是会改变的,且初始状态都是活着的 public static final int LIVE = 0;//活着的 public static final int DEAD = 1;//死了的 public static final int REMOVE = 2;//删除的 protected int state = LIVE;//当前状态(默认状态为活着的) 每个对象都是一张图片,既然是图片那么就一定有宽高,其次因为每个对象都是会随时移动的 即为都有x,y坐标 protected int width;//宽 protected int height;//高 protected int x;//左右移动(x坐标) protected int y;//上下移动(y坐标) /** * 飞行物移动(抽象) * 每个飞行物都是会移动的,但是移动方式不同 * 所以这里就将共有的行为抽到了超类中 * 但是设置成了抽象方法,实现了多态的效果 */ public abstract void step(); /** * 获取图片(抽象) * 所有对象都是图片但图片不相同所以抽象化了 */ public abstract BufferedImage getImage(); /** * 判断对象是否是活着的 */ public boolean isLive(){ return state == LIVE; } /** * 判断对象是否是死了的 */ public boolean isDead(){ return state == DEAD; } /** * 判断对象是否删除了 */ public boolean isRemove(){ return state == REMOVE; } /** * 判断对象(大敌机,小敌机,小蜜蜂)是否越界 * 当敌人越界我们就需要删除它否则程序越执行越卡,会出现内存泄露的问题,此方法就是为后续删除越界对象做铺垫的 * @return */ public boolean isOutOfBounds(){ return y >= World.HEIGHT; } /** * 给小/大敌机,小蜜蜂提供的 * 因为三种飞行物的宽,高不同所以不能写死。 * 若三种飞行物的宽,高相同,那么就可以将宽,高写死 */ public Flyer(int width,int height){ Random rand = new Random(); this.width = width; this.height = height; x = rand.nextInt(World.WIDTH-width);//x:0到负的width长度的之间的随机数 y = -height;//y:负的height高度 } /** * 给天空,子弹,英雄机提供的 * 因为英雄机,子弹,天空的宽,高,x,y都是不同的,所以数据不能写死,需要传参 */ public Flyer(int width,int height,int x,int y){ this.width = width; this.height = height; this.x = x; this.y = y; } /** *检测碰撞 * this:敌人(小敌机/小蜜蜂/大敌机) * other:子弹/英雄机 *@return */ public boolean isHit(Flyer other){ int x1 = this.x - other.width;//x1:敌人的x-英雄机/子弹的宽 int x2 = this.x + this.width;//x2:敌人的x加上敌人的宽 int y1 = this.y - other.height;//y1:敌人的y-英雄机/子弹的高 int y2 = this.y + this.height;//y2:敌人的y加上敌人的高 int x = other.x;//x:英雄机/子弹的x int y = other.y;//y:英雄机/子弹的y /* x在x1与x2之间 并且 y在y1与y2之间,即为撞上了 */ return x>x1 && x<=x2 && y>=y1 && y<=y2; } /** * 飞行物死亡 */ public void goDead(){ state = DEAD;//将当前状态修改为死了的 } }
图片工具类Images:
此类用来获取每个对象对应的图片
import java.awt.image.BufferedImage; import javax.imageio.ImageIO; import javax.swing.ImageIcon; /** * 图片工具类 */ public class Images { // 公开的 静态的 图片数据类型 变量名 /** * 对象图片 */ public static BufferedImage sky;//天空 public static BufferedImage bullet;//子弹 public static BufferedImage[] heros;//英雄机 public static BufferedImage[] airs;//小敌机 public static BufferedImage[] bairs;//大敌机 public static BufferedImage[] bees;//小蜜蜂 /** * 状态图片 */ public static BufferedImage start;//启动状态图 public static BufferedImage pause;//暂停状态图 public static BufferedImage gameover;//游戏结束状态图 static {//初始化静态图片 sky = readImage("background01.png");//天空 bullet = readImage("bullet.png");//子弹 heros = new BufferedImage[2];//英雄机图片数组 heros[0] = readImage("hero0.png");//英雄机图片1 heros[1] = readImage("hero1.png");//英雄机图片2 airs = new BufferedImage[5];//小敌机图片数组 bairs = new BufferedImage[5];//大敌机图片数组 bees = new BufferedImage[5];//小蜜蜂图片数组 airs[0] = readImage("airplane.png");//小敌机图片读取 bairs[0] = readImage("bigairplane.png");//大敌机图片读取 bees[0] = readImage("bee01.png");//小蜜蜂图片读取 /**爆炸图迭代读取*/ for (int i=1;i<5;i++){//遍历/迭代赋值 airs[i] = readImage("bom"+i+".png");//小敌机图片数组其余元素赋值爆炸图 bairs[i] = readImage("bom"+i+".png");//大敌机图片数组其余元素赋值爆炸图 bees[i] = readImage("bom"+i+".png");//小蜜蜂图片数组其余元素赋值爆炸图 } start = readImage("start.png");//启动状态图 pause = readImage("pause.png");//暂停状态图 gameover = readImage("gameover.png");//游戏结束状态图 } /** * 读取图片 * 此处的fileName:图片文件名 * * try.....catch:异常的一种处理方法 */ public static BufferedImage readImage(String fileName){ try{ BufferedImage img = ImageIO.read(Flyer.class.getResource(fileName)); //读取与Flyer在同一个包中的图片 return img; }catch(Exception e){ e.printStackTrace(); throw new RuntimeException(); } } }
世界窗口类/测试类 World:
此类用来集合所有类进行排序及具体的操作,和程序的最终运行
import javax.swing.JFrame; import javax.swing.JPanel; import java.awt.Graphics; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.nio.Buffer; //定时器 import java.util.Timer; //定时器任务 import java.util.TimerTask; //打开随机类 import java.util.Random; //扩容类 import java.util.Arrays; /** * 世界测试类(整个游戏窗口) */ public class World extends JPanel{ public static final int WIDTH = 400;//窗口宽 public static final int HEIGHT = 700;//窗口高 public static final int START = 0;//启动状态 public static final int RUNNING = 1;//运行状态 public static final int PAUSE = 2;//暂停状态 public static final int GAME_OVER = 3;//游戏结束状态 private int state = START;//当前状态默认是启动状态 /** * 声明每个类具体的对象 * 如下为:窗口中所看到的对象 */ private Sky s = new Sky();//天空对象 private Hero h = new Hero();//英雄机对象 private Flyer[] enemies ={};//敌人对象,分别是大敌机,小敌机,小蜜蜂所以写成了数组 private Bullet[] bt ={};//子弹也是有很多的所以写成了数组 /** * 生成敌人对象(小敌机,大敌机,小蜜蜂) */ public Flyer nextOne(){ Random rand = new Random(); int type = rand.nextInt(20);//0-19之间的随机数 if (type < 5){//当随机数小于5 return new Bee();//返回小蜜蜂 }else if (type < 13){//当随机数小于13 return new Airplane();//返回小敌机 }else{//大于十三则 return new BigAirplane();//返回大敌机 } } private int enterIndex = 0; /** * 敌人(大敌机,小敌机,小蜜蜂)入场 */ public void enterAction() {//每10毫秒走一次 enterIndex++; if (enterIndex%40 == 0 ){//四百毫秒走一次 Flyer fl = nextOne();//获取敌人对象 enemies = Arrays.copyOf(enemies,enemies.length+1);//扩容(每产生一个敌人数组就扩容1) enemies[enemies.length-1] = fl;//将生成的敌人fl放置enemies数组的末尾 } } int shootIndex = 0; /** * 子弹入场 */ public void shootAction(){//10毫秒走一次 shootIndex++; if (shootIndex%30 == 0){//每300毫秒走一次 Bullet[] bs = h.shoot();//获取子弹数组对象 bt = Arrays.copyOf(bt,bt.length+bs.length);//扩容子弹数组(每入场一个子弹就加一个元素) System.arraycopy(bs,0,bt,bt.length-bs.length,bs.length);//数组的追加 } } /** * 让除去英雄机外的所有对象(小敌机,大敌机,小蜜蜂,子弹,天空)移动 */ public void setpAction() {//每10毫秒走一次 s.step();//天空移动 for (int i=0;i
小敌机类Airplane:
此类存储小敌机特有的属性及行为:
移动速度,分值,及图片的切换
继承超类,且实现得分接口
package cn.tedu.shoot; import java.awt.image.BufferedImage; /** * 小敌机 */ public class Airplane extends Flyer implements EnemyScore{ // 移动速度 private int speed; public Airplane(){ super(66,89); speed = 2;//小敌机的下落速度 } /**重写step方法(移动)*/ public void step(){ y += speed;//y+表示向下 } int index = 1; /** * 重写getImage()获取对象图片 * @return */ public BufferedImage getImage() { if (isLive()){//若活着 则返回airs[0]图片 return Images.airs[0]; }else if (isDead()){//若死了 则返回airs[1~4]图片 BufferedImage img = Images.airs[index++];//获取爆破图 if (index == Images.airs.length){//若index到了5 则表示到了最后一张 state = REMOVE;//将当前状态修改为REMOVE删除的 } return img;//返回爆炸图 /* index = 1 10M isLive返回true 则 return返回airs[0]图片 20M isLive返回false 则 执行isDead返回true img = airs[1] index = 2 返回airs[1]图片 30M isLive返回false 则 执行isDead返回true img = airs[2] index = 3 返回airs[2]图片 40M isLive返回false 则 执行isDead返回true img = airs[3] index = 4 返回airs[3]图片 50M isLive返回false 则 执行isDead返回true img = airs[4] index = 5 state修改为REMOVE 返回airs[4]图片 60M isLive返回false 则 执行isDead返回false return返回null空值(不返回图片) */ } return null; } /** * 重写getScore()方法 * @return:分值 */ public int getScore(){ return 1; } }
大敌机类BigAirplane:
大敌机与小敌机几乎无差别
同样要继承超类,且实现得分接口
package cn.tedu.shoot; import java.awt.image.BufferedImage; import java.util.Random; /** * 大敌机 */ public class BigAirplane extends Flyer implements EnemyScore{ // 移动速度 private int speed; public BigAirplane(){//初始化默认属性 super(203,211);//图片宽,高 speed = 2;//移动速度 } /**重写step方法(移动)*/ public void step(){ y += speed;//y+表示直线向下移动 } int index = 1; @Override public BufferedImage getImage() { if (isLive()){//若活着 则返回airs[0]图片 return Images.bairs[0]; }else if (isDead()){//若死了 则返回airs[1~4]图片 BufferedImage img = Images.bairs[index++];//获取爆破图 if (index == Images.bairs.length){//若index到了5 则表示到了最后一张 state = REMOVE;//将当前状态修改为REMOVE删除的 } return img; } return null; } /** * 重写getScore()方法 * @return:分值 */ public int getScore(){ return 3; } }
小蜜蜂类Bee:
此类虽也可以算作敌人类,但是与小/大敌机有所不同,它是实现奖励值接口
package cn.tedu.shoot; import java.awt.image.BufferedImage; import java.util.Random; /** * 小蜜蜂 */ public class Bee extends Flyer implements EnemyAward{ // x坐标移动速度,y坐标移动速度, private int xSpeed;//x坐标移动速度 private int ySpeed;//y坐标移动速度 private int awardType;//奖励类型 public Bee(){//初始化属性 super(48,50);//图片宽,高 Random rand = new Random(); awardType = rand.nextInt(2);//随机奖励值类型0~2之间(不包括2)0表示火力值,1表示生命值 xSpeed = 1;//平行移动 ySpeed = 2;//垂直移动 } /**重写step方法(移动)*/ public void step() { y += ySpeed;//y+:向下移动 x += xSpeed;//x+:随机向左或是向右移动 if (x <= 0 || x >= World.WIDTH - width) { xSpeed *= -1;//到达边界后反方向移动(正负为负,负负为正) } } int index = 1; public BufferedImage getImage() { if (isLive()){//若活着 则返回airs[0]图片 return Images.bees[0];//返回小蜜蜂图 }else if (isDead()){//若死了 则返回airs[1~4]图片 BufferedImage img = Images.bees[index++];//获取爆破图 if (index == Images.bees.length){//若index到了5 则表示到了最后一张 state = REMOVE;//将当前状态修改为REMOVE删除的 } return img;//返回爆炸图 } return null; } /** * 重写getAwardType()方法 * @return */ public int getAwardType(){ return awardType;//返回奖励类型 } }
天空类Sky:
这里有一点需要强调,就是为了实现天空图片向下移动后会出现移动过的位置出现图片丢失的情况,就使用了两张图上下拼接起来,当第某张天空图完全移出窗口的时候会让它重新出现在窗口上方继续向下移动
package cn.tedu.shoot; import java.awt.image.BufferedImage; /** * 天空 */ public class Sky extends Flyer{ // 移动速度,y1 private int y1;//第二张图片的y坐标 private int speed;//移动速度 public Sky(){//设置初始值(默认值) //此处的宽高用常量是因为天空的宽高和窗口是一致的,x轴和y轴为若不为0就和窗口不匹配了 super(World.WIDTH,World.HEIGHT,0,0);//初始化图片坐标及宽,高 speed = 1;//初始化移动速度 y1 = -World.HEIGHT;//第二张图片设置在第一张图片上方 } /**重写step方法(移动)*/ public void step(){ y += speed;//第一张图向下移动 y1 += speed;//第二张图向下移动 if (y >= World.HEIGHT){//若y>=窗口的高 y = -World.HEIGHT;//将移动出去的第一张天空挪到窗口上方 } if (y1 >= World.HEIGHT){//若第二张天空挪出窗口 y1 = -World.HEIGHT;//将第二张天空挪到窗口上方 } } /**重写getImage()获取对象图片*/ @Override public BufferedImage getImage() {//10毫秒走一次 return Images.sky;//返回天空图片即可 } /** * 获取y1坐标 */ public int getY1(){ return y1;//返回y1 } }
英雄机类Hero:
package cn.tedu.shoot; import java.awt.image.BufferedImage; /** * 英雄机 */ public class Hero extends Flyer { // 命数,火力值 private int life;//命数 private int fire;//火力 /** * 初始化英雄机坐标机具体数据 */ public Hero() { super(97,139,140,400);//宽,高,及初始坐标 fire = 0;//初始火力值 0:单倍火力 life = 3;//初始生命值 } /**重写step方法(移动)*/ public void step(){//每10毫秒走一次 //因为英雄机是跟随鼠标移动的,而鼠标是在窗口上的所以这里就没有写具体的方法,而是在窗口类中去用鼠标的具体坐标计算出英雄机的移动位置 } int index = 0;//下标 /**重写getImage()获取对象图片*/ @Override public BufferedImage getImage() {//每10毫秒走一次 return Images.heros[index++ % Images.heros.length];//heros[0]和heros[1]来回切换 /** *过程 *index = 0 *10M 返回heros[0] index = 1 *20M 返回heros[1] index = 2 *30M 返回heros[0] index = 3 *40M 返回heros[1] index = 4 *50M 返回heros[0] index = 5 *60M 返回heros[1] index = 6 *........... */ } /** * 英雄机发射子弹(生成子弹对象) */ public Bullet[] shoot(){ int xStep = this.width/4;//子弹x坐标 int yStep = 5;//子弹y坐标 System.out.println(this.x+"\t"+this.y); if (fire>0){//双倍火力 Bullet[] bs = new Bullet[3];//2发子弹 bs[0] = new Bullet(this.x+1*xStep,this.y-yStep);//子弹坐标1 bs[1] = new Bullet(this.x+3*xStep,this.y-yStep);//子弹坐标2 bs[2] = new Bullet(this.x+2*xStep,this.y-yStep); fire -= 2;//发射一次双倍活力,则火力值-2 return bs; } else {//单倍火力 Bullet[] bs = new Bullet[1];//1发子弹 bs[0] = new Bullet(this.x+2*xStep,this.y-yStep);//x:英雄机的x+2/4英雄机的宽,y:英雄机的y- return bs; } } /** * 英雄机移动 */ public void moveTo(int x,int y){//形参列表:鼠标的x坐标,y坐标 this.x = x - this.width/2;//英雄机的x = 鼠标的x减1/2英雄机的宽 this.y = y - this.height/2;//英雄机的y = 鼠标的y减1/2英雄机的高 } /** * 英雄机增生命值 */ public void addLife(){ life++;//生命值+1 } /** * 获取英雄机生命值 * @return */ public int getLife(){ return life;//返回生命值 } /** * 英雄机减少生命值 */ public void subtractLife(){ life--;//生命值减1 } /** * 英雄机增火力值 */ public void addFier(){ fire += 40;//火力值+40 } /** * 清空火力值 */ public void clearFier(){ fire = 0;//火力值归零 } }
子弹类Bullet:
package cn.tedu.shoot; import java.awt.image.BufferedImage; /** * 子弹 */ public class Bullet extends Flyer { // 移动速度 private int speed; public Bullet(int x,int y) {//子弹有多个,每个子弹的初始坐标都不同,所以要写活 super(8,20,x,y); speed = 3;//初始移动速度 } /**重写step方法(移动)*/ public void step(){ y -= speed;//y-:表示直线向上移动 } /** * 重写getImage()获取对象图片 * @return */ @Override public BufferedImage getImage() {//10毫秒走一次 if (isLive()){//若活着则返回bullet图片 return Images.bullet; }else if (isDead()){//若死了则将state修改为REMOVE state = REMOVE; } return null;//死了的和删除的都返回null空值 /** * 若活着 则返回bullet图片 * 若死了 则修改REMOVE 再返回空值 * 若删除 则返回空值 */ } /** * 判断子弹是否越界 * @return */ public boolean isOutOfBounds(){ return y <= -height;若子弹的y轴坐标小于自己的高则说明移动到了窗口外部 } }
奖励值接口 EnemyAward:
package cn.tedu.shoot; /** * 奖励值接口 */ public interface EnemyAward { public int FIRE = 0;//火力 public int LIFE = 1;//生命值 /** * 获取奖励值类型 * @return */ int getAwardType(); }
得分接口 EnemyScore:
package cn.tedu.shoot; /*得分接口*/ public interface EnemyScore { /*得分*/ public int getScore(); }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。