typora-root-url: img
Java项目-飞机大战
1.项目概述
Shoot游戏是一款十分有趣的射击类小游戏,流畅的画面,高难度的挑战。游戏中,玩家驾驶英雄机,在空中进行战斗。点击并移动自己的英雄机,发射炮弹,打掉敌机以及蜜蜂,来获得奖励和分数,打掉一架敌机得5分,打掉一只蜜蜂得1条命或者获得20次双倍火力,如果撞上敌机或者小蜜蜂,将减少命双倍火力清零。每撞到一次蜜蜂或者是敌机命数减1,当命数为0时,则游戏结束。
2.准备工作
2.1.工程创建
首先,新建名为Shoot的java工程;然后在src目录下新建包cn.tedu.shoot;最后将该工程所需的图片拷贝到该包下,工程结构如下:
2.2.创建父类和接口
2.2.1.创建FlyingObject类
由上图可以分析出英雄机、敌机、子弹以及蜜蜂都属于整个游戏中的飞行物,且都有x,y,width以及height属性,因此,将这些属性抽取到父类FlyingObject中。另外,他们在界面上都以图片形式显示,因此在父类FlyingObject中添加image属性,表示它们的贴图,并提供上述5个属性的getter和setter方法,代码如下:
package cn.tedu.shoot;
import java.awt.image.BufferedImage;
/**
* 飞行物
* @author MaYF
*
*/
public class FlyingObject {
protected BufferedImage image;//图片
protected int width; //宽
protected int height; //高
protected int x; //x坐标
protected int y; //y坐标
}
因为以上属性要被子类使用故使用protected修饰。
2.2.2.创建Enemy接口
创建Enemy接口,表示敌人。如果子弹击中敌机,英雄机可以获取分数,因此,在Enemy接口中提供获取分数的方法,代码如下:
package cn.tedu.shoot;
/**
* 敌人
* @author MaYF
*
*/
public interface Enemy {
/**
* 得分
* @return
*/
public int getScore();
}
2.2.3.创建Award接口
创建接口Award,实现该接口的类表示奖励。如果子弹 击中了蜜蜂,英雄机可以获取奖励。奖励有两种形式,分别是双倍活力或者增命。因此,提供获取奖励类型的方法,代码如下:
package cn.tedu.shoot;
public interface Award {
/**
* 火力值
*/
public int DOUBLE_FIRE=0;
/**
* 英雄机命
*/
public int LIFE=1;
/**
* 获取奖励类型
* @return(0或者1)
*/
public int getType();
}
上述代码中,如果奖励类型为0,则表示奖励双倍火力;如果奖励类型为1,则表示奖励1条命。
2.3.创建子类
2.3.1.创建Airplane类
新建类Airplane,表示敌飞机。敌机属于飞行物,因此继承FlyObject类;敌机也属于敌人,因此需要实现Enemy接口。敌机可以向下移动,因此有移动的速度作为属性,代码如下所示:数据私有化,方法公开化;
package cn.tedu.shoot;
/**
* 敌机,既是飞行物也是敌人
* @author MaYF
*
*/
public class Airplane extends FlyingObject implements Enemy {
/**
* 敌机走步的步数
*/
private int speend=2;
/**
* 图片数组
*/
private BufferedImage[] images;
/**
* 小敌机的切换频率
*/
private int index;
/**
* 重写getScore()方法
*/
@Override
public int getScore() {
return 0;
}
}
2.3.1.创建BigAirplane类
新建类BigAirplane,表示大敌飞机。大敌机属于飞行物,因此继承FlyObject类;大敌机也属于敌人,因此需要实现Enemy接口。大敌机可以向下移动,因此有移动的速度作为属性,代码如下所示:数据私有化,方法公开化;
package cn.tedu.shoot;
/**
* 敌机,既是飞行物也是敌人
* @author MaYF
*
*/
public class BigAirplane extends FlyingObject implements Enemy {
/**
* 敌机走步的步数
*/
private int speend=2;
/**
* 图片数组
*/
private BufferedImage[] images;
/**
* 大敌机的切换频率
*/
private int index;
/**
* 重写getScore()方法
*/
@Override
public int getScore() {
return 0;
}
}
2.3.2.创建Bee类
蜜蜂属于飞行物,因此继承自FlyingObject类;击中蜜蜂可以获得奖励,因此,需要实现Award接口,并且有奖励类型作为属性。蜜蜂可以左右移动、向下移动,因此有移动速度作为属性,代码如下:
package cn.tedu.shoot;
public class Bee extends FlyingObject implements Award {
/**
* x坐标走步步数
*/
private int xspeed=1;
/**
* y坐标走步步数
*/
private int yspeed=2;
/**
* 奖励类型(0或)1
*/
private int awardType;
/**
* 图片数组
*/
private BufferedImage[] images;
/**
* 小蜜蜂的切换频率
*/
private int index;
/**
* 重写getType()
* @return 返回奖励类型
*/
@Override
public int getType() {
return awardType;
}
}
2.3.3.创建Bullet类
子弹属于飞行物,因此,继承自FlyingObject类;子弹可以向上移动,因此有移动的速度作为属性,代码如下:
package cn.tedu.shoot;
/**
* 子弹是飞行物
* @author MaYF
*
*/
public class Bullet extends FlyingObject {
/**
* 子弹走步的步数
*/
private int speed=3;
}
2.3.4.创建Hero类
英雄机属于飞行物,因此继承自FlyingObject类;英雄机发出子弹,击中蜜蜂可以获取双倍活力或者增命。因此,将双倍火力的子弹数量和命的数量作为该类的属性,代码如下:
package cn.tedu.shoot;
import java.awt.image.BufferedImage;
/**
* 英雄机是飞行物
* @author MaYF
*
*/
public class Hero extends FlyingObject {
/**
* 命
*/
private int life;
/**
* 火力值
*/
private int doubleFire;
/**
* 图片数组
*/
private BufferedImage[] images;
/**
* 控制切换的频率
*/
private int index;
}
上述代码中,还有images属性和index属性,其中images属性表示Hero的贴图,Hero的贴图由两张图片组成,因此使用数组类型;index属性是控制两张图片进行交替显示的频率值;
2.4.创建ShootGame主类
新建类ShootGame,该类继承自JPanel,在该类中,使用静态常量定义面板的宽和高,并使用ImageIO的read方法加载图片,代码如下:
package cn.tedu.shoot;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
/**
* 主程序类
* @author MaYF
*
*/
public class ShootGame{
public static final int WIDTH = 400; //窗口宽
public static final int HEIGHT = 654; //窗口高
public static BufferedImage background;//背景图
public static BufferedImage start;//启动图
public static BufferedImage pause;//暂停图
public static BufferedImage gameover;//游戏结束图
public static BufferedImage bullet;//子弹
public static BufferedImage airplane0;//敌机0
public static BufferedImage airplane1;//敌机1
public static BufferedImage bigairplane0;//大敌机0
public static BufferedImage bigairplane1;//大敌机1
public static BufferedImage bee0;//蜜蜂0
public static BufferedImage bee1;//蜜蜂1
public static BufferedImage hero0;//英雄机0
public static BufferedImage hero1;//英雄机1
public static BufferedImage bom1;//爆炸效果1
public static BufferedImage bom2;//爆炸效果2
public static BufferedImage bom3;//爆炸效果3
public static BufferedImage bom4;//爆炸效果4
static {//初始化静态资源(加载图片)
try {
//读取图片到静态图片变量中
background = ImageIO.read(ShootGame.class.getResource("background.png"));
bigairplane0 = ImageIO.read(ShootGame.class.getResource("bigairplane0.png"));
bigairplane1 = ImageIO.read(ShootGame.class.getResource("bigairplane1.png"));
airplane0 = ImageIO.read(ShootGame.class.getResource("airplane0.png"));
airplane1 = ImageIO.read(ShootGame.class.getResource("airplane1.png"));
bee0 = ImageIO.read(ShootGame.class.getResource("bee0.png"));
bee1 = ImageIO.read(ShootGame.class.getResource("bee1.png"));
bullet = ImageIO.read(ShootGame.class.getResource("bullet.png"));
hero0 = ImageIO.read(ShootGame.class.getResource("hero0.png"));
hero1 = ImageIO.read(ShootGame.class.getResource("hero1.png"));
bom1 = ImageIO.read(ShootGame.class.getResource("bom1.png"));
bom2 = ImageIO.read(ShootGame.class.getResource("bom2.png"));
bom3 = ImageIO.read(ShootGame.class.getResource("bom3.png"));
bom4 = ImageIO.read(ShootGame.class.getResource("bom4.png"));
pause = ImageIO.read(ShootGame.class.getResource("pause.png"));
gameover = ImageIO.read(ShootGame.class.getResource("gameover.png"));
start = ImageIO.read(ShootGame.class.getResource("start.png"));
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
}
}
3.初始化属性
3.1.Airplane类添加构造
给Airplane添加构造初始化属性
/**
* 构造方法
*/
public Airplane() {
image=ShootGame.airplane0;//敌机图片
width=image.getWidth();//敌机宽
height=image.getHeight();//敌机高
Random rand=new Random();//随机数对象
//x的范围为0到窗口宽-敌机宽之间的随机数
x=rand.nextInt(ShootGame.WIDTH-this.width);
//y=-this.height;//y为负的敌机的高
y=200;
//小敌机图片数组赋值
images=new BufferedImage[]{
ShootGame.airplane0,ShootGame.airplane1};
index=0;
}
3.2.BigAirplane类添加构造
/**
* 构造方法
*/
public BigAirplane() {
image=ShootGame.bigairplane0;//敌机图片
width=image.getWidth();//敌机宽
height=image.getHeight();//敌机高
Random rand=new Random();//随机数对象
//x的范围为0到窗口宽-敌机宽之间的随机数
x=rand.nextInt(ShootGame.WIDTH-this.width);
//y=-this.height;//y为负的敌机的高
y=200;
images = new BufferedImage[]{ShootGame.bigairplane0, ShootGame.bigairplane1};
index=0;
}
3.3.Bee类添加构造
给Bee添加构造初始化属性
/**
* 构造方法
*/
public Bee() {
image=ShootGame.bee0;//蜜蜂图片
width=image.getWidth();//宽
height=image.getHeight();//高
Random rand=new Random();//随机数
//x为0到屏幕宽-蜜蜂之间的随机数
x=rand.nextInt(ShootGame.WIDTH-this.width);
//y=-this.height;//y为负的蜜蜂的高
y=200;
//奖励类型为0到1之间的随机数
awardType=rand.nextInt(2);
//蜜蜂图片数组赋值
images=new BufferedImage[]{ShootGame.bee0,ShootGame.bee1};
index=0;
}
3.4.Bullet类添加构造
给Bullet添加构造初始化属性
/**
* 构造方法
* @param x 子弹的x坐标
* @param y 子弹的y坐标
*/
public Bullet(int x,int y) {
image=ShootGame.bullet;//图片
width=image.getWidth();//子弹宽
height=image.getHeight();//子弹高
this.x=x;//x坐标随着英雄机动
this.y=y;//y坐标随着英雄机动
}
3.5.Hero类添加构造
给Hero添加构造初始化属性
/**
* 构造方法
*/
public Hero() {
image = ShootGame.hero0;//英雄机图片
width = image.getWidth();//英雄机宽
height = image.getHeight();//英雄机高
x = 150;//x坐标:固定
y = 400;//y坐标:固定
life = 3;//命数为3
doubleFire = 0;//火力值为0,代表单倍火力
//图片数组hero0和hero1
images = new BufferedImage[]{ShootGame.hero0, ShootGame.hero1};
index=0;//控制切换的频率
}
4.编写main方法
在该方法中设置窗口的大小,居中、点击敞口的右上角“X”关闭窗口以及设置窗口可见,代码如下:
public class ShootGame extends JPanel{}
public static void main(String[] args) {
JFrame frame = new JFrame("飞机大战");//创建窗口对象
ShootGame game = new ShootGame(); //创建面板对象
frame.add(game); //将面板添加到窗口中
frame.setSize(WIDTH, HEIGHT); //设置窗口的大小
frame.setAlwaysOnTop(true); //设置其总在最上
//设置窗口默认的关闭操作(关闭窗口则退出程序)
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//frame.setIconImage(new ImageIcon("images/icon.jpg").getImage()); // 设置窗体的图标
frame.setLocationRelativeTo(null); //设置窗体初始位置(居中)
frame.setVisible(true); //设置窗口状态为显示
}
5.绘制界面
在ShootGame中编写如下代码:
private Hero hero = new Hero(); //英雄机
private FlyingObject[] flyings = {}; //敌人(敌机+小蜜蜂)
private Bullet[] bullets = {}; //子弹数组
//private Ember[] embers = {}; // 灰烬
public ShootGame() {
flyings=new FlyingObject[3];//3个飞行物
flyings[0]=new Airplane();//小敌机
flyings[1]=new BigAirplane();//大敌机
flyings[2]=new Bee();//蜜蜂
bullets=new Bullet[1];//1发子弹
bullets[0]=new Bullet(50, 100);
}
编写绘画方法
/** 画英雄机 */
private void paintHero(Graphics g) {
g.drawImage(hero.image, hero.x, hero.y, null);//画英雄机
}
/** 画敌人对象(敌人+小蜜蜂) */
private void paintFlyingObjects(Graphics g) {
for (int i = 0; i < flyings.length; i++) {//遍历所有敌人
FlyingObject f=flyings[i];//获取每一个敌人
g.drawImage(f.image, f.x, f.y, null);//画敌人对象
}
}
/** 画子弹对象 */
private void paintBullets(Graphics g) {
for (int i = 0; i < bullets.length; i++) {//遍历所有子弹
Bullet b=bullets[i];//获取每一个子弹
g.drawImage(b.image, b.x, b.y, null);//画子弹对象
}
}
修改Bee和Airplane中坐标
//y=-this.height;//y为负的蜜蜂的高
y=200;
//y=-this.height;//y为负的敌机的高
y=200;
重写paint
@Override
public void paint(Graphics g) {
g.drawImage(background, 0, 0, null);//画背景图
paintHero(g);//画英雄机
paintFlyingObject(g);//画敌人(敌人+小蜜蜂)
paintBullets(g);//画子弹
}
运行ShootGame效果图如图所示:
6.实现敌人(敌机+蜜蜂)入场
6.1.准备工作
打开ShootGame将构造方法删除;
还原Airplane、BigAirplane和Bee的y坐标;
y=-this.height;//y为负的敌机的高
//y=200;
y=-this.height;//y为负的大敌机的高
//y=200;
y=-this.height;//y为负的蜜蜂的高
//y=200;
6.2.分析
/** 画敌人对象(敌人+小蜜蜂) */
private void paintFlyingObjects(Graphics g) {
for (int i = 0; i < flyings.length; i++) {//遍历所有敌人
FlyingObject f=flyings[i];//获取每一个敌人
g.drawImage(f.image, f.x, f.y, null);//画敌人对象
}
}
/** 画子弹对象 */
private void paintBullets(Graphics g) {
for (int i = 0; i < bullets.length; i++) {//遍历所有子弹
Bullet b=bullets[i];//获取每一个子弹
g.drawImage(b.image, b.x, b.y, null);//画子弹对象
}
}
已上代码分析,入场时在界面能画出来,f代表数组中的一个对象,画flyings数组;此时只能画英雄机,因为flyings和bullets数组的长度为0;因此分析如下:
敌人(蜜蜂+敌机)入场
1.创建敌人对象–随机
2.把敌人对象添加到flyings数组中:实现只要运行程序什么都不做,敌机和蜜蜂仍然出现在页面;要实现此功能要使用定时器;
6.4.创建敌人对象
6.4.1.添加创建敌人方法
创建敌人对象过程中是随机的,有可能是小敌机、大敌机、也有可能是小蜜蜂;
工具方法:
Math.sqrt(25);
Math.random();
工厂方法都是生产对象的;
此处采用向上造型;
找到ShootGame类,在paint方法上面添加如下代码:
若方法操作仅仅与参数相关而与对象无关时用静态方法,方法分为工具方法和工厂方法,工厂方法返回值类型为对象。
/**
* 创建敌人对象(小敌机、大敌机、小蜜蜂)
* @return
*/
public static FlyingObject nextOne() {
Random rand=new Random();//随机数对象
int type=rand.nextInt(20);//随机生成0-19之间的随机数
if(type==0) {//随机数为0则返回蜜蜂对象
return new Bee();
}else if(type<10) {//随机数为1-9则返回小敌机对象
return new Airplane();
}else {//随机数为10-19则返回大敌机对象
return new BigAirplane();
}
}
6.4.2.添加启动程序方法
已上敌人对象添加到flyings数组中,但是此时程序不认识nextOne方法,只认识main。
1.在paint方法上面位置写action方法:
/**
* 启动程序的方法
*/
public void action() {
}
2.在main中添加如下调用代码:
game.action();
6.4.3.定时器
此时如果不需要干什么,就能使敌机小蜜蜂依然会出现在界面上时,需要用到定时器;
定时器:
1.事件间隔。
2.定时干事:此时需要定时做的事有敌人入场、子弹入场和走步。
第一个参数需要TimerTask类型
Timer.schedule(new TimerTask(),10,10);
这样写肯定错误,TimerTask是抽象类,不能new一个对象;抽象类不能实例化;但是可以造型,将TimerTask当做父类,传入一个子类;创建一个子类给子类new一个对象
Timer.schedule(new Aoo(),10,10);
class Aoo extends TimerTask{
}
new Aoo()只需要创建一次,此时采用匿名内部类;
测试定时器:代码如下
private Timer timer;//定时器
private int intervel=10;//时间间隔(毫秒)
/**
* 启动程序的方法
*/
public void action() {
timer=new Timer();//创建定时器对象
timer.schedule(new TimerTask() {//定时器任务
//run方法是定时器任务要执行的操作,只要到时间就自动调用run()
@Override
public void run() {
System.out.println(111);
}
}, intervel, intervel);
}
解释:
第一个10:从程序启动到第一次定时触发间隔。
第二个10:第一次定时到第二次定时,第二次定时到第三次定时。
此时控制台自动输出111
6.4.4.添加敌人入场方法
定时需要敌人入场;因此在run中写enterAction方法,专门实现敌人入场。
1.在nextOne方法下面写enterAction方法;
int flyEnteredIndex=0;//敌人入场计数
/**
* 敌人(大小敌机+小蜜蜂)入场
*/
public void enterAction() {//10毫秒走一次
flyEnteredIndex++;//10毫秒增1
//40次才成立一次,相当于400(40*10)毫秒
if(flyEnteredIndex%40==0) {
//40,80,120,160,200
FlyingObject obj=nextOne();//获取敌人对象
}
}
10毫秒走一次,意味着1秒中创建100个对象,有100个敌机,因此要加以控制;每400毫秒走一次,一秒钟两个半敌机
2.在action方法的定时器任务中调用enterAction方法;
//run方法是定时器任务要执行的操作,只要到时间就自动调用run()
@Override
public void run() {
enterAction();//调用敌人入场方法
}
6.5.将敌人对象添加到flyings数组中
private Hero hero = new Hero(); //英雄机
private FlyingObject[] flyings = {}; //敌人(敌机+小蜜蜂)
private Bullet[] bullets = {}; //子弹数组
由于此时flying数组目前为空没有元素;要装元素就要扩容,数组的长度在创建后是不可改变的,所谓扩容是指创建一个更大的数组并将原有数组的内容复制到其中。
int flyEnteredIndex=0;//敌人入场计数
/**
* 敌人(大小敌机+小蜜蜂)入场
*/
public void enterAction() {//10毫秒走一次
flyEnteredIndex++;//10毫秒增1
//40次才成立一次,相当于400(40*10)毫秒
if(flyEnteredIndex%40==0) {
//40,80,120,160,200
FlyingObject obj=nextOne();//获取敌人对象
flyings=Arrays.copyOf(flyings, flyings.length+1);//扩容
//将敌人对象添加到flyings数组的最后一个元素位置
flyings[flyings.length-1]=obj;
}
}
目前敌人已经装入;此时看不出效果,要想看到效果,修改xy坐标并重画。
6.6.重画
paint()被调用2种方式:
1…setVisible(true);
2.repaint();
//run方法是定时器任务要执行的操作,只要到时间就自动调用run()
@Override
public void run() {
enterAction();//调用敌人入场方法
repaint();//重绘,调用paint方法
}
修改Airplane、BigAirplane和Bee的y坐标;
//y=-this.height;//y为负的敌机的高
y=200;
//y=-this.height;//y为负的大敌机的高
y=100;
//y=-this.height;//y为负的蜜蜂的高
y=300;
执行得到如下效果:
7.飞行物走步
修改回Airplane、BigAirplane和Bee原来坐标
7.1.添加飞行物走步方法
飞行物走步是定时干的事;因此写到定时器中;调用stepAction()方法;
//run方法是定时器任务要执行的操作,只要到时间就自动调用run()
@Override
public void run() {
enterAction();//调用敌人入场方法
stepAction();//调用飞行物走步方法
repaint();//重绘,调用paint方法
}
在enterAction下面定义stepAction方法:飞行物走步不需要控制了,让10ms走一次比较连续;
/**
* 飞行物走步
*/
public void stepAction() {//10毫秒走一次
}
7.2.添加step方法
此时,敌机能走步,蜜蜂能走步,蜜蜂子弹和英雄机(两个图片在切换)能走步:所有飞行物都有走步行为,并且必须有。
共有行为放在FlyingObject父类中,并将step方法和FlyingObject类都设置为abstract抽象类,当继承抽象类必须重写抽象方法。
/**
* 飞行物
* @author MaYF
*
*/
public abstract class FlyingObject {
protected BufferedImage image;//图片
protected int width; //宽
protected int height; //高
protected int x; //x坐标
protected int y; //y坐标
public abstract void step();//方法抽象,类也必须抽象
}
只定义方法没有方法体;
此时子类报错;要重写抽象方法;
7.2.1.关于小敌机
小敌机从上向下移动,所以y坐标向下移动speed步数。
/**
* 重写step()方法
*/
@Override
public void step() {
image=images[index++/10%images.length];
y+=speed;
}
7.2.2.关于大敌机
大敌机从上向下移动,所以y坐标向下移动speed步数。
/**
* 重写step()方法
*/
@Override
public void step() {
image=images[index++/10%images.length];
y+=speed;
}
7.2.3.关于子弹
子弹从下向上移动,所以y坐标向下移动speed步数。
/**
* 重写step()方法
*/
@Override
public void step() {
y-=speed;
}
7.2.4.对于蜜蜂
因为蜜蜂是斜下移动,所以x坐标向右y坐标向下移动相应步数。
/**
* 重写step()方法
*/
@Override
public void step() {
image=images[index++/10%images.length];
x+=xspeed;
y+=yspeed;
if(x>ShootGame.WIDTH-this.width) {
xspeed-=1;
}
if(x<0) {
xspeed=1;
}
}
7.2.5.关于英雄机
此时英雄机的两个图片在切换,代码如下:
/**
* 重写step()方法
*/
@Override
public void step() {
image=images[index++/10%images.length];
}
// index++;
// int a = index/10;
// int b = a%2;
// image = images[b];
image = images[index++/10%images.length];//英雄机每100ms切换一次
/*
* 10MS index = 1 a = 1/10=0 b = 0; image = image[0]
* 20MS index = 2 a = 0; b = 0;
* 30MS index = 3 a = 0; b = 0;
* ……
* 100MS index = 10 a = 1; b = 1;image = image[1]
* 110MS image = image[1]
* ……
* 200MS image = image[0]
*/
7.3.调用step
属于定时发生,在ShootGame中stepAction方法中调用英雄机走步方法,并调用敌人(大小敌机)和蜜蜂的走步方法,子弹的走步方法。
/**
* 飞行物走步
*/
public void stepAction() {//10毫秒走一次
hero.step();
for (int i = 0; i < flyings.length; i++) {//遍历所有飞行物
flyings[i].step();
}
for (int i = 0; i < bullets.length; i++) {//遍历所有子弹
bullets[i].step();
}
}
此处运行效果如下;
8.子弹入场
8.1.分析
子弹入场:
子弹是英雄机发射出来的,定时发生的。
1.创建子弹对象
2.将子弹对象添加到bullets数组中
8.2.添加入场方法
因为发射子弹是定时发生的,所以在action中添加子弹发射方法shootAction。
shootAction();//调用子弹入场方法
8.3.定义入场方法
在ShootGame类中添加入场方法,如下
/**
* 子弹入场方法
*/
public void shootAction(){//10毫秒走一次
}
8.4.创建子弹对象
因为发射子弹是英雄机的行为,所以在Hero中添加如下代码:
/**
* 英雄机发射子弹
*/
public Bullet[] shoot(){
}
注意:英雄机发射子弹其实就是创建子弹对象,发射子弹有可能单发也有可能双发。所以返回类型为Bullet[]数组。
8.4.1.判断火力值
根据火力值判断单发还是双发。
/**
* 英雄机发射子弹
*/
public Bullet[] shoot(){
if(doubleFire>0){//双倍火力
Bullet[] bullets=new Bullet[2];//创建子弹数组,存储两个子弹对象
return bullets;
}else{//单倍火力
Bullet[] bullets=new Bullet[1];//创建子弹数组,存储一个子弹对象
return bullets;
}
}
8.4.2.传参创建子弹对象
传参:
New 子弹对象要传参,Bullet类构造中有x和y参数
单倍火力在中心发;双倍火力在一半的一半;将英雄机的宽分为4分;单倍火力在1/2位置;
双倍火力在1/4和3/4位置;下来就是得到这些点;
单倍火力:
子弹的x是英雄机的x+1/2英雄机的宽;
双倍火力:
子弹的x是英雄的x+1/4英雄机宽
子弹的x是英雄的x+3/4英雄机宽
Y是英雄机的y-20像素;
/**
* 英雄机发射子弹
*/
public Bullet[] shoot(){
int xStep=this.width/4;//英雄机的四分之一
if(doubleFire>0){//双倍火力
Bullet[] bullets=new Bullet[2];//创建子弹数组,存储两个子弹对象
//子弹x坐标:英雄机的x+1/4英雄机宽
//子弹y坐标:英雄机的y-20
bullets[0]=new Bullet(this.x+xStep, this.y-10);
//子弹x坐标:英雄机的x+3/4英雄机宽
//子弹y坐标:英雄机的y-20
bullets[1]=new Bullet(this.x+xStep*3, this.y-10);
doubleFire-=2;//双倍火力二十次
return bullets;
}else{//单倍火力
Bullet[] bullets=new Bullet[1];//创建子弹数组,存储一个子弹对象
//子弹x坐标:英雄机的x+1/2英雄机宽
//子弹y坐标:英雄机的y-20
bullets[0]=new Bullet(this.x+xStep*2,this.y-10);
return bullets;
}
}
8.5.编写入场方法
回到ShootGame类的shootAction()方法中,调用shoot()方法将子弹对象扩容添加到bullets数组中,并控制子弹的创建为300ms一次。
/**
* 子弹入场方法
*/
int shootIndex=0;//子弹入场计数
public void shootAction(){//10毫秒走一次
shootIndex++;
if(shootIndex%30==0){//0 30 60 90 120 150
Bullet[] bs=hero.shoot();//英雄机发射子弹
//扩容bullets数组
bullets=Arrays.copyOf(bullets, bullets.length+bs.length);
//将子弹数组bs中的子弹添加到bullets数组中
System.arraycopy(bs, 0, bullets, bullets.length-bs.length, bs.length);
}
}
此时是单发,在Hero中将doubleFire改成40就变成双倍火力,由于没发一次减2所以一会就会变成单发。
9.控制英雄机
9.1.分析
**事件:**发生了一个事
**事件处理:**发生这个事之后所做的响应
事件:
鼠标点击------启动状态变为运行状态
鼠标移动------英雄机随着动
鼠标移开------运行状态变为暂停状态
鼠标移入------暂停状态变为运行状态
如何将事件和事件处理联系在一起,采用监听器
采用监听器需如下两步:
1.创建监听器对象
2.安装监听器
谁在操作-----鼠标在操作
这些事件都是在对谁操作-----面板
因此要将监听器安装在面板上;
//创建监听器对象
MouseListener l = new MouseListener(){
重写mouseClicked
重写mousePressed
重写mouseReleased
重写mouseEntered
重写mouseExited
}
MouseMotionListener ml = new MouseMotionListener(){
重写mouseDragged
重写mouseMoved
}//将侦听器安装到面板上
this.addMouseListener(l);
this.addMouseMotionListener(ml);
我们采用
MouseAdpater是MouseListener,MouseMotionListener的孩子
l是MouseAdpter的子类的对象
l是Listener的孩子的孩子
MouseAdapter implements MouseListener,MouseMotionListener{
}
addMouseListener(MouseListener l){
}
addMouseMotionListener(MouseMotionListener l){
}
9.2.英雄机随鼠标动
在ShootGame中添加如下程序:
public void action(){
//创建监听器对象
MouseAdapter l=new MouseAdapter(){
};
//在面板上安装监听器
this.addMouseListener(l);
this.addMouseMotionListener(l);
}
9.2.1.重写方法
让英雄机随着鼠标动,需要重写mouseMoved鼠标移动方法
public void action(){
//创建监听器对象
MouseAdapter l=new MouseAdapter(){
@Override
public void mouseMoved(MouseEvent e) {//鼠标移动事件
}
};
}
9.2.2.获得鼠标的坐标
public void mouseMoved(MouseEvent e) {//鼠标移动事件
int x=e.getX();//鼠标的x坐标
int y=e.getY();//鼠标的y坐标
}
9.2.3.添加moveTo方法
设置英雄机改变位置,这个移动是英雄机的行为,找到Hero类添加
英雄机的x=鼠标的x-1/2英雄机的宽
英雄机的y=鼠标的y-1/2英雄机的高
/**
* 英雄机移动
* @param x 鼠标的x坐标
* @param y 鼠标的y坐标
*/
public void moveTo(int x,int y){
this.x=x-this.width/2;
this.y=y-this.height/2;
}
9.2.4.调用moveTo方法
@Override
public void mouseMoved(MouseEvent e) {//鼠标移动事件
int x=e.getX();//鼠标的x坐标
int y=e.getY();//鼠标的y坐标
hero.moveTo(x, y);//调用英雄机移动方法
}
10.子弹与敌机的碰撞
10.1.分析
子弹撞到敌人,敌人消失,子弹不消失,分析撞的行为属于哪个对象?
10.2.定义碰撞方法
由图可以看出,当子弹的x坐标在蜜蜂的x与x+width之间,并且子弹的y坐标在蜜蜂的y与y+height之间时,子弹击中敌人;
回到FlyingObject中编写如下程序:
/**
* 飞行物(大小敌机、蜜蜂)被子弹碰撞的方法
* @param bullet 子弹
* @return 是否被碰撞(是 true 否 false)
*/
public boolean shootBy(Bullet bullet){
int x1=this.x;//代表被碰撞飞行物的x坐标
int x2=this.x+this.width;//飞行物的x+飞行物的宽
int y1=this.y;//代表被碰撞飞行物的y坐标
int y2=this.y+this.height;//飞行物的y+飞行物的高
int x=bullet.x;//子弹的x坐标
int y=bullet.y;//子弹的y坐标
//返回x在x1与x2之间,并且y在y1与y2之间的判断结果。
return x>x1&&x<x2&&y>y1&&y<y2;
}
子弹与敌人撞上之后
1.英雄机得分或者得奖励
-
若得分则加分
-
若得奖励,判断奖励类型是命还是火力值
若为火力则增火力
若为命则增命
2.敌人消失
10.3.编写奖励方法
如果被子弹打中,奖励命或者火力值;回到Hero中编写如下两个方法。
/**
* 英雄机加命
*/
public void addLife(){
life++;
}
/**
* 英雄机增加火力值
*/
public void addDoubleFire(){
doubleFire+=40;
}
10.4.检测子弹与敌人撞
10.4.1.调用碰撞方法
检查敌人和子弹撞、子弹和敌人撞也是定时触发的,因为什么不动,子弹撞该得分得分,该增命增命;
找到Action编写子弹与敌人撞方法bangAction;所有子弹和所有敌人一起撞因此要遍历子弹敌人;
@Override
public void run() {
enterAction();//调用飞行物入场方法
stepAction();//调用飞行物走步方法
shootAction();//调用子弹入场方法
bangAction();//调用子弹与飞行物碰撞的方法
}
10.4.2.定义bangAction方法
在action外写bangAction方法
/**
* 检测所有子弹与所有飞行物(大小敌机、小蜜蜂)碰撞
*/
int score=0;//总分数
public void bangAction(){
}
10.4.3.定义bang方法
子弹与敌人的碰撞
1.判断子弹与额敌人是否撞上了;
2.英雄机得分或者得奖励
3.敌人消失
第一步:
所有敌人和一个子弹撞,需要遍历飞行物数组。
第二步:
获取到被撞敌人后得分或者得火力值,此时要判断被撞对象是什么东西;
第三步:
将被撞的敌人对象消失——从flying数组中删除
1.消失意味着在页面上不再画出来了
2.画的是flyings数组中的对象
3.flyings数组中有的就能画出来
flyings数组中没有的就画不出来
4.消失意味着在flyings中不存在了
5.将被撞的敌人对象从flyings中删除
缩容保证最后的元素是被撞对象即可,将被撞对象和最后一个元素交换
/**
* 检测一个子弹与所有飞行物(大小敌机、小蜜蜂)的碰撞
* @param b 被撞子弹
* @param j 被撞子弹坐标
*/
public void bang(Bullet b,int j){
int index=-1;//记录被撞敌人的下标,不能为0
for (int i = 0; i < flyings.length; i++) {//遍历所有敌人
FlyingObject f=flyings[i];//得到每一个敌人
if(f.shootBy(b)){//判断是否发生碰撞
index=i;//记录被撞敌人的下标
break;//其余的敌人不再做比较
}
}
if(index!=-1){//有被撞的敌人
FlyingObject one=flyings[index];//获取被撞的对象
if(one instanceof Enemy){//判断被撞对象是否是敌人
Enemy e=(Enemy)one;//强制转换
score+=e.getScore();//将敌人的分数得到并加到总分数上
}
if(one instanceof Award){//判断被撞对象是否是奖励
Award a=(Award)one;//强转
int type=a.getType();//获取奖励类型
switch(type){//判断是双倍火力还是加命
case Award.DOUBLE_FIRE:
hero.addDoubleFire();//增加火力值
break;
case Award.LIFE:
hero.addLife();//增加命
break;
}
}
//让被撞敌人消失
FlyingObject f=flyings[index];//取出被撞敌人
//将数组最后一个敌人添加到被撞敌人的位置
flyings[index]=flyings[flyings.length-1];
//将被撞敌人添加到数组的最后一个位置等待数组缩容
flyings[flyings.length-1]=f;
//缩容 会删除数组的最后一个元素
flyings=Arrays.copyOf(flyings,
flyings.length-1);
//让被撞子弹消失
//将数组最后一个子弹添加到被撞子弹的位置
bullets[j]=bullets[bullets.length-1];
//将被撞子弹添加到数组的最后一个位置等待数组缩容
bullets[bullets.length-1]=b;
//缩容 会删除数组的最后一个元素
bullets=Arrays.copyOf(bullets,
bullets.length-1);
}
}
10.4.4.编写bangAction()方法
所有敌人和所有子弹撞,需要遍历子弹数组。
/**
* 检测所有子弹与所有飞行物(大小敌机、小蜜蜂)碰撞
*/
int score=0;//分数
public void bangAction(){
//取出所有子弹
for (int i=0;i<bullets.length;i++) {
//将子弹数组中的所有子弹都进行检测
bang(bullets[i],i);
}
}
11.画分画命
11.1.添加方法
得到score就行,命是英雄机的命
找到Hero添加getLife方法
/**
* 获取英雄机的命
* @return
*/
public int getLife() {
return life;
}
找到Airplane和BigAirplane添加getScore方法
/**
* 重写后的getScore()
*/
@Override
public int getScore() {
return 5;//打中小敌机得5分
}
/**
* 重写后的getScore()
*/
@Override
public int getScore() {
return 10;//打中大敌机得10分
}
11.2.添加绘制方法
/**
* 画分画命
*/
private void paintScore(Graphics g) {
g.setColor(Color.WHITE);
//设置样式: SANS_SERIF BOLD加粗 22字体大小
g.setFont(new Font(Font.SANS_SERIF,Font.BOLD,22));
g.drawString("SCORE:"+score, 10, 25);//画分
g.drawString("LIFE:"+hero.getLife(), 10, 45);//画命
}
11.3.调用绘制方法
/**
* 绘制方法
*/
@Override
public void paint(Graphics g) {
paintHero(g);//画英雄机
paintFlyingObjects(g);//画飞行物
paintBullets(g);//画子弹
paintScore(g);//调用画分画命
}
12.删除越界飞行物
12.1.分析
越界是所有对象均有的,放到父类FlyingObject;
12.2.定义越界方法
/**
* 检测飞行物是否越界
* @return
*/
public abstract boolean outOfBounds();
12.3.重写越界方法
对于大小敌机和蜜蜂
/**
* 重写越界方法outOfBounds()
*/
@Override
public boolean outOfBounds() {
return this.y>ShootGame.HEIGHT;//小敌机的y坐标大于窗口的高
}
对于子弹
/**
* 重写越界方法
*/
@Override
public boolean outOfBounds() {
return this.y<-this.height;
}
对于英雄机
英雄机永不越界
/**
* 英雄机不越界
*/
@Override
public boolean outOfBounds() {
return false;
}
12.4.定义删除越界飞行物方法
找到ShootGame类定义outOfBoundsAction()方法
/**
* 删除越界飞行物
*/
public void outOfBoundsAction() {
//不越界数组的下标和不越界飞行物的数量
int index=0;
//创建一个新飞行物数组用来存储不越界的飞行物
FlyingObject[] flyingLives=new FlyingObject[flyings.length];
for (int i = 0; i < flyings.length; i++) {//遍历所有飞行物
FlyingObject f=flyings[i];//获取每一个飞行物
if(!f.outOfBounds()) {//不越界的飞行物
flyingLives[index]=f;//将不越界的飞行物添加到新数组中
index++;//下标加一,不越界飞行物数量加一
}
}
//将不越界数组中的飞行物全部复制给flyings数组
flyings=Arrays.copyOf(flyingLives, index);
//删除越界子弹
//将下标和数量归零
index=0;
//创建一个新的子弹数组存储不越界的子弹
Bullet[] bulletsLives=new Bullet[bullets.length];
for (int i = 0; i < bullets.length; i++) {//遍历所有子弹
Bullet b=bullets[i];//获取每一个子弹
if(!b.outOfBounds()) {//不越界的子弹
bulletsLives[index]=b;//将还不越界的子弹添加到新数组中
index++;//下标加一,不越界飞行物数量加一
}
}
//将不越界的数组中子弹全部复制给bullets数组
bullets=Arrays.copyOf(bulletsLives, index);
}
12.5.调用删除方法
检查越界后删掉,属于定时发生的;因此写到action中的run中调用;
public void run() {
enterAction();//调用飞行物入场方法
stepAction();//调用飞行物走步方法
shootAction();//调用子弹入场方法
bangAction();//调用子弹与飞行物碰撞的方法
outOfBoundsAction();//调用删除越界飞行物的方法
}
13.检查英雄机与敌人的碰撞
13.1.分析
图
13.2.编写碰撞方法
在Hero中编写hit方法:
/**
* 英雄机和飞行物碰撞的方法
* @param other 飞行物(大小敌机、小蜜蜂)
* @return true 碰撞 false未碰撞
*/
public boolean hit(FlyingObject other) {
int x1=other.x-this.width/2;
int x2=other.x+other.width+this.width/2;
int y1=other.y-this.height/2;
int y2=other.y+other.height+this.height/2;
int hx=this.x+this.width/2;//英雄机的中心点x坐标
int hy=this.y+this.height/2;//英雄机的中心点y坐标
return hx>x1 && hx<x2 && hy>y1 && hy<y2;
}
13.3.编写减命减火力方法
英雄机和敌人撞上了
英雄机减命,火力值归0;
敌人消失;
在Hero中编写:
/**
* 英雄机减命
*/
public void reduceLife() {
life--;
}
/**
* 设置英雄机火力值为单倍
*/
public void setDoubleFire(int doubleFire) {
this.doubleFire=doubleFire;
}
13.4.判断游戏是否结束
什么时候游戏结束:英雄机命数小于等于0即游戏结束,英雄机命数为1时蜜蜂和敌机同时撞英雄机命数为-1;游戏结束
在ShootGame中编写isGameOver
/**
* 判断游戏游戏是否结束
* @return true 结束 false 未结束
*/
public boolean isGameOver() {
for (int i = 0; i < flyings.length; i++) {//遍历所有敌人
FlyingObject f=flyings[i];//获取每一个敌人
if(hero.hit(f)) {//判断英雄机是否与当前这个敌人相撞
//减命
hero.reduceLife();
//英雄机活力值清零
hero.setDoubleFire(0);
//将数组最后一个敌人添加到被撞敌人的位置
flyings[i]=flyings[flyings.length-1];
//将被撞敌人添加到数组的最后一个位置等待数组缩容
flyings[flyings.length-1]=f;
//缩容 会删除数组的最后一个元素
flyings=Arrays.copyOf(flyings, flyings.length-1);
}
}
return hero.getLife()<=0;
}
13.5.定义检查游戏结束方法
在ShootGame中编写
/**
* 检测游戏结束
*/
public void checkGameOver() {
if(isGameOver()) {//游戏结束
System.out.println("游戏结束!");
}
}
13.6.调用检查方法
英雄机和敌人撞也是定时发生的;
检查是否结束也是定时发生的
回到Action中的run调用
public void run() {
enterAction();//调用飞行物入场方法
stepAction();//调用飞行物走步方法
shootAction();//调用子弹入场方法
bangAction();//调用子弹与飞行物碰撞的方法
outOfBoundsAction();//调用删除越界飞行物的方法
checkGameOver();//调用检测游戏结束的方法
}
14.画状态
14.1.分析
游戏分为四种状态,分别为START、RUNNING、PAUSE、GAME_OVER,表示游戏开始状态、运行状态、暂停状态以及游戏结束状态。
首先结束下鼠标事件对状态的影响,当执行鼠标点击事件时,会对游戏中的START状态、GAME_OVER状态产生影响。如果点击鼠标时为START状态,则将游戏的状态设置为RUNNING,即点击鼠标游戏进入运行状态,如果点击鼠标时为GAME_OVER状态,则将flyings数组、bullets数组、hero对象、score变量设置为初始状态,并将状态设置为START状态。
14.2.编写四个常量和状态属性
public class ShootGame 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;//记录当前的状态,初始状态为未启动状态
}
14.3.状态切换
14.3.1.定义绘制状态方法
/**
* 绘制游戏的状态
* @param g
*/
private void paintState(Graphics g) {
switch(state) {
case START:
g.drawImage(start,0,0,null);
break;
case PAUSE:
g.drawImage(pause,0,0,null);
break;
case GAME_OVER:
g.drawImage(gameover,0,0,null);
break;
}
}
14.3.2.调用绘制方法
找到paint方法
/**
* 绘制方法
*/
@Override
public void paint(Graphics g) {
paintHero(g);//画英雄机
paintFlyingObjects(g);//画飞行物
paintBullets(g);//画子弹
paintScore(g);//画分画命
paintState(g);//画游戏状态
}
14.3.3.调用状态
回到run中,此时运行程序敌人不见;只有英雄机在动。
@Override
public void run() {
if(state==RUNNING) {
enterAction();//调用飞行物入场方法
stepAction();//调用飞行物走步方法
shootAction();//调用子弹入场方法
bangAction();//调用子弹与飞行物碰撞的方法
outOfBoundsAction();//调用删除越界飞行物的方法
checkGameOver();//调用检测游戏结束的方法
}
repaint();//在if外,重新绘制
}
此时鼠标动英雄机不动了;
@Override
public void mouseMoved(MouseEvent e) {//鼠标移动事件
if(state==RUNNING) {
int x=e.getX();//鼠标的x坐标
int y=e.getY();//鼠标的y坐标
hero.moveTo(x, y);//调用英雄机移动方法
}
}
13.3.4.添加事件
添加鼠标点击事件
此时启动能变运行,但是没法将结束变为启动。
@Override
public void mouseClicked(MouseEvent e) {//鼠标点击事件
switch(state) {//根据当前状态操作
case START://启动状态
state=RUNNING;//修改为运行状态
break;
case GAME_OVER://结束状态
state=START;//修改为启动状态
break;
}
}
回到检测游戏结束方法中,此时结束状态变启动
/**
* 检测游戏结束
*/
public void checkGameOver() {
if(isGameOver()) {//游戏结束
state=GAME_OVER;
}
}
此时还需要所有数据清零;
@Override
public void mouseClicked(MouseEvent e) {//鼠标点击事件
switch(state) {//根据当前状态操作
case START://启动状态
state=RUNNING;//修改为运行状态
break;
case GAME_OVER://结束状态
score=0;//分数清零
flyings=new FlyingObject[0];//敌人清空
bullets=new Bullet[0];//子弹清空
hero=new Hero();//英雄机重新创建
state=START;//修改为启动状态
break;
}
}
添加鼠标移出和移入事件
在移出和移入事件中添加暂停状态
@Override
public void mouseExited(MouseEvent e) {//鼠标移出事件
//在运行状态下出现鼠标移出事件则将状态改为暂停
if(state==RUNNING) {
state=PAUSE;
}
}
@Override
public void mouseEntered(MouseEvent e) {//鼠标移入事件
//在暂停状态下出现鼠标移入事件则将状态改为运行状态
if(state==PAUSE) {
state=RUNNING;
}
}
15.背景图轮播
15.1.定义天空类
package cn.tedu.shoot;
/**
* 表示天空
* @author Administrator
*
*/
public class Sky extends FlyingObject{
/**
* 构造方法
*/
public Sky() {
image=ShootGame.background;//背景图片
height=image.getHeight();//背景图的高度
y1=-ShootGame.HEIGHT;
}
/**
* 天空的走步步数
*/
private int speed=1;
/**
* 第二张天空的y坐标
*/
protected int y1;
/**
* 重写走步方法
*/
@Override
public void step() {
y+=speed;//第一张y增加向下走
y1+=speed;//第二张y增加向下走
if(y>=ShootGame.HEIGHT) {//第一张图片移动到窗口下方了
y=-ShootGame.HEIGHT;//将第一张图片调整到窗口的上方
}
if(y1>=ShootGame.HEIGHT) {//第二张图片移动到窗口下方了
y1=-ShootGame.HEIGHT;//将第二张图片调整到窗口的上方
}
}
@Override
public boolean outOfBounds() {
return false;//背景图不轮播
}
}
15.2.创建天空对象
在ShootGame中创建天空对象
private Hero hero=new Hero();//英雄机
private Sky sky=new Sky();//天空对象
private FlyingObject[] flyings={};//飞行物数组(大敌机、小敌机、小蜜蜂)
private Bullet[] bullets={};//子弹数组
15.3.定义绘制天空方法
/**
* 绘制天空
* @param g
*/
private void paintSky(Graphics g) {
g.drawImage(sky.image,sky.x,sky.y,null);//画背景图
g.drawImage(sky.image,sky.x,sky.y1,null);//画背景图
}
15.4.调用绘制方法
找打paint()方法
/**
* 绘制方法
*/
@Override
public void paint(Graphics g) {
paintHero(g);//画英雄机
paintFlyingObjects(g);//画飞行物
paintBullets(g);//画子弹
paintScore(g);//画分画命
paintState(g);//画游戏状态
paintSky(g);//调用绘制天空
}
15.5.调用天空走步方法
找到stepAction()方法
/**
* 飞行物走步的启动方法
*/
public void stepAction(){
sky.step();//天空走步
hero.step();//英雄机走步
for (int i = 0; i < flyings.length; i++) {//遍历所有飞行物(大小敌机、蜜蜂)
flyings[i].step();//大小敌机、蜜蜂走步
}
for (int i = 0; i < bullets.length; i++) {//遍历所有子弹
bullets[i].step();//子弹走步
}
}
15.6.天空清空
@Override
public void mouseClicked(MouseEvent e) {//鼠标点击事件
switch(state) {//根据当前状态操作
case START://启动状态
state=RUNNING;//修改为运行状态
break;
case GAME_OVER://结束状态
score=0;//分数清零
flyings=new FlyingObject[0];//敌人清空
bullets=new Bullet[0];//子弹清空
hero=new Hero();//英雄机重新创建
sky=new Sky();//天空清空
embers=new Ember[0];//爆炸清空
state=START;//修改为启动状态
break;
}
}
16.爆炸效果
16.1.定义爆炸类
package cn.tedu.shoot;
import java.awt.image.BufferedImage;
/**
* 灰烬 飞机被打掉以后的残影
* @author Robin
*/
public class Ember {
private BufferedImage[] images={};
private int index;
private int i;
private BufferedImage image;
private int intevel = 10;
private int x,y;
public Ember(FlyingObject object) {
images = new BufferedImage[] {ShootGame.bom1,ShootGame.bom2,ShootGame.bom3,ShootGame.bom4};
image = ShootGame.bom1;
x = object.x;
y = object.y;
index = 0;
i = 0;
}
public boolean burnDown(){
i++;
if(i%intevel==0){
if(index == images.length){
return true;
}
image = images[index++];
}
return false;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public BufferedImage getImage() {
return image;
}
}
16.2.创建爆炸对象数组
private Hero hero=new Hero();//英雄机
private Sky sky=new Sky();//天空
private FlyingObject[] flyings={};//飞行物数组(大敌机、小敌机、小蜜蜂)
private Bullet[] bullets={};//子弹数组
private Ember[] embers = {}; // 灰烬
16.3.绘制爆炸方法
/**
* 绘制爆炸方法
* @param g
*/
public void paintEmber(Graphics g) {
for (int i = 0; i < embers.length; i++) {
Ember e = embers[i];
g.drawImage(e.getImage(), e.getX(), e.getY(), null);
}
}
16.4.调用绘制爆炸方法
/**
* 绘制方法
*/
@Override
public void paint(Graphics g) {
paintSky(g);//画天空
paintHero(g);//画英雄机
paintFlyingObjects(g);//画飞行物
paintBullets(g);//画子弹
paintScore(g);//画分画命
paintState(g);//画游戏状态
paintEmber(g);//调用绘制爆炸方法
}
16.5.删除爆炸方法
private void emberAction() {
Ember[] live = new Ember[embers.length];
int index = 0;
for (int i = 0; i < embers.length; i++) {
Ember ember = embers[i];
if(! ember.burnDown()){
live[index++]=ember;
}
}
embers = Arrays.copyOf(live, index);
}
16.6.调用删除爆炸方法
public void run() {
if(state==RUNNING) {
enterAction();//调用飞行物入场方法
stepAction();//调用飞行物走步方法
shootAction();//调用子弹入场方法
bangAction();//调用子弹与飞行物碰撞的方法
outOfBoundsAction();//调用删除越界飞行物的方法
checkGameOverAction();//检查游戏是否结束
emberAction();//调用删除爆炸方法
}
16.7.飞行物爆炸
让飞行物爆炸找到bang方法
/**
* 检测一个子弹与所有飞行物(大小敌机、小蜜蜂)的碰撞
*/
public void bang(Bullet b){
int index=-1;//记录被撞敌人的下标,不能为0
for (int i = 0; i < flyings.length; i++) {//遍历所有敌人
FlyingObject f=flyings[i];//得到每一个敌人
if(f.shootBy(b)){//判断是否发生碰撞
index=i;//记录被撞敌人的下标
break;//其余的敌人不再做比较
}
}
if(index!=-1){//有被撞的敌人
FlyingObject one=flyings[index];//获取被撞的对象
if(one instanceof Enemy){//判断被撞对象是否是敌人
Enemy e=(Enemy)one;//强制转换
score+=e.getScore();//将敌人的分数得到并加到总分数上
}
if(one instanceof Award){//判断被撞对象是否是奖励
Award a=(Award)one;//强转
int type=a.getType();//获取奖励类型
switch(type){//判断是双倍火力还是加命
case Award.DOUBLE_FIRE:
hero.addDoubleFire();//增加火力值
break;
case Award.LIFE:
hero.addLife();//增加命
break;
}
}
//让被撞敌人消失
FlyingObject f=flyings[index];//取出被撞敌人
//将数组最后一个敌人添加到被撞敌人的位置
flyings[index]=flyings[flyings.length-1];
//将被撞敌人添加到数组的最后一个位置等待数组缩容
flyings[flyings.length-1]=f;
//缩容 会删除数组的最后一个元素
flyings=Arrays.copyOf(flyings,
flyings.length-1);
//飞行物变成灰烬
Ember ember = new Ember(one);
embers = Arrays.copyOf(embers, embers.length+1);
embers[embers.length-1]=ember;
}
}
16.8.删除爆炸物
/**
* 判断游戏是否结束
* @return
*/
public boolean isGameOver() {
for (int i = 0; i < flyings.length; i++) {
FlyingObject f=flyings[i];//获取每一个敌人
if(hero.hit(f)) {//撞上了
hero.reduceLife();//英雄机减命
hero.setDoubleFire(0);//英雄机火力清零
Ember ember = new Ember(hero);
embers = Arrays.copyOf(embers, embers.length+1);
embers[embers.length-1]=ember;
//让被撞敌人消失
//FlyingObject f=flyings[i];//取出被撞敌人
//将数组最后一个敌人添加到被撞敌人的位置
flyings[i]=flyings[flyings.length-1];
//将被撞敌人添加到数组的最后一个位置等待数组缩容
flyings[flyings.length-1]=f;
//缩容 会删除数组的最后一个元素
flyings=Arrays.copyOf(flyings, flyings.length-1);
Ember ember1 = new Ember(f);
embers = Arrays.copyOf(embers, embers.length+1);
embers[embers.length-1]=ember1;
}
}
return hero.getLife()<=0;//英雄机命数小于等于0游戏结束
}