飞机大战的思路
飞机大战中该有的东西
1、我们控制的飞机(以下简称主机)名为Hero
2、敌机 小蜜蜂(统一称为其他飞行物) 名为Airplane || Bee
3、游戏有关的图片(背景图、飞机图、状态图)
以下是我们控制的飞机的实现代码,需要实现以下的方法:
1、一个无参的Hero方法用来创建Hero飞机
2、我们控制的飞机要有喷射尾焰的效果,用step方法一定的时间里连续切换图片,形成所需效果
3、是否出界outOfBounds方法(离开我们创建的界面),不过我们用鼠标来控制主机,不存在出界问题,故返回值是false
4、 获取移动的方法moveto方法,不过需要进行微调,因为我们一般是控制主机的中心位置,而这默认位置是左上角
5、一些游戏的机制
5.1、加血 addLife方法
5.2、扣血 subtraLife方法
5.3、获取血量值 getLIfe方法
5.4、增加火力 addFire方法
5.5、减少火力 clearFire方法
6、子弹,以一个数组来存放
7、是否与其他飞行物碰撞 collide方法,可以有以下两种方式
7.1、 以主机为中心
7.2、 以其他飞行物为中心(推荐大家可以去思考下如何实现,可以发在评论中交流)
package com.game;
import java.awt.image.BufferedImage;
/**
* @auother lungcen
*/
/**
* 主机的一些属性
*/
public class Hero extends FlyObject//主机的类
{
private int doubleFire;//火力值
private int life;//生命值
private BufferedImage[] images;//图片数组
private int index;//辅助图片进行切换
/**
* 主机的类方法
*/
public Hero()
{
image = ShootGame.hero0;//飞机的图片属性
width = image.getWidth();
height = image.getHeight();
x = 150;//一开始的位置
y = 310;
//两张图片存进数组中
images = new BufferedImage[] {ShootGame.hero0, ShootGame.hero1};
index = 0;
life = 3;//初始生命值
}
@Override
public void step()//时间间隔为10ms,让主机动起来
{
image = images[index++/10%images.length];
/*index++;
int a = index/10;
int b = a%2;
image = images[b];
10ms index = 0 a = 0 b = 0
20ms index = 1 a = 0 b = 0
30ms index = 2 a = 0 b = 0
nms index = 10 a = 1 b = 1
b 最终的结果 取余 不是0 就是1
*/
}
@Override
public boolean outOfBounds()//判断是否出界,由于主机不会出界,则返回值为false
{
return false;
}
public void moveTo(int x, int y)//获取鼠标移动的位置
{//是鼠标的位置在图片的中心
this.x = x - this.width/2;//调整鼠标的位置
this.y = y - this.height/2;
}
public void addLife()//游戏机制
{
life++;
}
public void subtractLife()//游戏机制
{
life--;
}
public int getLife()//游戏机制
{
return life;
}
public void addFire()//游戏机制
{
doubleFire += 20;
}
public void clearFire()//游戏机制
{
doubleFire = 0;
}
public Bullet[] shoot()//子弹数组
{
int xStep = this.width/4;//主机中间发射子弹
int yStep = 20;//从机管中射出,而不是从头射出
if (doubleFire>0)//双倍
{
Bullet[] bullets = new Bullet[2];
bullets[0] = new Bullet(this.x+1*xStep,this.y-yStep);//两个开火口
bullets[1] = new Bullet(this.x+3*xStep,this.y-yStep);
return bullets;
}
else//单倍
{
Bullet[] bullets = new Bullet[1];
bullets[0] = new Bullet(this.x+2*xStep,this.y-yStep);//一个开火口
return bullets;
}
}
public boolean collide(FlyObject flyObject)//判断是否碰撞,以主机为主体
{
int x1 = flyObject.x - this.width/2;//确定敌机的碰撞范围
int x2 = flyObject.x + flyObject.width +this.width/2;
int y1 = flyObject.y - this.height/2;
int y2 = flyObject.y + flyObject.height + this.height/2;
int x = this.x + this.width/2;//确定主机的位置
int y = this.y + this.height/2;
return x >= x1 && x <= x2 && y >= y1 && y <= y2;//当主机在碰撞范围,则发生碰撞
}
}
我们控制的主机写完了,接下来我们控制不了的写其他飞行物,需要有以下基本的方法(当然也可以加点其他的方法):
1、打中可以获得分数,于是就给飞机添加一个接口(获得分数的接口)Enemy接口 注:为啥要用接口勒,因为那时候我们正在学接口,不用接口也是可以的
2、和主机类似,一个无参的Airplane方法来创建敌机
3、敌机的飞行方式step方法
4、敌机出界的方法outOfBounds方法,这个方法还是很重要的,如果越界后不清掉,那就会一直堆在下面,浪费空间
5、打中敌机的分数getScore方法(从Enemy中重写的)
package com.game;
public interface Enemy//得分机制
{
int getScore();
}
package com.game;
import java.util.Random;
/**
* @auother lungcen
*/
public class Airplane extends FlyObject implements Enemy//敌机对象
{
private int step = 2;//移动速度
public Airplane()//敌机的属性
{
image = ShootGame.airplane;//敌机的图片属性
width = image.getWidth();
height = image.getHeight();
Random rand = new Random();//随机数
x = rand.nextInt(ShootGame.WIDTH-this.width);//随机的范围
y = -2/4*this.height;//形成一种由外往里的效果
}
public void step()//飞机的移动方式
{
y= y + 2*step;
}
public boolean outOfBounds()//判断是否出界
{
return this.y >ShootGame.HEIGHT-1.5*this.height;
}
@Override
public int getScore()//打中敌机的分数
{
return 2;
}
}
主机有了,敌机也有了,还差点啥勒?有分数机制,还需要奖励机制。奖励机制用蜜蜂来承载:
1、奖励机制提取出来,成为一个接口 Award接口
2、蜜蜂与敌机都是飞行物,都一个对应的无参函数来创建,蜜蜂以Bee方法来创建蜜蜂
3、setp方法来控制蜜蜂的移动
4、outOfBounds方法来判断是否出界
5、获取奖励的方法getType方法,蜜蜂类独有的
package com.game;
public interface Award//奖励机制
{
int getType();
}
package com.game;
import java.util.Random;
/**
* @auother lungcen(刘金成)
*/
public class Bee extends FlyObject implements Award{//蜂蜜类
private int xSpeed = 10;//蜜蜂移动方向的速度
private int ySpeed = 3;
private int AwardType;//0表示双倍火力,1表示加生命值
public Bee()//蜜蜂类的属性和位置
{
image = ShootGame.bee;//蜂蜜的图片属性
width = image.getWidth();
height = image.getHeight();
Random rand = new Random();//生成随机数
AwardType = rand.nextInt(2);//随机数字(0~1)
x = rand.nextInt(ShootGame.WIDTH-this.width);//随机出现在范围位置中
y = -this.height;//形成从窗口外到窗口的效果
}
@Override
public void step()//蜜蜂移动
{
x+=xSpeed;//移动的速度
y+=ySpeed;
if (x>=ShootGame.WIDTH-1.4*this.width)//到右边界就反起来移动
{
xSpeed = -10;
}
if (x<=0)//到左边界就反起来移动
{
xSpeed = 10;
}
}
public boolean outOfBounds()//判断出界
{
return this.y>ShootGame.HEIGHT - this.height;//出界的范围
}
@Override
public int getType()//获得奖励的效果
{
return AwardType;//返回奖励的种类
}
}
有主机、敌机、蜜蜂,三者都是飞行物,总不能写飞机代码的时候就定义一下那些基本的属性,然后写敌机的时候也再写一遍,写蜜蜂是又写一遍,这就显得冗余了。于是就抽象出一个飞机类Flyobject抽象类
package com.game;
import java.awt.image.BufferedImage;
/**
* @auother lungcen
*/
/**
* @aouther lungcen
*/
public abstract class FlyObject//一个抽象类
{
protected BufferedImage image;//获取图片
protected int width;//图片的属性
protected int height;
protected int x;//坐标的位置
protected int y;
public abstract void step(); //移动方式
public abstract boolean outOfBounds(); //越界的问题
/**
*
* @param bullet 传递子弹是否命中飞行物
*/
public boolean shootBy(Bullet bullet)
{
int x1 = this.x;//获取飞行物的撞击范围
int y1 = this.y;
int x2 = this.x + this.width;
int y2 = this.y + this.height;
int x = bullet.x;//确定子弹的位置
int y = bullet.y;
return (x >= x1 && x <= x2) && (y >= y1 && y <= y2);//当子弹在撞击范围中则发生碰撞
}
}
接下的就是飞机发射的子弹Bullet:
1、一个对应的无参函数来创建,子弹以Bullet方法来创建子弹1、一个有参的方法来创建子弹,需要传入位置
注 :可能你会好奇,为啥主机和其他飞行物不需要参数,因为主机的移动是由moveTo来传入参数的,蜜蜂是由setp来移动的,而子弹需要根据主机的位置来出现,所以需要传入主机的位置来创建子弹的位置
2、子弹出现后的移动step方法
3、子弹出界问题outOfBounds方法
package com.game;
/**
* @auother lungcen
*/
public class Bullet extends FlyObject//子弹类
{
private int speed = 3;//控制子弹的移动速度
/**
* 获取子弹的位置
* @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;
}
public void step()
{
y-=2*speed;//子弹移动
}
public boolean outOfBounds()//出界的处理
{
return this.y<ShootGame.HEIGHT;//确定出界的范围
}
}
重头戏来了,我们有了飞机、敌机、蜜蜂、子弹这些,还剩把这些放在一起运行的界面,有以下功能:
1、生成一个界面
2、将图片存储到缓存区
3、游戏状态的值
4、处理异常
5、生成敌机、蜜蜂、子弹
6、使飞行物移动(调用飞行的方法)
7、检测碰撞(子弹与敌机,主机与其他飞行物)
8、消除越界的飞机
9、游戏的三个状态(结束、暂停、运行)
10、action——游戏开始的运行器
11、画飞机、其他飞行物、子弹、背景、状态背景
12、mian主函数入口
package com.game;
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.Random;
import java.util.Timer;
import java.util.TimerTask;
/**
* @auother lungcen
*/
public class ShootGame extends JPanel//继承JPanel
{
/*
public static final int WIDTH = 497; //游戏背景的宽和高
public static final int HEIGHT = 899;
*/
public static final int WIDTH = 420; //界面的宽和高,后面出界的判断根据这个数值来进行判断
public static final int HEIGHT = 700;
public static BufferedImage background;//设置图片的容器,将图片加载到内存的缓存区
public static BufferedImage start;//设置图片的容器,将图片加载到内存的缓存区
public static BufferedImage pause;//设置图片的容器,将图片加载到内存的缓存区
public static BufferedImage gameover;//设置图片的容器,将图片加载到内存的缓存区
public static BufferedImage bee;//设置图片的容器,将图片加载到内存的缓存区
public static BufferedImage bullet;//设置图片的容器,将图片加载到内存的缓存区
public static BufferedImage hero0;//设置图片的容器,将图片加载到内存的缓存区
public static BufferedImage hero1;//设置图片的容器,将图片加载到内存的缓存区
public static BufferedImage airplane;//设置图片的容器,将图片加载到内存的缓存区
//定义游戏的状态,游戏状态中定义的一些值
public static final int START = 0;//初始值
public static final int RUNNING = 1;//运行的值
public static final int PAUSE = 2;//暂停的值
public static final int GAMEOVER = 3;//结束的值
private int state = START;//默认的游戏状态,一开始的初值
static {//这个,,我也不知道是啥
try {//图片的路径
background = ImageIO.read(ShootGame.class.getResource("/com/images/background.png"));
start = ImageIO.read(ShootGame.class.getResource("/com/images/start.png"));
pause = ImageIO.read(ShootGame.class.getResource("/com/images/pause.png"));
gameover = ImageIO.read(ShootGame.class.getResource("/com/images/gameover.png"));
bee = ImageIO.read(ShootGame.class.getResource("/com/images/bee.png"));
bullet = ImageIO.read(ShootGame.class.getResource("/com/images/bullet.png"));
hero1 = ImageIO.read(ShootGame.class.getResource("/com/images/hero1.png"));
hero0 = ImageIO.read(ShootGame.class.getResource("/com/images/hero0.png"));
airplane = ImageIO.read(ShootGame.class.getResource("/com/images/airplane.png"));
} catch (Exception e) {
e.printStackTrace();
}
}
private Hero hero = new Hero();//生成主机
private FlyObject[] flys = {};//存放父类飞行器的数组
private Bullet[] bullets = {};//存放子弹的数组
/**
* 随机生成敌机
* @return
*/
public FlyObject nextOne()//随机生成其他飞行物
{
Random random = new Random();//生成随机数,按照随机数来生成敌机
int type = random.nextInt(20);//type的范围是(0~19)
if (type<3)//随机数小于3是生成蜜蜂
{
return new Bee();//出现蜜蜂
}
else//反之则是飞机
{
return new Airplane();//出现敌机
}
}
/**
* 令飞行物可以动起来
*/
public void stepAction()//使飞行物动起来
{
hero.step();//运动的频率10ms,让主机的两张图交换出现,形成尾气的效果
for (int i = 0; i < flys.length; i++)
{
flys[i].step();//使每一个飞行物都动起来
}
for (Bullet b:bullets)
{
b.step();//使子弹也动起来
}
}
/**
* 生成速度和存放空间
*/
int flyIndex = 0, x=0, y=40;//计数
public void enterAction()//在一定的时间内产生一组敌人
{
flyIndex++;
if (flyIndex% y == 0)//产生的频率,每间隔40秒产生一个其他飞行物
{
x = x + 2; //使敌机的数量随着时间增加
if (x%12==0 && y!=6)
--y; //每过一下,y值减少,飞机的速度也对应变快起来
FlyObject obj = nextOne();//生成其他飞行物
flys = Arrays.copyOf(flys,flys.length+1);//给数组扩容,长度加一
flys[flys.length-1] = obj;//新产生的飞行物放在最后面的位置
}
}
/**
* 子弹
*/
int bulletIndex = 0;
public void enterBullet()//子弹入场
{
bulletIndex++;
if (bulletIndex%15 == 0)//控制子弹发射的频率
{
Bullet[] bull = hero.shoot();//一个子弹数组,存放子弹
bullets = Arrays.copyOf(bullets,bullets.length+bull.length);//扩大数组容量
System.arraycopy(bull,0,bullets,bullets.length-bull.length,bull.length);//扩容后的数组进行填充内容
}
}
/**
* 检测主机和敌机的碰撞
*/
public void CollideBoth()
{
for (int i = 0; i < flys.length; i++) //检测敌机与主机是否碰撞
{
if (hero.collide(flys[i]))//判断是否碰撞
{
hero.subtractLife();//减少生命
hero.clearFire();//取消双倍火力
//碰撞后也要消除敌机,不然会一直撞
FlyObject deFly = flys[i];//临时记录被撞
flys[i] = flys[flys.length -1];//将最后一个放到现在的位置
flys[flys.length -1] = deFly;//将被撞的放到最后面
flys = Arrays.copyOf(flys,flys.length-1);//缩小数组的容量,删去最后一个
}
}
}
/**
* 碰撞的方法,主体是子弹,检测子弹是否碰撞敌人
*/
int score = 0;
public int beng (Bullet bullet)
{
int index = -1;//利用index的值判断
for (int i = 0; i < flys.length; i++)
{
if (flys[i].shootBy(bullet))
{
index = i;//将碰撞的子弹的值传给index
break;
}
/**
* 如何消除击中的子弹
*/
}
if (index != -1)//发生了碰撞
{
FlyObject one = flys[index];//获取被子弹击中的飞行物
if (one instanceof Enemy)//判断类型
{
score += ((Enemy) one).getScore();//增加分数
}
if (one instanceof Award)//判断类型
{
int type = ((Award) one).getType();//判断类型
switch (type)//进一步分析类型
{
case 1:
hero.addLife();//增加生命
break;
case 0:
hero.addFire();//增加火力
break;
}
}
//消除被击中的飞行物
FlyObject deFly = flys[index];//临时记录被撞
flys[index] = flys[flys.length -1];//将最后一个放到现在的位置
flys[flys.length -1] = deFly;//将被撞的放到最后面
flys = Arrays.copyOf(flys,flys.length-1);//缩小数组的容量,删去最后一个
return 1;//如果击中就返回1
}
return 0;//如果没有击中就返回0
}
/**
* 判断是否发生了碰撞,同时消除碰撞的子弹
*/
public void bengAction()//隔一定的时间就检测一遍是否碰撞
{
for (int i = 0; i < bullets.length; i++)
if (beng(bullets[i])==1)//如果子弹发生碰撞,则消除掉
{
Bullet deFly = bullets[i];//临时记录被撞
bullets[i] = bullets[bullets.length -1];//将最后一个放到现在的位置
bullets[bullets.length -1] = deFly;//将被撞的放到最后面
bullets = Arrays.copyOf(bullets,bullets.length-1);//缩小数组的容量,删去最后一个
}
}
/**
* 删除越界的飞机
* 需要消除的有 向下飞 的 敌机 和 小蜜蜂
*/
int die = 0;//设置难度1,如越界的飞机大于5,就游戏结束
private void outOfBoundsAction()//消除越界的飞机
{
int index = 0;//不越界的敌人数组的下标,还在屏幕内的敌人
FlyObject[] flyLive = new FlyObject[flys.length];//存放还未越界的敌人对象
for (int i =0; i<flys.length; i++)//遍历数组
{
if (!flys[i].outOfBounds())//判断是否出界
{
flyLive[index] = flys[i];//将未出界放在flyLive中
index++;
}
else
{
die++;//记录越界的飞机数
}
}
flys = Arrays.copyOf(flyLive, index);//将不越界的敌人复制到flys中,个数就是不越界敌人的个数
}
/**
* 游戏结束的值
*/
public void checkedGameOver()
{
if(hero.getLife() == 0 || die == 5)//生命值为0结束 或者 错过的飞机数为5
{
state = GAMEOVER;//结束
}
}
/**
* 开始
*/
public void action()
{
MouseAdapter mouse = new MouseAdapter()//创建鼠标监视器
{
@Override
public void mouseMoved(MouseEvent e)//获取鼠标位置
{
if (state == RUNNING)//程序是运行状态时获取鼠标位置
{
int x = e.getX();//获取位置
int y = e.getY();
hero.moveTo(x, y);//将获取的位置传入主机中
}
}
@Override
public void mouseClicked(MouseEvent e)//判断程序的状态
{
switch (state)
{
case START://处于准备时点击转换为运行
state = RUNNING;
break;
case RUNNING://处于运行时点击转换为暂停
state = PAUSE;
break;
case PAUSE://处于暂停时点击转换为运行
state = RUNNING;
break;
case GAMEOVER://处于结束时点击就数据清零
score =0;
state = START;
hero = new Hero();
flys = new FlyObject[0];
bullets = new Bullet[0];
break;
}
}
@Override
public void mouseExited(MouseEvent e)//鼠标移出事件
{
if (state == RUNNING)//处于运行时鼠标移出界面就转换为暂停
{
state = PAUSE;
}
}
@Override
public void mouseEntered(MouseEvent e)//鼠标移入事件
{
if (state == PAUSE)//处于暂停时鼠标移入界面就转换为运行
{
state = RUNNING;
}
}
};
this.addMouseMotionListener(mouse);//处理鼠标的点击的操作事件——移动监听
this.addMouseListener(mouse);//处理鼠标的点击的操作事件——点击、双击、移入、移出等
Timer timer = new Timer();//一个定时器,一直在不停的更改
int interval = 10;//时间间隔是10ms
timer.schedule(new TimerTask()
{
@Override
public void run()//在定时器中运行
{
if (state == RUNNING)//处于运行状态时开始
{
enterAction();//产生敌机
stepAction();//进行移动
repaint();//重新画一下界面
enterBullet();//子弹
bengAction();//计数分数
CollideBoth();//飞机与敌机碰撞
checkedGameOver();//是否结束
}
outOfBoundsAction();//消除越界其他飞行物
repaint();//据说是重写,但是不知道重写了个啥,没有测试出来,网上也没有找到
}
}, interval, interval);//第一启动延迟interval毫秒,之后每隔interval毫秒 执行一次
}
/**
* //画笔的工具
* @param g
*/
public void paint(Graphics g)
{
paintBackground(g);//画背景
paintHero(g);//画主机
paintAirplane(g);//画其他飞行物
paintBullet(g);//画子弹
paintScore(g);//画分数
paintState(g);//画状态
}
public void paintAirplane(Graphics g)//画飞行物
{
for (int i = 0; i < flys.length; i++)
{
FlyObject a = flys[i];//根据数组来画飞行物
g.drawImage(a.image,a.x,a.y,null);
}
}
public void paintBullet(Graphics g)//画子弹
{
for (Bullet b:bullets)
{
g.drawImage(b.image,b.x,b.y,null);//observer - 当缩放并转换了更多图像时要通知的对象
}
}
public void paintHero(Graphics g)//画主机
{
g.drawImage(hero.image, hero.x, hero.y, null);
}
public void paintBackground(Graphics g)//画背景
{
g.drawImage(ShootGame.background, 0, 0, null);
}
public void paintScore(Graphics g)//画分数
{
g.setColor(new Color(0x3A8DFF));//字体颜色
g.setFont(new Font(Font.SANS_SERIF,Font.BOLD,24));//字体属性
g.drawString("SCORE:"+score,10,25);//画的位置
g.setColor(new Color(0xFFD238));//字体颜色
g.drawString("LIFE:"+hero.getLife(),10,45);//画的位置
}
/**
* 画每一个状态
* @param g 画笔的参数
*/
public void paintState(Graphics g)//画状态
{
switch (state)
{
case START://准备状态
g.drawImage(start, 0,0,null);
g.setColor(new Color(0xFF1900));//字体颜色
g.drawString("难度1:每隔10秒飞机数量变多" ,20,76);//画的位置
g.drawString("难度2:错过飞机数大于5则结束" ,20,106);//画的位置
break;
case PAUSE://暂停状态
g.drawImage(pause, 0,0,null);
g.setColor(new Color(0x36FF00));//字体颜色
g.drawString("真男人就要接着战斗!!!" ,80,200);//画的位置
break;
case GAMEOVER://结束状态
g.drawImage(gameover, 0,0,null);
g.setColor(new Color(0xFF1900));//字体颜色
g.drawString("失败是什么?这可是成功之母^_^" ,25,200);//画的位置
break;
}
}
/**
* 主函数
* @param args
*/
public static void main(String[] args)//程序入口main
{
JFrame jFrame = new JFrame("飞机大战");//外框、主题
ShootGame sG = new ShootGame();//创建面板
jFrame.add(sG);//添加组件到容器JFrame里面
jFrame.setSize(WIDTH, HEIGHT);//设置界面的大小
jFrame.setAlwaysOnTop(true);//悬停在界面最上面
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//点X可以直接关闭程序
jFrame.setLocationRelativeTo(null);//居中显示界面
jFrame.setVisible(true);//设置界面是否显示
sG.action();//界面开始运动起来
}
}