利用面想对象知识,实现一个简单的“飞机大战”小游戏。代码粗糙,有兴趣可以自己优化下!
文章源自亦枫博客-https://yflad.cn/2024.html
文章源自亦枫博客-https://yflad.cn/2024.html
案例具体要求:
要求实现飞机大战功能文章源自亦枫博客-https://yflad.cn/2024.html
- 有敌机,加分
- 有奖励机,加生命值,或者火力值,飞行有一定规则
- 有英雄机,碰到敌机会减少生命值,且火力值回复初始,直至游戏结束
- 有子弹,与敌机、奖励机碰撞,敌机、奖励机消失
- 有计分,生命值(声明变量,画出分数,生命值。子弹和飞行物有碰撞时,判断类型)
- 敌机,奖励机不可以越界(逆向思维,将在窗口中显示的对象添加到相关的数组中。定义一个abstract,判断y的值,有没有超过飞行物的值。遍历所有的飞行物,把没有越界的放到下面,判断是否越界)
- 有开始界面,鼠标控制英雄机,鼠标移出暂停游戏,鼠标进入继续游戏,生命归零,结束游戏(定义一个初始状态,添加鼠标监听器,调用英雄机类,重写相关方法)
文章源自亦枫博客-https://yflad.cn/2024.html
文章源自亦枫博客-https://yflad.cn/2024.html
实现代码:
文章源自亦枫博客-https://yflad.cn/2024.html
飞行物的父类
package cn.yflad.game; import java.awt.image.BufferedImage; /** * @Author: yflad (yflad@qq.com) * @Blog: https://yflad.cn * @Date: 2018-12-09 * @role: 飞行物的父类 */ public abstract class FlyingObject { public BufferedImage image;// 有图片 public int x;// x坐标 public int y;// y坐标 public int height;// 图片的高度 public int width;// 图片的宽度 public abstract void step(); // 飞机移动 public boolean checkBang(Bullet b) {// 检查子弹和飞行物碰撞的方法 int x1 = this.x - b.width; int y1 = this.y - b.height; int x2 = this.x + width; int y2 = this.y + height; if (b.x >= x1 && b.x <= x2 && b.y >= y1 && b.y <= y2) { return true; } return false; } // 检查飞行物越界 public abstract boolean outOfBounds(); }
文章源自亦枫博客-https://yflad.cn/2024.html
普通的敌机类:飞行物的子类
文章源自亦枫博客-https://yflad.cn/2024.html
package cn.yflad.game; /** * @Author: yflad (yflad@qq.com) * @Blog: https://yflad.cn * @Date: 2018-12-10 * @role: 普通的敌机类:飞行物的子类 */ public class Airplane extends FlyingObject implements Enemy { int ySpeed = 3; // y轴上的速度 // 通过构造函数对敌机进行初始化操作 public Airplane() { image = TestGame.airplane;// 敌机的图片 width = image.getWidth();// 获取图片宽度 height = image.getHeight();// 获取图片的高度 x = (int) (Math.random() * (TestGame.WIDTH - width));// x轴 原点到(窗口宽-敌机宽)之间的随机数 y = -height; } // 敌机移动的方式 @Override public void step() { y += ySpeed;// y轴+(向下) } // 子弹碰撞敌机,每碰撞一次加10分 @Override public int getScore() { return 10; } // 敌机越界 @Override public boolean outOfBounds() { return y > TestGame.HEIGHT;// 判断y的值,有没有超过本类的值 } }
文章源自亦枫博客-https://yflad.cn/2024.html
小蜜蜂类:飞行物的子类
文章源自亦枫博客-https://yflad.cn/2024.html
package cn.yflad.game; /** * @Author: yflad (yflad@qq.com) * @Blog: https://yflad.cn * @Date: 2018-12-09 * @role: 小蜜蜂类:飞行物的子类 */ public class Bee extends FlyingObject implements Award { int xspeed = 3; // x坐标移动速度 int yspeed = 2; // y坐标移动速度 // 初始化数据 public Bee() { image = TestGame.bee;// 小蜜蜂的图片 width = image.getWidth(); height = image.getHeight(); x = (int) (Math.random() * (TestGame.WIDTH - width));// x轴出现的位置在窗口高度范围内随机出现 y = -height; } @Override public void step() { // 蜜蜂走z字型 x += xspeed;// x+(向左或向右) y += yspeed;// y+(向下) if (x <= 0 || x >= TestGame.WIDTH - width) { xspeed = -xspeed; } } //奖励类型可能为加命或者加双倍火力 @Override public int getType() { int type = (int) (Math.random() * 2); return type; } // 小蜜蜂越界 @Override public boolean outOfBounds() { return y > TestGame.HEIGHT; } }
文章源自亦枫博客-https://yflad.cn/2024.html
英雄机类:飞行物的子类
文章源自亦枫博客-https://yflad.cn/2024.html
package cn.yflad.game; import java.awt.image.BufferedImage; /** * @Author: yflad (yflad@qq.com) * @Blog: https://yflad.cn * @Date: 2018-12-09 * @role: 英雄机类:飞行物的子类 */ public class Hero extends FlyingObject { // 有什么 BufferedImage[] images; int doubleFire;// 默认为单倍火力 int life;// 默认生命值 public Hero() {// 通过构造函数对敌机进行初始化操作 image = TestGame.hero0; images = new BufferedImage[] { TestGame.hero0, TestGame.hero1 }; width = image.getWidth(); height = image.getHeight(); x = TestGame.WIDTH / 2 - width / 2; y = TestGame.HEIGHT / 2 + height / 2; doubleFire = 0;// 默认单倍火力 life = 3; } int index = 0; int indexStep = 0; // 敌机移动的方式 @Override public void step() { if (index++ % 5 == 0) {// 相当于150毫秒切换一次图片 image = images[indexStep++ % images.length]; } } // 英雄机移动的方法。x:鼠标的x坐标 y:鼠标的y坐标 public void moveTo(int x, int y) { this.x = x - width / 2; this.y = y - height / 2; } // 英雄机发射子弹的方法 public Bullet[] shoot() { int x = width / 4;// 局部变量 Bullet[] b; if (doubleFire > 0) { // 双倍火力 b = new Bullet[2]; b[0] = new Bullet(this.x + x, this.y - 10); b[1] = new Bullet(this.x + 3 * x, this.y - 10); //双倍火力不是源源不断,给定一定量双倍,发射完时 为单倍火力 doubleFire -= 2;// doubleFire=doubleFire-2; } else { // 单倍火力 b = new Bullet[1]; b[0] = new Bullet(this.x + 2 * x, this.y - 10); } return b; } // 获取生命的方法 public int getLife() { return life; } // 加命的方法 public void addLife() { life++; } // 减命的方法 public void deleteLife() { life--; } // 加双倍火力的方法 public void addDoubleFire() { doubleFire = 40; } // 双倍火力清零的方法 public void clearDoubleFire() { doubleFire = 0; } // 英雄机和飞行物撞 public boolean hit(FlyingObject other) {// 传对象 int x1 = this.x - other.width; int x2 = this.x + width; int y1 = this.y - other.height; int y2 = this.y + height; if (other.x >= x1 && other.x <= x2 && other.y >= y1 && other.y <= y2) { return true; } return false; } // 飞行机永不越界 @Override public boolean outOfBounds() { return false; } }
文章源自亦枫博客-https://yflad.cn/2024.html
子弹类:飞行物的子类
文章源自亦枫博客-https://yflad.cn/2024.html
package cn.yflad.game; /** * @Author: yflad (yflad@qq.com) * @Blog: https://yflad.cn * @Date: 2018-12-09 * @role: 子弹类:飞行物的子类 */ public class Bullet extends FlyingObject { // 有什么 int yspeed = -3;// y轴上的速度 /* * 因为子弹的坐标是由英雄机所决定的 所以这里可以用有参构造函数来初始化子弹 */ public Bullet(int x, int y) { image = TestGame.bullet; height = image.getHeight(); width = image.getWidth(); this.x = x; this.y = y; } @Override public void step() { y += yspeed; } // 子弹越界 @Override public boolean outOfBounds() { return y < -height; } }
文章源自亦枫博客-https://yflad.cn/2024.html
加命或加双倍火力
package cn.yflad.game; /** * @Author: yflad (yflad@qq.com) * @Blog: https://yflad.cn * @Date: 2018-12-11 * @role: 奖励类型: 加命或加双倍火力 */ public interface Award { public static final int ADD_LIFE = 0;// 生命 public static final int DOUBLE_FIRE = 1;// 火力 public abstract int getType();// 奖励类型 }
文章源自亦枫博客-https://yflad.cn/2024.html
加分类
package cn.yflad.game; /** * @Author: yflad (yflad@qq.com) * @Blog: https://yflad.cn * @Date: 2018-12-09 * @role: 奖励类型:加分 */ public interface Enemy { // 获取分数的方法 public abstract int getScore(); }
文章源自亦枫博客-https://yflad.cn/2024.html
运行窗口程序:
package cn.yflad.game; import java.awt.Color; 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.util.Arrays; import java.util.Timer; import java.util.TimerTask; import javax.imageio.ImageIO; import javax.swing.JFrame; import javax.swing.JPanel; /** * @Author: yflad (yflad@qq.com) * @Blog: https://yflad.cn * @Date: 2018-12-09 * @role: 实现 */ public class TestGame extends JPanel {// 继承了JPanel类,相当于继承了一个画笔 public static final int WIDTH = 400;// 面板宽 public static final int HEIGHT = 654;// 面板高 /* * 加载图片 */ public static BufferedImage bj; public static BufferedImage airplane; public static BufferedImage bee; public static BufferedImage bullet; public static BufferedImage gameover; public static BufferedImage hero0; public static BufferedImage hero1; public static BufferedImage pause; public static BufferedImage start; // 程序的状态 public static final int START = 0;// 开始 public static final int RUNNING = 1;// 运行状态 public static final int PAUSE = 2;// 暂停状态 public static final int GAMEOVER = 3;// 结束状态 // 默认状态为开始状态 int state = 0; FlyingObject[] flyings = {};// 创建飞行物类型数组 Hero hero = new Hero();// 创建英雄机对象 Bullet[] bullets = {};// 声明一个存放子弹的数组 int score = 0;// 声明一个分数的变量 /* * 通过静态代码块来加载图片 */ static { try { bj = ImageIO.read(TestGame.class.getResource("..\\image\\background.png")); airplane = ImageIO.read(TestGame.class.getResource("..\\image\\airplane.png")); bee = ImageIO.read(TestGame.class.getResource("..\\image\\bee.png")); bullet = ImageIO.read(TestGame.class.getResource("..\\image\\bullet.png")); gameover = ImageIO.read(TestGame.class.getResource("..\\image\\gameover.png")); hero0 = ImageIO.read(TestGame.class.getResource("..\\image\\hero0.png")); hero1 = ImageIO.read(TestGame.class.getResource("..\\image\\hero1.png")); pause = ImageIO.read(TestGame.class.getResource("..\\image\\pause.png")); start = ImageIO.read(TestGame.class.getResource("..\\image\\start.png")); } catch (Exception e) { // 删掉IO System.out.println("图片加载失败!"); } } /* * 用画笔画画,重写JPanel中的paint()方法 */ @Override public void paint(Graphics g) { // 画背景 g.drawImage(bj, 0, 0, null); // 画敌人 for (int i = 0; i < flyings.length; i++) {// 遍历所有敌人(敌机+小蜜蜂) FlyingObject f = flyings[i];// 获取每一个飞行物对象 g.drawImage(f.image, f.x, f.y, null);// 画敌人(敌机+小蜜蜂)对象 } // 画英雄机 g.drawImage(hero.image, hero.x, hero.y, null); // 画子弹 for (int i = 0; i < bullets.length; i++) {// 遍历所有子弹 Bullet b = bullets[i]; g.drawImage(b.image, b.x, b.y, null); } // 设置字体的颜色和大小 g.setColor(Color.BLUE); g.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 24)); // 画分数和生命值 g.drawString("分数:" + score, 10, 25); g.drawString("生命:" + hero.life, 10, 45); // 画状态 switch (state) { case START: g.drawImage(start, 0, 0, null); break; case PAUSE: g.drawImage(pause, 0, 0, null); break; case GAMEOVER: g.drawImage(gameover, 0, 0, null); break; } } /** * 创建飞行物对象的方法 */ public FlyingObject nextOne() { int type = (int) (Math.random() * 10);// 生成敌机和小蜜蜂的概率比大约为10:1 if (type == 0) { return new Bee();// 蜜蜂对象 } return new Airplane();// 敌机对象 } /** * 将创建的飞行物对象添加到飞行物数组中去 */ int index = 0; public void addAction() {// 添加飞行物,并对数组进行扩容 /* * 默认情况下是30毫秒创建一个对象添加到数组中去,通过index遍历来控制添加的对象个数 */ if (index++ % 20 == 0) {// 相当于30*20毫秒往数组中添加一个对象 flyings = Arrays.copyOf(flyings, flyings.length + 1);// 将飞行物数组扩容 flyings[flyings.length - 1] = nextOne();// 将创建的对象放到数组中去 } } /** * 将英雄机发射出来的子弹数组复制到bullets子弹数组中去 */ int index2 = 0; public void shootAction() { if (index2++ % 20 == 0) { // 英雄机发射子弹 Bullet[] bs = hero.shoot(); // 对bullets子弹数据进行扩容 bullets = Arrays.copyOf(bullets, bullets.length + bs.length); // 通过数组的复制将英雄机发射出来的子弹添加到bullets数组中去 System.arraycopy(bs, 0, bullets, bullets.length - bs.length, bs.length); } } /** * 所有飞行物类移动的方法 */ public void stepAction() { for (int i = 0; i < flyings.length; i++) {// 敌机飞行一步 FlyingObject f = flyings[i];// 向上造型。获取每一个敌机对象 /* * 判断对象属于哪个子类,因为子类的移动方式不一样 所以在这里得通过向下造型来操作 */ if (f instanceof Airplane) {// 敌机移动方式 Airplane a = (Airplane) f; a.step(); } if (f instanceof Bee) {// 蜜蜂移动方式 Bee b = (Bee) f; b.step(); } } // 英雄机移动的方式 hero.step(); // 子弹的移动方式 for (int i = 0; i < bullets.length; i++) {// 敌机飞行一步 Bullet b = bullets[i];// 获取每一个敌机对象 b.step(); } } /** * 一个子弹和一堆敌人撞 */ public void bang(Bullet b) { int index = -1;// 记录被撞飞行物下标 // 遍历所有的飞行物对象 for (int i = 0; i < flyings.length; i++) { FlyingObject f = flyings[i]; if (f.checkBang(b)) { // 进入到这说明子弹和飞行物发生了碰撞 index = i;// 将被撞物的下标赋值给临时变量 break; } } if (index != -1) {// 说明子弹和飞行物有碰撞 if (flyings[index] instanceof Enemy) {// 判断属于什么奖励类型.是敌人,则加分 score += ((Enemy) flyings[index]).getScore(); } if (flyings[index] instanceof Award) {// 若为奖励,设置奖励 Bee bee = (Bee) flyings[index]; int type = bee.getType();// 获取奖励类型 if (type == 0) { hero.addLife();// 加命 } if (type == 1) { hero.addDoubleFire();// 加双倍火力 } } // 对碰撞的飞行物进行缩容 FlyingObject fo = flyings[index]; flyings[index] = flyings[flyings.length - 1]; flyings[flyings.length - 1] = fo; flyings = Arrays.copyOf(flyings, flyings.length - 1); } } /** * 一堆子弹和一堆敌人碰撞 */ public void bangAction() { // 遍历所有的子弹对象 for (int i = 0; i < bullets.length; i++) { Bullet b = bullets[i]; bang(b); } } /** * 英雄机和敌机碰撞 */ public void hitAction() { int index = -1;// 记录被撞飞行物的下标 for (int i = 0; i < flyings.length; i++) {// 遍历所有敌人 FlyingObject f = flyings[i];// 获取每一个敌人 if (hero.hit(f)) {// 进入到这里说明英雄机和飞行物撞上了 index = i; break; } } if (index != -1) { // 交换被撞的敌人与数组中的最后一个元素 FlyingObject fo = flyings[index]; flyings[index] = flyings[flyings.length - 1]; flyings[flyings.length - 1] = fo; flyings = Arrays.copyOf(flyings, flyings.length - 1);// 缩容(去掉最后一个元素,即被撞的敌人对象) hero.deleteLife();// 碰撞后要减命 hero.clearDoubleFire();// 碰撞后火力值清零 } } /** * 检查游戏是否结束的方法 */ public void checkGameOver() { int life = hero.getLife(); if (life == 0) { state = GAMEOVER; } } /** * 检查飞行物越界方法 * 思路:用逆向思维,将在窗口中显示的对象添加到相关的数组中 */ public void outOfBoundsAction() { int index = 0;// 记录数组的下标,记录对象的个数 // 声明一个在窗口中显示的飞行物对象数组 FlyingObject[] aliveFlying = new FlyingObject[flyings.length];// 默认所有的对象都活着 // 遍历所有的飞行物,把没有越界的放到下面,判断是否越界,越界的不添加到aliveFlying for (int i = 0; i < flyings.length; i++) { FlyingObject f = flyings[i]; if (!f.outOfBounds()) { // 进入到这说明没有越界,添加到aliveFlying aliveFlying[index++] = f; } } // for循环遍历完之后,fAlive中存放的都是没有越界的对象 // 将fAlive这个数组中对象把flying数组覆盖掉 flyings = Arrays.copyOf(aliveFlying, index); // 子弹数组和飞行物数组一致 index = 0;// 记录数组的下标,记录对象的个数 Bullet[] aliveBullet = new Bullet[bullets.length];// 默认所有的对象都活着 // 遍历所有的飞行物,把没有越界的放到下面,判断是否越界,越界的不添加到aliveBullet for (int i = 0; i < bullets.length; i++) { Bullet b = bullets[i]; if (!b.outOfBounds()) { // 进入到这说明没有越界,添加到aliveBullet aliveBullet[index++] = b; } } // for循环遍历完之后,fAlive中存放的都是没有越界的对象 // 将fAlive这个数组中对象把flying数组覆盖掉 bullets = Arrays.copyOf(aliveBullet, index); } /** * 通过定时器来执行程序和刷新页面 */ Timer timer = new Timer(); /* * 执行代码 */ public void action() { // 添加一个鼠标监听器 MouseAdapter l = new MouseAdapter() { // 重写所需的方法 @Override public void mouseMoved(MouseEvent e) {// 鼠标移动 // 获取鼠标的x坐标,y坐标 int x = e.getX(); int y = e.getY(); hero.moveTo(x, y);// 调用Hero类的方法 } // 重写鼠标点击的方法 @Override public void mouseClicked(MouseEvent e) { if (state == START) { state = RUNNING; } if (state == GAMEOVER) { state = START; score = 0;// 分数清零 hero.life = 3;// 恢复到初始状态 hero.clearDoubleFire(); state=START; } } // 重写鼠标移入的方法 @Override public void mouseExited(MouseEvent e) { if (state == RUNNING) { state = PAUSE; } } // 重写鼠标移出的方法 @Override public void mouseEntered(MouseEvent e) { if (state == PAUSE) { state = RUNNING; } } }; timer.schedule(new TimerTask() { @Override public void run() { if (state == RUNNING) { addAction();// 调用 将敌机对象放入敌机数组 的方法 shootAction();// 英雄机发射出来的子弹 stepAction();// 调用 敌机移动 方法 bangAction();// 一堆子弹和一堆敌人碰撞 hitAction();// 英雄机撞 outOfBoundsAction(); checkGameOver(); } repaint();// 刷新页面 } }, 0, 30); // 将监听器适配器添加到鼠标移动事件中去 this.addMouseMotionListener(l);// 添加鼠标运动监听器 this.addMouseListener(l);// 添加鼠标监听器 } public static void main(String[] args) { JFrame frame = new JFrame("飞机大战1.0"); // 创建一个窗体程序,相当于画板 TestGame game = new TestGame(); // 创建一个画板对象 frame.add(game); // 将面板(画笔)添加到frame frame.setSize(WIDTH, HEIGHT);// 设置窗口的大小 frame.setVisible(true);// 设置窗口的显示 frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 设置窗口关闭按钮 frame.setAlwaysOnTop(true);// 设置窗口置顶显示 frame.setLocationRelativeTo(null);// 设置窗口居中 game.action(); // 执行 } }
文章源自亦枫博客-https://yflad.cn/2024.html
文章源自亦枫博客-https://yflad.cn/2024.html文章源自亦枫博客-https://yflad.cn/2024.html继续阅读
扫扫关注公众号
我的微信
扫扫体验小程序
我的公众号
江苏省苏州市 1F
照这个写法,背景图是静止的吗,好像并不会动