Airplane
package com.shoot;
/**
* 敌机的编写 继承自FlyingObject 还要实现Enemy
*/
public class Airplane extends FlyingObject implements Enemy {
//敌机向下移动的速度
private int speed = 2;
//添加构造器
public Airplane() {
image = ShootGame.airplane;
height = image.getHeight();
width = image.getWidth();
//x的出现范围 0~ShootGame.WIDTH - this.width
x = (int) (Math.random() * (ShootGame.WIDTH - this.width));
y = -height;
}
//向下移动
@Override
public void step() {
y += speed;
}
//敌机越界 true表示越界 false表示未越界
@Override
public boolean outOfBounds() {
return y > ShootGame.HEIGHT;
}
//每个敌机五分
@Override
public int getScore() {
return 5;
}
}
Award
package com.shoot;
/**
* 奖励接口: Award, 有两种奖励
* 第一种是双倍火力
* 第二种是生命值奖励
*/
public interface Award {
//将两种奖励设置为两个常量
int DOUBLE_FIRE = 0;
int LIFE = 1;
//定义一个抽象方法,用于获取奖励类型
int getType();
}
Bee
package com.shoot;
/**
* 小蜜蜂类型: 继承FlyingObject 实现 Award
*/
public class Bee extends FlyingObject implements Award {
//bee的速度,在x,y轴的上
private int xSpeed = 3;
private int ySpeed = 2;
public Bee() {
image = ShootGame.bee;
height = image.getHeight();
width = image.getWidth();
//x的数值应该在0-ShootGame之间
x = x = (int) (Math.random() * (ShootGame.WIDTH - width));
//y的数值应该在-height之间
y = -height;
}
//bee身上的奖励在1,0之间
@Override
public int getType() {
return (int) (Math.random() * 2);
}
@Override
public void step() {
y += ySpeed;
x += xSpeed;
//当小蜜蜂到达最右边时
if (x > ShootGame.WIDTH - width) {
//x轴的速度改为 -3
xSpeed = -xSpeed;
}
//当小蜜蜂到达最左边时
if (x <= 0) {
//x轴的速度改为 3
xSpeed = -xSpeed;
}
}
@Override
public boolean outOfBounds() {
return this.y > ShootGame.HEIGHT;
}
}
Bullet
package com.shoot;
/**
* 子弹类型 :继承FlyingObject
*/
public class Bullet extends FlyingObject {
//子弹每一次移动多少
private int speed = 2;
//添加子弹的构造器,进行初始化 设置形参 参数为子弹的x,y坐标
//形参是由英雄机决定的
public Bullet(int x, int y) {
image = ShootGame.bullet;
this.x = x;
this.y = y;
width = image.getWidth();
height = image.getHeight();
}
//子弹上移 y值减少
@Override
public void step() {
y -= speed;
}
//子弹是否越界
//ture表示越界 false表示未越界
@Override
public boolean outOfBounds() {
return this.y < -this.height;
}
}
Enemy
package com.shoot;
/**
* 定义接口:Enemy 敌人 敌机身上有五分
*/
public interface Enemy {
//获取分数的功能
int getScore();
}
FlyingObject
package com.shoot;
import java.awt.image.BufferedImage;
/**
* 定义所有飞行物的父类型,flyingObject 抽象类和不抽象类都可以、
* 共同特征:
* image x y width height
* 共同行为:
* 移动:step() 抽象方法
* 是否越界: boolean outOfBounds()
*
* 分析发现:敌机和bee都会与子弹撞击,因此将该行为提取,放入父类中
* shootBy(Bullet bullet)
*/
public abstract class FlyingObject {
protected BufferedImage image;//图片
protected int x; //图片左上角x坐标
protected int y;//图片左上角y坐标
protected int width;//图片的宽
protected int height;//图片的高
public abstract void step();
public abstract boolean outOfBounds();
//敌机和bee是否被子弹击中 因此子弹是形参
// true 表示击中 false 表示未击中
public boolean shootBy(Bullet bullet) {
//获取传入的子弹的坐标
int x = bullet.x;
int y = bullet.y;
//获取飞行物的x范围
int xMin = this.x;
int xMax = this.x + this.height;
//判断子弹是否在飞行物x范围之内
int yMin = this.y;
int yMax = this.y + this.height;
//子弹的x,y在飞行物的x,y范围之内,就是击中
return x > xMin && x < xMax && y > yMin && y < yMax;
}
}
Hero
package com.shoot;
import java.awt.image.BufferedImage;
/**
* 英雄机的编写: 继承FlyingObject
*/
public class Hero extends FlyingObject {
//火力值属性 为零时单倍火力 大于零双倍火力
private int doubleFire;
private int life;
//图片数组,使用两张图片 模拟英雄机在飞
private BufferedImage[] images;
//定义一个变量 用来协助英雄机的两张图片切换,100ms切换一次
private int index;
//添加构造器
public Hero() {
//初始位置 火力 hp
x = 150;
y = 430;
doubleFire = 0;
life = 3;
image = ShootGame.hero0;
width = image.getWidth();
height = image.getHeight();
images = new BufferedImage[]{ShootGame.hero0, ShootGame.hero1};
index = 0;
}
/*hero的飞行: 就是两张图片切换的模拟效果
注意 该方法是10ms调用一次,但是想要100ms执行一次
调用次数 图片数组下标 index++/10 index++/10%2
10ms 0 1 0
20ms 0 2
...
90ms 0 9 1
110ms 1 10
120ms 1 11
100ms 1 12
...
190ms 1 19
200ms 0 20 2
......
*/
@Override
public void step() {
image = images[index++ / 10 % 2];
}
// hero永不越界 ,越界就是游戏暂停
@Override
public boolean outOfBounds() {
return false;
}
/*
英雄机随着鼠标的位置而变化
@param x 鼠标的x坐标
@param y 鼠标的y坐标
*/
public void moveTo(int x, int y) {
//hero的x ,y坐标
this.x = x - width / 2;
this.y = y - height / 2;
}
//英雄机发射子弹
public Bullet[] shoot() {
Bullet[] bullets = null;
int xwidth = width / 4; //hero的1/4宽度
if (doubleFire > 0) {
//两发子弹
bullets = new Bullet[2];
bullets[0] = new Bullet(x + xwidth, y - 20);
bullets[1] = new Bullet(x + 3 * xwidth, y - 20);
//随发射次数减少火力值 (弹夹数方式) 双倍奖励后最多发射20次子弹
doubleFire -= 2;
} else {
//一发子弹
bullets = new Bullet[1];
bullets[0] = new Bullet(x + 2 * xwidth-3, y - 20);
}
return bullets;
}
/**
* 判断英雄机是否被飞行物obj 击中
*
* @param obj
* @return
*/
public boolean hit(FlyingObject obj) {
//获取飞行物的左上角的x,y坐标
int ox = obj.x;
int oy = obj.y;
//获取飞行物的宽和高
int ow = obj.width;
int oh = obj.height;
//范围 以英雄机以为参照物
return ox > this.x - obj.width && ox < this.x + this.width &&
oy > this.y - obj.height && oy < this.y + this.height;
}
public void subLife() {
life--;
}
public int addLife() {
return life;
}
//绘制时,需要获取多少条命 判断撞击后 减命判定是否结束
public int getLife() {
return life;
}
//修改撞机之后的火力值 恢复单倍状态
public void setDoubleFire(int doubleFire) {
this.doubleFire = doubleFire;
}
// 获取双倍火力的奖励
public void getDoubleFire() {
doubleFire += 40;
}
}
ShootGame
package com.shoot;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
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;
/**
* 定义一个游戏窗口类型:
* 1.继承自SWING的JPanel类型
*/
public class ShootGame extends JPanel {
//设置将窗口的宽高设置成常量
public static final int WIDTH = 400;
public static final int HEIGHT = 650;
//面板上应该要一个英雄机对象
protected Hero hero = new Hero();
//bee和敌机
protected FlyingObject[] flyings = {};
//子弹
protected Bullet[] bullets = {};
//添加成员变量 积分器,hp
protected int scores = 0;
protected int lifes = hero.getLife();
//定义游戏的状态属性和常量
private int state; //该变量,用于记录当前游戏的状态值
private static final int READY = 0;
private static final int RUNNING = 1;
public static final int PAUSE = 2;
public static final int GAMEOVER = 3;
//定义图片的静态属性
public static BufferedImage airplane; //敌机
public static BufferedImage background; //背景
public static BufferedImage bee; //蜜蜂
public static BufferedImage bullet; //子弹
public static BufferedImage gameover; //游戏结束画面
public static BufferedImage hero0; //英雄机1
public static BufferedImage hero1; //英雄机2
public static BufferedImage pause; //暂停画面
public static BufferedImage start; //游戏启动画面
static {
try {
airplane = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("airplane.png"));
background = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("background.png"));
bee = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("bee.png"));
bullet = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("bullet.png"));
gameover = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("gameover.png"));
hero0 = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("hero0.png"));
hero1 = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("hero1.png"));
pause = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("pause.png"));
start = ImageIO.read(ShootGame.class.getClassLoader().getResourceAsStream("start.png"));
} catch (Exception e) {
e.printStackTrace();
}
}
//重写面板的基本绘制功能: g 可以理解为是一个画笔
@Override
public void paint(Graphics g) {
//绘制背景图片
g.drawImage(background, 0, 0, null);
//绘制hero
g.drawImage(hero.image, hero.x, hero.y, null);
//绘制飞行物
paintFlying(g);
//绘制子弹
paintBullet(g);
//绘制分数和生命
paintScoreAndLife(g);
//绘制状态
paintState(g);
}
private void paintState(Graphics g) {
switch (state) {
case READY:
g.drawImage(ShootGame.start, 0, 0, null);
break;
case PAUSE:
g.drawImage(ShootGame.pause, 0, 0, null);
break;
case GAMEOVER:
g.drawImage(ShootGame.gameover, 0, 0, null);
break;
}
}
private void paintScoreAndLife(Graphics g) {
g.setColor(Color.red);
//new Font("字体名称",字体样式,字体大小)
g.setFont(new Font("楷体", Font.BOLD, 20));
g.drawString("Scores:" + scores, 10, 30);
g.drawString("LIFES:" + hero.getLife(), 10, 50);
}
private void paintBullet(Graphics g) {
//遍历子弹
for (Bullet bullet : bullets) {
g.drawImage(bullet.image, bullet.x, bullet.y, null);
}
}
private void paintFlying(Graphics g) {
//遍历Flyings
for (FlyingObject flying : flyings) {
g.drawImage(flying.image, flying.x, flying.y, null);
}
}
//定义一个定时器成员变量
private Timer timer;
private long interval = 10;
//游戏启动方法
public void action() {
//鼠标监听器
MouseAdapter mouseAdapter = new MouseAdapter() {
//鼠标移动操作
@Override
public void mouseMoved(MouseEvent e) {
if (state == RUNNING) {
//从e(鼠标事件)对象上可以获取鼠标在屏幕上的x,y的坐标
int x = e.getX();
int y = e.getY();
//调用英雄机的移动方法,跟随鼠标
hero.moveTo(x, y);
}
}
@Override
public void mouseExited(MouseEvent e) {
if (state == RUNNING) {
state = PAUSE;
}
}
@Override
public void mouseEntered(MouseEvent e) {
if (state == PAUSE) {
state = RUNNING;
}
}
//重写鼠标点击事件
@Override
public void mouseClicked(MouseEvent e) {
if (state == READY) {
state = RUNNING;
}
}
};
//将鼠标监听对象与面板关联
this.addMouseListener(mouseAdapter); //鼠标监听
this.addMouseMotionListener(mouseAdapter); //鼠标滚轮监听
//初始化定时器
timer = new Timer();
//调用定时器的调度计划schedule(TimerTask tt, long delay, long period);
//第一个参数:定时任务,第二个参数:延迟时间,第三个参数,周期 注意:单位是 毫秒
timer.schedule(new TimerTask() {
@Override
public void run() {
if (state == RUNNING) {
enterAction();//飞行物入场
stepAction();//所有飞行物开始移动,包括英雄机
shootAction();//子弹入场
bangAction(); //子弹和敌人碰撞
checkOutOfBounds();//没有被击中的敌人和子弹越界处理
checkGameOver();//检查游戏结束
}
//调用面板自带的重新绘制功能,每10毫秒绘制一次
repaint();
}
}, interval, interval);
}
private void checkGameOver() {
if (isGameOver()) {
state = GAMEOVER;
}
}
private boolean isGameOver() {
int index = -1;//记录被撞击的飞行物的下标
//英雄机与敌人的碰撞
for (int i = 0; i < flyings.length; i++) {
FlyingObject flying = flyings[i];
if (hero.hit(flying)) {
index = i;
break;
}
}
//不等于-1 说明hero和敌人相撞,hero恢复默认状态
if (index != -1) {
hero.setDoubleFire(0);
hero.subLife();
//删除相撞的敌人
flyings[index] = flyings[flyings.length - 1];
//缩容
flyings = Arrays.copyOf(flyings, flyings.length - 1);
}
return hero.getLife() <= 0;
}
/**
* 敌人和子弹越界处理
* 思路: 将不越界的元素,copy到一个新的数组,然后使用面板上的flyings指向新数组
*/
private void checkOutOfBounds() {
//敌机越界处理
int index = 0;
//创建一个新数组
FlyingObject[] newflyings = new FlyingObject[flyings.length];
for (FlyingObject flying : flyings) {
if (!flying.outOfBounds()) {
newflyings[index] = flying;
index++; //可以用来统计没有越界的个数,以及为新数组提供从左到右依次挨着的下标.
//将没有越界的元素的地址依次从左到右放入新的数组
}
}
//对新数组进行缩容,缩容后的新地址直接赋值给面板上的flyings
flyings = Arrays.copyOf(newflyings, index);
//子弹越界处理
int index1 = 0;
//一个子弹的新数组
Bullet[] newbullets = new Bullet[bullets.length];
for (Bullet bullet : bullets) {
if (!bullet.outOfBounds()) {
newbullets[index1] = bullet;
index1++;
}
}
//新数组缩容并赋值给面板
bullets = Arrays.copyOf(newbullets, index1);
}
/**
* 子弹与敌人(bee+敌机)碰撞
*/
private void bangAction() {
//遍历子弹数组,查看每一发子弹是否集中敌人
for (Bullet bullet1 : bullets) {
//用于检查一个子弹是否击中敌人
bang(bullet1);
}
}
/**
* 用于检查一个子弹是否击中敌人
*/
public void bang(Bullet bullet) {
//定义一个变量,记录被击中敌人的下标
int index = -1;
//遍历每一个敌人是否被子弹击中
for (int i = 0; i < flyings.length; i++) {
//获取一个具体的敌人
FlyingObject flying = flyings[i];
if (flying.shootBy((bullet))) {
index = i;
//如果一个敌人被击中,需要处理该敌人的其他操作,不需要继续向后检查其他敌人是否被击中
//因为10ms之后该方法还会继续调用进行再次检查,所以该循环可以打断跳出。
break;
}
}
//当检查到一个敌人被击中
if (index != -1) {
//如果index不等于-1,说明该敌人被击中
//1.从数组中移除该敌人
FlyingObject flying = flyings[index];
//判断敌人变量
if (flying instanceof Enemy) {
//强转
scores += ((Enemy) flying).getScore();
} else {
//不是敌机就是bee
Award award = (Award) flying;
int type = award.getType();
switch (type) {
case Award.LIFE:
lifes += 1;
break;
case Award.DOUBLE_FIRE:
hero.getDoubleFire();
break;
}
}
//删除被击中的敌人,将该敌人与敌人数组中最后一个元素交换位置,不交换
//直接将最后一个元素覆盖一下被击中的元素即可。最后位置上的元素就没用了,直接缩容
flyings[index] = flyings[flyings.length - 1];
//缩容之后产生一个新数组,需要将新数组的地址赋值到面板的飞行物数组变量里
flyings = Arrays.copyOf(flyings, flyings.length - 1);
}
}
/**
* hero射击行为
* 频率过高 改成300ms
*/
int bulletIndex = 0;
private void shootAction() {
bulletIndex++; //10ms执行一次
if (bulletIndex % 30 == 0) { //如果bulletIndex是30的倍数,说明过了3·00ms
Bullet[] bs1 = hero.shoot();
//对面板上的子弹扩容
bullets = Arrays.copyOf(bullets, bullets.length + bs1.length);
//新子弹放入数组中
/*
System.arraycopy(srcArr,srcPos,desArr,desPos,length).
作用:将一个数组中的元素拷贝到另外一个数组中
srcArr:源数组。即要被拷贝/复制的数组
srcPos:源数组中的下标。表示从该下标开始复制,源数组中的元素
desPos:目标数组。即要拷贝到哪个数组中。
desArr:目标数组中的下标。表示从该下标开始存储,向后存储
length:要拷贝的个数。
*/
System.arraycopy(bs1, 0, bullets, bullets.length - bs1.length, bs1.length);
// 使用for循环方式实现
// for(int i= bs1.length-1;i>=0;i--){
// 将bs1中的最后一个元素放在多出来的位置的第一个位置
// bullets[bullets.length-1-i] =bs1[i];
// }
}
}
/**
* 所有飞行物移动
*/
private void stepAction() {
//hero移动
hero.step();
//遍历飞行物数组
for (FlyingObject flying : flyings) {
flying.step();
}
//遍历子弹数组
for (Bullet bullet : bullets) {
bullet.step();
}
}
/**
* 创建一个方法,用于控制飞行物(bee和敌机)的入场
* 如果随机数是0 是bee
* 其他数字都是敌机
*/
public FlyingObject nextOne() {
int num = (int) (Math.random() * 20);
if (num < 2) {
return new Bee();
} else {
return new Airplane();
}
}
/*
定义一个方法,用于控制飞行物(bee和敌机)的入场
10ms频率太高 控制为400ms出现一个飞行物
*/
int flyingsIndex = 0;
private void enterAction() {
flyingsIndex++; //10m执行一次
if (flyingsIndex % 40 == 0) { //如果flyingsIndex是40的倍数,说明过了400ms
//创建一个敌机对象
FlyingObject obj = nextOne();
//将敌机对象放入数组中
flyings = Arrays.copyOf(flyings, flyings.length + 1);
flyings[flyings.length - 1] = obj;
}
}
public static void main(String[] args) {
//创建窗口对象
JFrame frame = new JFrame("飞机大战");
//创建JPanel对象
ShootGame game = new ShootGame(); //面板
//将面板嵌入窗口
frame.add(game);
//对窗口进行一些简单的设置
frame.setSize(WIDTH, HEIGHT); //设置宽与高
frame.setAlwaysOnTop(true); //将游戏界面设置到屏幕的最上面
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//窗口关闭,即程序结束
frame.setLocationRelativeTo(null);//界面居中
frame.setVisible(true);//窗口可见,并尽快调用绘制paint()功能
//启动游戏功能
game.action();
}
}