前言
飞机大战是一个非常经典的案例,因为它包含了多种新手需要掌握的概念,是一个非常契合面向对象思想的入门练习案例
程序分析:
在此游戏中共有六个对象:
小敌机Airplane,大敌机BigAirplane,小蜜蜂Bee,天空Sky,英雄机Hero,子弹Bullet
其次我们还需要三个类:
超类Flyer,图片类Images,测试类World
还需:
英雄机2张,小敌机,大敌机,小蜜蜂,子弹,天空各1张,爆炸图4张,游戏开始,暂停,游戏结束各1张,共14张图片放入与图片类Images同包中
超类Flyer:
此类是用来封装所有对象共有的行为及属性的
不管是写什么程序,都建议遵循两点:数据私有化,行为公开化
import java.util.Random;
import java.awt.image.BufferedImage;
public abstract class Flyer {
//所有对象都有三种状态:活着的,死了的,及删除的
//这里之所以选择用常量表示状态是因为首先状态是一个不需要去修改的值
//其次状态需要反复使用所以结合这两个特点,我选择了使用常量表示
//state是用来表示当前状态的,每个对象都有一个实时的状态,此状态是会改变的,且初始状态都是活着的
public static final int LIVE = 0;//活着的
public static final int DEAD = 1;//死了的
public static final int REMOVE = 2;//删除的
protected int state = LIVE;//当前状态(默认状态为活着的)
每个对象都是一张图片,既然是图片那么就一定有宽高,其次因为每个对象都是会随时移动的 即为都有x,y坐标
protected int width;//宽
protected int height;//高
protected int x;//左右移动(x坐标)
protected int y;//上下移动(y坐标)
public abstract void step();
public abstract BufferedImage getImage();
public boolean isLive(){
return state == LIVE;
}
public boolean isDead(){
return state == DEAD;
}
public boolean isRemove(){
return state == REMOVE;
}
public boolean isOutOfBounds(){
return y >= World.HEIGHT;
}
public Flyer(int width,int height){
Random rand = new Random();
this.width = width;
this.height = height;
x = rand.nextInt(World.WIDTH-width);//x:0到负的width长度的之间的随机数
y = -height;//y:负的height高度
}
public Flyer(int width,int height,int x,int y){
this.width = width;
this.height = height;
this.x = x;
this.y = y;
}
public boolean isHit(Flyer other){
int x1 = this.x - other.width;//x1:敌人的x-英雄机/子弹的宽
int x2 = this.x + this.width;//x2:敌人的x加上敌人的宽
int y1 = this.y - other.height;//y1:敌人的y-英雄机/子弹的高
int y2 = this.y + this.height;//y2:敌人的y加上敌人的高
int x = other.x;//x:英雄机/子弹的x
int y = other.y;//y:英雄机/子弹的y
return x>x1 && x<=x2 && y>=y1 && y<=y2;
}
public void goDead(){
state = DEAD;//将当前状态修改为死了的
}
}
图片工具类Images:
此类用来获取每个对象对应的图片
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
public class Images {
// 公开的 静态的 图片数据类型 变量名
public static BufferedImage sky;//天空
public static BufferedImage bullet;//子弹
public static BufferedImage[] heros;//英雄机
public static BufferedImage[] airs;//小敌机
public static BufferedImage[] bairs;//大敌机
public static BufferedImage[] bees;//小蜜蜂
public static BufferedImage start;//启动状态图
public static BufferedImage pause;//暂停状态图
public static BufferedImage gameover;//游戏结束状态图
static {//初始化静态图片
sky = readImage("background01.png");//天空
bullet = readImage("bullet.png");//子弹
heros = new BufferedImage[2];//英雄机图片数组
heros[0] = readImage("hero0.png");//英雄机图片1
heros[1] = readImage("hero1.png");//英雄机图片2
airs = new BufferedImage[5];//小敌机图片数组
bairs = new BufferedImage[5];//大敌机图片数组
bees = new BufferedImage[5];//小蜜蜂图片数组
airs[0] = readImage("airplane.png");//小敌机图片读取
bairs[0] = readImage("bigairplane.png");//大敌机图片读取
bees[0] = readImage("bee01.png");//小蜜蜂图片读取
for (int i=1;i<5;i++){//遍历/迭代赋值
airs[i] = readImage("bom"+i+".png");//小敌机图片数组其余元素赋值爆炸图
bairs[i] = readImage("bom"+i+".png");//大敌机图片数组其余元素赋值爆炸图
bees[i] = readImage("bom"+i+".png");//小蜜蜂图片数组其余元素赋值爆炸图
}
start = readImage("start.png");//启动状态图
pause = readImage("pause.png");//暂停状态图
gameover = readImage("gameover.png");//游戏结束状态图
}
public static BufferedImage readImage(String fileName){
try{
BufferedImage img = ImageIO.read(Flyer.class.getResource(fileName)); //读取与Flyer在同一个包中的图片
return img;
}catch(Exception e){
e.printStackTrace();
throw new RuntimeException();
}
}
}
世界窗口类/测试类 World:
此类用来集合所有类进行排序及具体的操作,和程序的最终运行
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.nio.Buffer;
//定时器
import java.util.Timer;
//定时器任务
import java.util.TimerTask;
//打开随机类
import java.util.Random;
//扩容类
import java.util.Arrays;
public class World 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;//当前状态默认是启动状态
private Sky s = new Sky();//天空对象
private Hero h = new Hero();//英雄机对象
private Flyer[] enemies ={};//敌人对象,分别是大敌机,小敌机,小蜜蜂所以写成了数组
private Bullet[] bt ={};//子弹也是有很多的所以写成了数组
public Flyer nextOne(){
Random rand = new Random();
int type = rand.nextInt(20);//0-19之间的随机数
if (type < 5){//当随机数小于5
return new Bee();//返回小蜜蜂
}else if (type < 13){//当随机数小于13
return new Airplane();//返回小敌机
}else{//大于十三则
return new BigAirplane();//返回大敌机
}
}
private int enterIndex = 0;
public void enterAction() {//每10毫秒走一次
enterIndex++;
if (enterIndex%40 == 0 ){//四百毫秒走一次
Flyer fl = nextOne();//获取敌人对象
enemies = Arrays.copyOf(enemies,enemies.length+1);//扩容(每产生一个敌人数组就扩容1)
enemies[enemies.length-1] = fl;//将生成的敌人fl放置enemies数组的末尾
}
}
int shootIndex = 0;
public void shootAction(){//10毫秒走一次
shootIndex++;
if (shootIndex%30 == 0){//每300毫秒走一次
Bullet[] bs = h.shoot();//获取子弹数组对象
bt = Arrays.copyOf(bt,bt.length+bs.length);//扩容子弹数组(每入场一个子弹就加一个元素)
System.arraycopy(bs,0,bt,bt.length-bs.length,bs.length);//数组的追加
}
}
public void setpAction() {//每10毫秒走一次
s.step();//天空移动
for (int i=0;i<enemies.length;i++) {//遍历所有敌人
enemies[i].step();//敌人移动
}
for (int i=0;i<bt.length;i++){//遍历所有子弹
bt[i].step();//子弹移动
}
}
public void outOfBoundsAction() {//每10毫秒走一次
for (int i=0;i<enemies.length;i++){//遍历所有敌人
if (enemies[i].isOutOfBounds() || enemies[i].isRemove()){
enemies[i] = enemies[enemies.length-1];//最后一个敌人和越界敌人替换
enemies = Arrays.copyOf(enemies,enemies.length-1);//缩容
}
}
for (int i=0;i<bt.length;i++){//迭代所有子弹
if (bt[i].isOutOfBounds() || bt[i].isRemove()){
bt[i] = bt[bt.length-1];//用最后一个子弹替换出界的子弹
bt = Arrays.copyOf(bt,bt.length-1);//缩容
}
}
}
private int score = 0;//玩家的得分
public void bulletBangAction() {//每10毫秒走一次
for (int i=0;i<bt.length;i++){//遍历所有子弹
Bullet b = bt[i];//获取每一个子弹
for (int j=0;j<enemies.length;j++){//迭代每一个敌人
Flyer f = enemies[j];//获取每一个敌人
if (b.isLive() && f.isLive() && f.isHit(b)){//若子弹活着的,敌人活着的,并且两个对象相撞
b.goDead();//子弹当前状态修改为死亡
f.goDead();//敌人当前状态修改为死亡
if (f instanceof EnemyScore) {//判断死亡的敌人类型能否强转为得分接口类型
EnemyScore es = (EnemyScore) f;//将死亡敌人向下造型
score += es.getScore();//调用具体的敌人对象的得分接口的getScore()加分方法
}
if (f instanceof EnemyAward){//判断死亡的敌人类型能否强转为奖励值接口类型
EnemyAward ea = (EnemyAward) f;//将死亡敌人强转为奖励值接口类型
int type = ea.getAwardType();//将具体的奖励值赋值给type
switch (type){
case EnemyAward.FIRE://火力值
h.addFier();//返回增加火力值
break;
case EnemyAward.LIFE://生命值
h.addLife();//返回增加生命值
break;
}
}
}
}
}
}
private void heroBangAction() {//每10毫秒走一次
for (int i=0;i<enemies.length;i++){//迭代所有敌人
Flyer f = enemies[i];//获取每个敌人
if (f.isLive() && h.isLive() && f.isHit(h)){//判断碰撞
f.goDead();//敌人死亡
h.subtractLife();//英雄机减生命值
h.clearFier();//英雄机清空火力值
}
}
}
private void checkGameOverAction() {//每10毫秒走一次
if (h.getLife() <= 0) {//若英雄机生命值为0或小于0
state = GAME_OVER;//将状态修改为GAME_OVER游戏结束状态
}
}
public void action() {//测试代码
MouseAdapter m = new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
if (state == RUNNING){//仅在运行状态下执行
int x = e.getX();//获取鼠标的x坐标
int y = e.getY();//获取鼠标的y坐标
h.moveTo(x,y);//接收鼠标具体坐标
}
}
public void mouseClicked(MouseEvent e){
switch (state){//根据当前状态做不同的处理
case START://启动状态时
state = RUNNING;//鼠标点击后改成运行状态
break;
case GAME_OVER://游戏结束状态时
score = 0;//总分归零
s = new Sky();//天空初始化所有属性
h = new Hero();//英雄机初始化所有属性
enemies = new Flyer[0];//敌人初始化所有属性
bt = new Bullet[0];//子弹初始化所有属性
state = START;//鼠标点击后修改为启动状态
break;
}
}
public void mouseExited(MouseEvent e){
if (state == RUNNING){//若状态为运行
state = PAUSE;//则将当前状态修改为暂停
}
}
public void mouseEntered(MouseEvent e){
if (state == PAUSE){//若当前状态为暂停
state = RUNNING;//则将当前状态修改为运行
}
}
};
this.addMouseListener(m);
this.addMouseMotionListener(m);
Timer timer = new Timer();//定时器对象
int interval = 10;//定时的间隔(此间隔是以毫秒为单位)
timer.schedule(new TimerTask() {
@Override
public void run() {//定时干的事(每10毫秒自动执行此方法当中的所有方法)
if (state == RUNNING){//只在运行状态下执行
enterAction();//敌人(大敌机,小敌机,小蜜蜂)入场
shootAction();//子弹入场
setpAction();//飞行物移动
outOfBoundsAction();//删除越界的敌人
bulletBangAction();//子弹与敌人的碰撞
heroBangAction();//英雄机与敌人的碰撞
checkGameOverAction();//检测游戏结束
}
repaint();//重新调用paint()方法(重画)
}
}, interval, interval);//定时计划表
}
public void paint(Graphics g){//每10毫秒走一次
g.drawImage(s.getImage(), s.x, s.y, null);//画天空
g.drawImage(s.getImage(), s.x, s.getY1(), null);//画第二张天空
g.drawImage(h.getImage(),h.x,h.y,null);//画英雄机
for (int i=0;i<enemies.length;i++){//遍历所有敌人
Flyer f = enemies[i];//获取每一个敌人
g.drawImage(f.getImage(),f.x,f.y,null);//画敌人
}
for (int i = 0; i<bt.length; i++){//遍历所有子弹
Bullet b = bt[i];//获取所有子弹
g.drawImage(b.getImage(),b.x,b.y,null);//画子弹
}
g.drawString("SCORE:"+score,10,25);//在窗口右上角画分数
g.drawString("HP:"+h.getLife(),10,45);//在窗口右上角画出英雄机的生命值
switch (state){//画状态图
case START:
g.drawImage(Images.start,0,0,null);//启动状态图
break;
case PAUSE:
g.drawImage(Images.pause,0,0,null);//暂停图
break;
case GAME_OVER:
g.drawImage(Images.gameover,0,0,null);//游戏结束图
break;
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
World world = new World();
frame.add(world);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(WIDTH,HEIGHT);
frame.setLocationRelativeTo(null);
frame.setVisible(true);//1)设置窗口可见 2)尽快调用paint()方法
world.action();//启动程序的执行
}
}
小敌机类Airplane:
此类存储小敌机特有的属性及行为:
移动速度,分值,及图片的切换
继承超类,且实现得分接口
package cn.tedu.shoot;
import java.awt.image.BufferedImage;
public class Airplane extends Flyer implements EnemyScore{
// 移动速度
private int speed;
public Airplane(){
super(66,89);
speed = 2;//小敌机的下落速度
}
public void step(){
y += speed;//y+表示向下
}
int index = 1;
public BufferedImage getImage() {
if (isLive()){//若活着 则返回airs[0]图片
return Images.airs[0];
}else if (isDead()){//若死了 则返回airs[1~4]图片
BufferedImage img = Images.airs[index++];//获取爆破图
if (index == Images.airs.length){//若index到了5 则表示到了最后一张
state = REMOVE;//将当前状态修改为REMOVE删除的
}
return img;//返回爆炸图
}
return null;
}
public int getScore(){
return 1;
}
}
大敌机类BigAirplane:
大敌机与小敌机几乎无差别
同样要继承超类,且实现得分接口
package cn.tedu.shoot;
import java.awt.image.BufferedImage;
import java.util.Random;
public class BigAirplane extends Flyer implements EnemyScore{
// 移动速度
private int speed;
public BigAirplane(){//初始化默认属性
super(203,211);//图片宽,高
speed = 2;//移动速度
}
public void step(){
y += speed;//y+表示直线向下移动
}
int index = 1;
@Override
public BufferedImage getImage() {
if (isLive()){//若活着 则返回airs[0]图片
return Images.bairs[0];
}else if (isDead()){//若死了 则返回airs[1~4]图片
BufferedImage img = Images.bairs[index++];//获取爆破图
if (index == Images.bairs.length){//若index到了5 则表示到了最后一张
state = REMOVE;//将当前状态修改为REMOVE删除的
}
return img;
}
return null;
}
public int getScore(){
return 3;
}
}
小蜜蜂类Bee:
此类虽也可以算作敌人类,但是与小/大敌机有所不同,它是实现奖励值接口
package cn.tedu.shoot;
import java.awt.image.BufferedImage;
import java.util.Random;
public class Bee extends Flyer implements EnemyAward{
// x坐标移动速度,y坐标移动速度,
private int xSpeed;//x坐标移动速度
private int ySpeed;//y坐标移动速度
private int awardType;//奖励类型
public Bee(){//初始化属性
super(48,50);//图片宽,高
Random rand = new Random();
awardType = rand.nextInt(2);//随机奖励值类型0~2之间(不包括2)0表示火力值,1表示生命值
xSpeed = 1;//平行移动
ySpeed = 2;//垂直移动
}
public void step() {
y += ySpeed;//y+:向下移动
x += xSpeed;//x+:随机向左或是向右移动
if (x <= 0 || x >= World.WIDTH - width) {
xSpeed *= -1;//到达边界后反方向移动(正负为负,负负为正)
}
}
int index = 1;
public BufferedImage getImage() {
if (isLive()){//若活着 则返回airs[0]图片
return Images.bees[0];//返回小蜜蜂图
}else if (isDead()){//若死了 则返回airs[1~4]图片
BufferedImage img = Images.bees[index++];//获取爆破图
if (index == Images.bees.length){//若index到了5 则表示到了最后一张
state = REMOVE;//将当前状态修改为REMOVE删除的
}
return img;//返回爆炸图
}
return null;
}
public int getAwardType(){
return awardType;//返回奖励类型
}
}
天空类Sky:
这里有一点需要强调,就是为了实现天空图片向下移动后会出现移动过的位置出现图片丢失的情况,就使用了两张图上下拼接起来,当第某张天空图完全移出窗口的时候会让它重新出现在窗口上方继续向下移动
package cn.tedu.shoot;
import java.awt.image.BufferedImage;
public class Sky extends Flyer{
// 移动速度,y1
private int y1;//第二张图片的y坐标
private int speed;//移动速度
public Sky(){//设置初始值(默认值)
//此处的宽高用常量是因为天空的宽高和窗口是一致的,x轴和y轴为若不为0就和窗口不匹配了
super(World.WIDTH,World.HEIGHT,0,0);//初始化图片坐标及宽,高
speed = 1;//初始化移动速度
y1 = -World.HEIGHT;//第二张图片设置在第一张图片上方
}
public void step(){
y += speed;//第一张图向下移动
y1 += speed;//第二张图向下移动
if (y >= World.HEIGHT){//若y>=窗口的高
y = -World.HEIGHT;//将移动出去的第一张天空挪到窗口上方
}
if (y1 >= World.HEIGHT){//若第二张天空挪出窗口
y1 = -World.HEIGHT;//将第二张天空挪到窗口上方
}
}
@Override
public BufferedImage getImage() {//10毫秒走一次
return Images.sky;//返回天空图片即可
}
public int getY1(){
return y1;//返回y1
}
}
英雄机类Hero:
package cn.tedu.shoot;
import java.awt.image.BufferedImage;
public class Hero extends Flyer {
// 命数,火力值
private int life;//命数
private int fire;//火力
public Hero() {
super(97,139,140,400);//宽,高,及初始坐标
fire = 0;//初始火力值 0:单倍火力
life = 3;//初始生命值
}
public void step(){//每10毫秒走一次
//因为英雄机是跟随鼠标移动的,而鼠标是在窗口上的所以这里就没有写具体的方法,而是在窗口类中去用鼠标的具体坐标计算出英雄机的移动位置
}
int index = 0;//下标
@Override
public BufferedImage getImage() {//每10毫秒走一次
return Images.heros[index++ % Images.heros.length];//heros[0]和heros[1]来回切换
}
public Bullet[] shoot(){
int xStep = this.width/4;//子弹x坐标
int yStep = 5;//子弹y坐标
System.out.println(this.x+"\t"+this.y);
if (fire>0){//双倍火力
Bullet[] bs = new Bullet[3];//2发子弹
bs[0] = new Bullet(this.x+1*xStep,this.y-yStep);//子弹坐标1
bs[1] = new Bullet(this.x+3*xStep,this.y-yStep);//子弹坐标2
bs[2] = new Bullet(this.x+2*xStep,this.y-yStep);
fire -= 2;//发射一次双倍活力,则火力值-2
return bs;
} else {//单倍火力
Bullet[] bs = new Bullet[1];//1发子弹
bs[0] = new Bullet(this.x+2*xStep,this.y-yStep);//x:英雄机的x+2/4英雄机的宽,y:英雄机的y-
return bs;
}
}
public void moveTo(int x,int y){//形参列表:鼠标的x坐标,y坐标
this.x = x - this.width/2;//英雄机的x = 鼠标的x减1/2英雄机的宽
this.y = y - this.height/2;//英雄机的y = 鼠标的y减1/2英雄机的高
}
public void addLife(){
life++;//生命值+1
}
public int getLife(){
return life;//返回生命值
}
public void subtractLife(){
life--;//生命值减1
}
public void addFier(){
fire += 40;//火力值+40
}
public void clearFier(){
fire = 0;//火力值归零
}
}
子弹类Bullet:
package cn.tedu.shoot;
import java.awt.image.BufferedImage;
public class Bullet extends Flyer {
// 移动速度
private int speed;
public Bullet(int x,int y) {//子弹有多个,每个子弹的初始坐标都不同,所以要写活
super(8,20,x,y);
speed = 3;//初始移动速度
}
public void step(){
y -= speed;//y-:表示直线向上移动
}
@Override
public BufferedImage getImage() {//10毫秒走一次
if (isLive()){//若活着则返回bullet图片
return Images.bullet;
}else if (isDead()){//若死了则将state修改为REMOVE
state = REMOVE;
}
return null;//死了的和删除的都返回null空值
}
public boolean isOutOfBounds(){
return y <= -height;若子弹的y轴坐标小于自己的高则说明移动到了窗口外部
}
}
奖励值接口 EnemyAward:
package cn.tedu.shoot;
public interface EnemyAward {
public int FIRE = 0;//火力
public int LIFE = 1;//生命值
int getAwardType();
}
得分接口 EnemyScore:
package cn.tedu.shoot;
public interface EnemyScore {
public int getScore();
}
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程网。