利用面想对象知识,实现一个简单的“飞机大战”小游戏。代码粗糙,有兴趣可以自己优化下!
案例具体要求:
要求实现飞机大战功能有敌机,加分
有奖励机,加生命值,或者火力值,飞行有一定规则
有英雄机,碰到敌机会减少生命值,且火力值回复初始,直至游戏结束
有子弹,与敌机、奖励机碰撞,敌机、奖励机消失
有计分,生命值(声明变量,画出分数,生命值。子弹和飞行物有碰撞时,判断类型)
敌机,奖励机不可以越界(逆向思维,将在窗口中显示的对象添加到相关的数组中。定义一个abstract,判断y的值,有没有超过飞行物的值。遍历所有的飞行物,把没有越界的放到下面,判断是否越界)
有开始界面,鼠标控制英雄机,鼠标移出暂停游戏,鼠标进入继续游戏,生命归零,结束游戏(定义一个初始状态,添加鼠标监听器,调用英雄机类,重写相关方法)
实现代码:
飞行物的父类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();
}
普通的敌机类:飞行物的子类
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的值,有没有超过本类的值
}
}
小蜜蜂类:飞行物的子类
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;
}
}
英雄机类:飞行物的子类
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;
}
}
子弹类:飞行物的子类
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;
}
}
加命或加双倍火力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();// 奖励类型
}
加分类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();
}
运行窗口程序: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(); // 执行
}
}
下载信息 飞机大战素材