Java飞机大战
小孙仗剑走天涯 人气:0一、飞机大战
1 封装所有飞行物公共属性和功能的父类
import java.awt.image.BufferedImag /** * 封装所有飞行物的公共属性和功能的父类 */ public abstract class Flyer { protected int x; //飞行物左上角x坐标 protected int y; //飞行物左上角y坐标 protected int height; //飞行物的高度 protected int width; //飞行物的宽度 protected BufferedImage image; //飞行物的图片 /** * 要求所有飞行物必须都能移动 * 但移动的方式由子类自己实现 */ public abstract void step(); /** * 检查越界的方法 * @return 是否越界 */ public abstract boolean outOfBounds(); /** * 专门检测两个矩形飞行物是否碰撞的工具方法 * 和具体对象无关,所以定义为静态方法 * @param f1 飞行对象1 * @param f2 飞行对象2 * @return 是否碰撞 */ public static boolean boom(Flyer f1,Flyer f2){ //step1: 求出两个矩形的中心点 int f1x = f1.x + f1.width/2; int f1y = f1.y + f1.height/2; int f2x = f2.x + f2.width/2; int f2y = f2.y + f2.height/2; //step2: 横向和纵向碰撞检测 boolean H = Math.abs(f1x - f2x) < (f1.width + f2.width)/2; boolean V = Math.abs(f1y -f2y) < (f1.height + f2.height)/2; //step3: 必须两个方向同时碰撞 return H&V; } }
2 封装英雄机属性和功能类
import java.util.Random; /** * 封装英雄机的属性和功能类 */ public class Hero extends Flyer { private int doubleFire; //双倍火力子弹数 private int life; //生命值 private int score; //得分 //对外提供读取生命值的方法 public int getLife(){ return life; } //对外提供的获取得分的方法 public int getScore(){ return score; } /** * 英雄机对象的无参构造方法 */ public Hero(){ image = ShootGame.hero0; height = image.getHeight(); width = image.getWidth(); x = 127; y = 388; doubleFire = 0; life = 3; score = 0; } /** * 实现英雄机的动画效果的方法 * 让英雄机的图片在hero0和hero1之斗切换 */ @Override public void step() { Random r = new Random(); if(r.nextInt(2) == 0){ image = ShootGame.hero0; }else{ image = ShootGame.hero1; } } @Override public boolean outOfBounds() { // TODO Auto-generated method stub return false; } /** * 英雄机随鼠标移动的方法 * 要求传入鼠标当前的位置 * @param x 鼠标位置的x坐标 * @param y 鼠标位置的y坐标 */ public void move(int x,int y){ //传入的x,y是鼠标的坐标 //move的作用是让英雄机的中心位置和鼠标位置一致 this.x = x - width / 2; this.y = y - height / 2; } /** * 英雄机获得分数或奖励的方法 * @param f 是一个飞行物父类方法,可以指向敌机或者大飞机 */ public void getScore_Award(Flyer f){ //先判断敌人对象的类型 if(f instanceof Airplane){ //如果敌人是敌机 //获得敌机对象中的分数,加到当现分数上 score += ((Airplane)f).getScore(); }else{ //如果对象是大飞机 //继续判断大飞机对象中保存的奖励类型 if(((BigPlane)f).getAwardType() == BigPlane.DOUBLE_FIRE){ //如果保存的是双倍火力 doubleFire += 20; }else{ //如果保存的是生命值奖励 life += 1; } } } /** * 英雄机发射子弹的方法 * @return 新创建出来的子弹对名 * 可能是一发,也可能 是两发,用数组保存 */ public Bullet[] shoot(){ Bullet[] bullets = null; //何时开启双倍火力: if(doubleFire != 0){ //创建双倍火力 bullets = new Bullet[2]; Bullet b1 = new Bullet(x + width/4 - ShootGame.bullet.getWidth()/2,y + ShootGame.bullet.getWidth()); Bullet b2 = new Bullet(x + width*3/4 - ShootGame.bullet.getWidth()/2,y + ShootGame.bullet.getWidth()); bullets[0] = b1; bullets[1] = b2; //每创建一个双倍火力,doubleFire-1 doubleFire -= 1; }else{ //单倍火力: //子弹x坐标:x+英雄机宽度/2-子弹宽度/2 //子弹y坐标:y-子弹高度 bullets = new Bullet[1]; bullets[0] = new Bullet(x + width/2 - ShootGame.bullet.getWidth()/2,y - ShootGame.bullet.getHeight()); } return bullets; } /** * 英雄机自带和敌人碰撞检测方法 * @param f 可能发生碰撞的敌人 * 可能是敌机也可能是大飞机 * @return 是否碰撞 */ public boolean hit(Flyer f){ //调用碰撞检测方法,检测是否碰撞 boolean r = Flyer.boom(this, f); if(r){ //如果碰撞 life--; doubleFire = 0; } return r; } }
3 封装敌机属性和功能的类
import java.util.Random; /** * 封装敌机属性和功能的类 */ public class Airplane extends Flyer { private int speed = 2; //敌机每次下落2个单位长度 private int score = 5; //敌机包含的奖励分数 //对外提供的读取敌机奖励分数的方法 public int getScore(){ return score; } /** * 敌机类的无参构造方法 */ public Airplane(){ image = ShootGame.airplane; width = image.getWidth(); height = image.getHeight(); y = -height; Random r = new Random(); x = r.nextInt(ShootGame.WIDTH - width); } @Override public void step() { //敌机每次向下移动一个speed长度 y += speed; } @Override public boolean outOfBounds() { //敌机y坐标>游戏界面,越界 return y > ShootGame.HEIGHT; } }
4 封装大飞机属性和功能的类
import java.util.Random; /** * 封装大飞机属性和功能的类 */ public class BigPlane extends Flyer { /*定义奖励类型的备选项常量*/ public static final int DOUBLE_FIRE = 0; //奖励类型是0,说明奖励双倍火力 public static final int FILE = 1; //奖励类型是1,说明奖励一次生命 /*大飞机类私有成员*/ private int xspeed = 1; //水平移动的速度为1 private int yspeed = 2; //垂直移动的速度为2 private int awardType; //当前大飞机保存的奖励类型 //对外提供的读取大飞机奖励类型的方法 public int getAwardType(){ return awardType; } /** * 大飞机的无参构造方法 */ public BigPlane(){ //step1: 从主程序中获取大飞机图片的静态变量——bigplane image = ShootGame.bigplane; //step2: 使用图片宽高设置对象宽高 width = image.getWidth(); height= image.getHeight(); //step3: 设置大飞机开始下落的高度 y = -height; //step4: 大飞机对象开始下落的x坐标在0~(界面宽度 - 大飞机图片宽度)之前随机 Random r = new Random(); x = r.nextInt(ShootGame.WIDTH - width); //在0和1之间随机先择一种奖励类型 awardType = r.nextInt(2); } @Override public void step() { //每次x移动一个xspeed,y移动一个yspeed x += xspeed; y += yspeed; //大飞机不能起出边界,一旦超出那么xspeed*(-1),相当于反向移动 if(x < 0 || x > ShootGame.WIDTH - width){ xspeed *= -1; } } @Override public boolean outOfBounds() { //大飞机的y坐标>游戏界面,越界 return y > ShootGame.HEIGHT; } }
5 子弹类
public class Bullet extends Flyer{ private int speed = 5; //子弹上升的速度为3 /** * 子弹类的带参构造方法 * 因为子弹对象创造的位置要根据英雄机的位置决定 * 所以子弹对名的x和y要从外界传入 * @param x 英雄机指定子弹创造位置的x坐标 * @param y 英雄机指定子弹创造位置的y坐标 */ public Bullet(int x,int y){ image = ShootGame.bullet; width = image.getWidth(); height = image.getHeight(); this.x = x; this.y = y; } @Override public void step() { //子弹每次向上移动一个speed长度 y -= speed; } @Override public boolean outOfBounds() { //子弹的y坐标+子弹的高度<0,越界 return (y + height) < 0; } }
6 飞机大战射击的主方法
import java.awt.Font; import java.awt.Graphics; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.image.BufferedImage; import java.io.IOException; import java.util.Arrays; import java.util.Random; import java.util.Timer; import java.util.TimerTask; import javax.imageio.ImageIO; import javax.swing.JFrame; import javax.swing.JPanel; public class ShootGame extends JPanel { private static final long serialVersionUID = 1L; //背景图片的大小320*568 public static final int WIDTH = 320; public static final int HEIGHT = 568; //游戏界面固定大小336*607 public static final int FRAME_WIDTH = 336; public static final int FRAME_HEIGHT = 607; /* * 游戏启动第一件事是从硬盘加载所有要用到的图片到内存当中 * 而且仅在启动时加载一次——静态块 * 缓存在程序中的所有图片,都会反复使用,仅保存一份——静态变量 * 下面,为每张图片加载一个静态变量,然后在静态块加加载每张图片 */ public static BufferedImage background; //背景图片 public static BufferedImage start; //开始图片 public static BufferedImage airplane; //敌机图片 public static BufferedImage bigplane; //大飞机 public static BufferedImage hero0; //英雄机状态0 public static BufferedImage hero1; //英雄机状态1 public static BufferedImage bullet; //子弹 public static BufferedImage pause; //暂停图片 public static BufferedImage gameover; //游戏结束 //静态块,在类加载到方法区时执行一次,专门加载静态资源 static{ /* * java从硬盘中加载图片到内存中: * ImageIO.read方法:专门从硬盘中加载图片的静态方法 * 不用实例化,直接调用 * ShootGame.class:获得当前类的加载器所在路径 * ShootGame.class.getRerource("文件名"); 从当前类所在路径加载指定文件到程序中 */ try { background = ImageIO.read(ShootGame.class.getResource("background.png")); airplane = ImageIO.read(ShootGame.class.getResource("airplane.png")); bigplane = ImageIO.read(ShootGame.class.getResource("bigplane.png")); bullet = ImageIO.read(ShootGame.class.getResource("bullet.png")); start = ImageIO.read(ShootGame.class.getResource("start.png")); pause = ImageIO.read(ShootGame.class.getResource("pause.png")); hero0 = ImageIO.read(ShootGame.class.getResource("hero0.png")); hero1 = ImageIO.read(ShootGame.class.getResource("hero1.png")); gameover = ImageIO.read(ShootGame.class.getResource("gameover.png")); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /* * 为游戏中的角色定义数据结构,包括: * 1个英雄机对象 * 1个存储所有敌人(敌机和大飞机)的对象数组 * 1个存储所有子弹的对象数组 */ public Hero hero = new Hero(); public Flyer[] flyers = {}; //存储所有敌人对象的数组 public Bullet[] bullets = {}; //存储所有子弹对象的数组 //定义游戏状态:当前状态变量:默认为开始状态 private int state = START; //定义游戏状态的备选项常量: 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; public static void main(String[] args) { /* * java中绘制窗体:JFrame对象——窗框 * 要想在窗体中绘制内容,还需要嵌入背景面板——JPanel */ JFrame frame = new JFrame("ShootGame"); frame.setSize(FRAME_WIDTH,FRAME_HEIGHT);//(336, 607); frame.setAlwaysOnTop(true); //设置窗体置顶 //设置窗体关闭同时,退出程序 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setLocationRelativeTo(null); //设置窗体的位置,null表示居中 /*在窗体中嵌入背景面板对象——JPanel*/ ShootGame game = new ShootGame(); //创建背景面板对象 frame.add(game); //将背景面板对象嵌入到窗体对象中 /*窗体默认不可见!必须调用setVisible方法才能显示窗体*/ frame.setVisible(true); //自动调用窗体的paint方法 game.action(); } /** * 游戏启动时要做的事 */ public void action(){ /*游戏开始时,要定义鼠标事件的监听*/ //step1: 创建MouseAdapter匿名内部类——事件的响应程序 MouseAdapter l = new MouseAdapter(){ //step2: 重写希望的鼠标事件——鼠标移动 @Override public void mouseMoved(MouseEvent e) { //只有在RUNNING状态下英雄机才跟随鼠标移动 if(state == RUNNING){ //step3: 获得鼠标新位置 int x = e.getX(); int y = e.getY(); //step4: 将鼠标位置传给英雄机的move方法 hero.move(x, y); } } @Override public void mouseClicked(MouseEvent e) { if(state == START || state == PAUSE){ //START或者PAUSE状态,单击才会改改为RUNNING状态 state = RUNNING; }else if(state == RUNNING){ //游戏点击暂停 state = PAUSE; }else if(state == GAME_OVER){ //游戏结束后单击,游戏初始化 state = START; //从GAME_OVER到START,要重新初始化游戏数据 flyers = new Flyer[0]; bullets = new Bullet[0]; hero = new Hero(); } } @Override public void mouseExited(MouseEvent e) { if(state == RUNNING){ //仅在处于RUNNING状态下,鼠标移出才暂停 state = PAUSE; } } @Override public void mouseEntered(MouseEvent e) { if(state == PAUSE){ state = RUNNING; } } }; //匿名内部类要以分号结尾 /*step5: 要响应鼠标事件,必须将鼠标事件添加到程序的监听器中*/ this.addMouseMotionListener(l); //支持鼠标的移动事件,不支持鼠标单击事件 this.addMouseListener(l);; //支持鼠标单击事件 //step1: 创建定时器 Timer timer = new Timer(); //step2: 调用定时器对象的schedule方法,做计划 // 第一个参数:TimerTask类型的匿名内部类 // 必须重写run方法——核心——要做什么事 timer.schedule(new TimerTask(){ //首先定义一个计时器变量index,记录run方法运行的次数 private int runTimes = 0; @Override public void run() { //除了repaint方法,其余功能只在RUNNING状态下执行 if(state == RUNNING){ //每执行一次run方法,runTimes就+1 runTimes++; //每500亳秒生成一次敌人 if(runTimes % 50 == 0){ nextOne(); //自动随机创建敌人对象 } //遍历每一个对象,调用对象的step方法,移动一次对象的位置 for(int i = 0;i < flyers.length;i++){ flyers[i].step(); } //每300亳秒生成一次子弹 if(runTimes % 30 == 0){ shoot(); //创建一次子弹 } //遍历子弹数组的每一个对象,移动位置 for(int i = 0;i < bullets.length;i++){ bullets[i].step(); } //英雄机动画效果 hero.step(); //添加子弹和敌人的碰撞检测 boom(); //英雄机碰撞检测 hit(); //添加越界检测 outOfBounds(); } /*强调:只要界面发生变化,必须调用repaint方法重新绘制界面*/ repaint(); } }, 10,10); //界面每隔10亳秒变化一次 } @Override public void paint(Graphics g) { //step1: 绘制背景图片 g.drawImage(background, 0, 0, null); //step2: 绘制英雄机 paintHero(g); //step3: 批量绘制敌人数组 paintFlyers(g); //step4: 批量绘制子弹数组 paintBullets(g); //绘制分数和生命值 paintScore_Life(g); //根据游戏状态绘制不同图片 if(state == START){ g.drawImage(start, 0, 0, null); }else if(state == PAUSE){ g.drawImage(pause, 0, 0, null); }else if(state == GAME_OVER){ g.drawImage(gameover, 0, 0, null); } } /** * 绘制英雄机对象的方法 * @param g 画笔 */ public void paintHero(Graphics g){ g.drawImage(hero.image, hero.x, hero.y, null); } /** * 遍历敌人数组,批量绘制所有敌人的方法 * @param g */ public void paintFlyers(Graphics g){ for(int i = 0;i < flyers.length;i++){ g.drawImage(flyers[i].image, flyers[i].x, flyers[i].y, null); } } /** * 遍历子弹数组,批量绘制所有子弹的方法 * @param g */ public void paintBullets(Graphics g){ for(int i = 0;i < bullets.length;i++){ g.drawImage(bullets[i].image, bullets[i].x, bullets[i].y, null); } } /** * 随机生成1个敌人对象 * 每生成一个新敌人, flyers数组就要扩容1 * 然后将新敌人放入数组最后一个元素 */ public void nextOne(){ Random r = new Random(); Flyer f = null; if(r.nextInt(20) == 0){ //只有随机数取0时才创建大飞机 f = new BigPlane(); }else{ //其余全部生成敌机 f = new Airplane(); } //对flyers数组扩容1 flyers = Arrays.copyOf(flyers, flyers.length + 1); //将新敌人放入数组末尾 flyers[flyers.length - 1] = f; } /** * 获得英雄机对象发射的子弹对象 * 将新的子弹对象保存到子弹数组中,统一管理 */ public void shoot(){ Bullet[] newBullets = hero.shoot(); //获得英雄机返回的新子弹数组 //根据返回新子弹的数量,扩容子弹数组 bullets = Arrays.copyOf(bullets, bullets.length + newBullets.length); //从newBullets数组中拷贝所有元素到bullets数组末尾 System.arraycopy(newBullets, 0, bullets, bullets.length - newBullets.length, newBullets.length); } /** * 遍历子弹数组和敌人数组,进行碰撞检测 * 一旦发生碰撞,子弹和敌人都减少一个 */ public void boom(){ for(int i = 0;i < bullets.length;i++){ for(int j = 0;j < flyers.length;j++){ if(Flyer.boom(bullets[i], flyers[j])){ //为英雄机获得分数和奖励 hero.getScore_Award(flyers[j]); //从敌人数组中删除被击中的敌机 //step1: 使用敌人数组最后一个元素替换被击中的敌机 flyers[j] = flyers[flyers.length - 1]; //step2: 压缩数组 flyers = Arrays.copyOf(flyers, flyers.length - 1); //从子弹数组中删除击中敌机的子弹 bullets[i] = bullets[bullets.length - 1]; bullets = Arrays.copyOf(bullets, bullets.length -1); i--; //第发现一次碰撞,子弹就要退一个元素,重新检测当前位置 break; //只要发现碰撞就退出当前敌人数组的循环 } } } } /** * 绘制分数和生命值的方法 * @param g */ public void paintScore_Life(Graphics g){ int x = 10; //文字在左上角的x坐标 int y = 15; //文字在左上角的y坐标 Font font = new Font(Font.SANS_SERIF,Font.BOLD,14); g.setFont(font); //设置字体的画笔对象 //绘制第一行:分数 g.drawString("SCORE: " + hero.getScore(), x, y); //绘制第二行:生命值,y坐标下移20个单位 y += 20; g.drawString("LIFE: " + hero.getLife(), x, y); } /** * 检查所有飞行物是否越界 */ public void outOfBounds(){ //检查所有敌人是否越界 Flyer[] Flives = new Flyer[flyers.length]; //遍历敌人数组,将存活的敌人对象存到新数组中 //设置Flives数组的计数器index: 1.标示下一个存活对象的位置 // 2.统计Flives中一共有多少元素 int index = 0; for(int i = 0;i < flyers.length;i++){ if(!flyers[i].outOfBounds()){ //没有越界的对象 Flives[index] = flyers[i]; index++; } //遍历结束后: //index是存活对象的个数 //Flives数组里是存活的对象,个数为index //把Flives数组压缩为index大小 //压缩后的新数组 应替换回flyers数组 } flyers = Arrays.copyOf(Flives, index); //检测所有子弹是否越界 Bullet[] Blives = new Bullet[bullets.length]; index = 0; for(int i = 0;i < bullets.length;i++){ if(!bullets[i].outOfBounds()){ Blives[index] = bullets[i]; index++; } } bullets = Arrays.copyOf(Blives, index); } /** * 遍历敌人数组,判断英雄机和每个敌人是否碰撞 */ public void hit(){ Flyer[] lives = new Flyer[flyers.length]; //记录存活的敌人 int index = 0; for(int i = 0;i < flyers.length;i++){ if(!hero.hit(flyers[i])){ lives[index] = flyers[i]; index++; } } if(hero.getLife() <= 0){ //如果英雄机生命值小于等于0,游戏结束 state = GAME_OVER; } //压缩敌人数组,并替换数组 flyers = Arrays.copyOf(lives, index); } }
二、测试结果
加载全部内容