亲爱的同学们,大家好。今天我要分享一个java的小项目——飞机大战,编写过程中要用到我们最熟悉的eclipse来编写一个窗口程序,实现这个飞机大战的游戏。以下是游戏界面截图:
分享前,要声明几点内容:
一、本游戏源码非原创,只是本人通过视频学习的方式一点一点的编写而成,所以在网上还是可以搜索到相关的源码的。
二、源码的内容已经做好相关详细注释,这些注释是个人编写程序中自己的思考和理解,希望这些注释能够帮助大家看懂。
三、因为时间有限,部分功能还没有完善,例如英雄飞机与敌机的碰撞,具体的得分接口等等细节性功能还未写好,但是主要的功能已经编写完成,可以用作正常的使用,暂且称为飞机大战v1.0
好了,讲完了这些,那么接下来让我们进入正题吧。
我们知道,java这种程序语言是以面向对象这种思想来设计程序的,所以我们要讲的飞机大战也是以面向对象的思想来编写的。
在飞机大战游戏中,我们主要设计的对象有6个,分别为大敌机、小敌机、小蜜蜂、子弹、天空、以及玩家掌握的英雄机。所以我们需要分别创建6个类(Airplane、BigAirplane、Bee、Sky、Bullet、Hero),以及创建一个放共有属性的超类FlyingObject、放主程序的World(带main)
程序某个功能的设计思想:
1) 先写行为:
a) 对象所特有的行为,设计在对象类中
b) 所有对象特有的行为,设计在超类中
2) 窗口调用:
a) 定时触发的,设计在定时器中调用
b) 事件触发的,设计在侦听器中调用
整体步骤:
一).创建6个对象类,并创建World类测试
二).给6个对象添加构造方法,并测试
三).设计数组对象:
a)设计小敌机数组,大敌机数组,小蜜蜂数组,子弹数组,并测试
b)设计FlyingObject超类,并给6个对象继承
c)给FlyingObject超类设计两个构造方法,6个对象分别调用
四).向上造型数组(多态)、重写step、画窗口
a)将小敌机数组大敌机数组小蜜蜂数组向上造型为Flyingob数组
b)在6个对象类中重写step()
c)画窗口//无需掌握
.
五).遵循一个原则:数据私有,行为公开,继承用protected
a)给类中成员添加访问控制修饰符,数据私有行为公开
b)给6个对象类添加图片属性
六).设定主窗口为常量宽高,给对象获取图片
a). 在world主窗口中定义WIDTH和HEIGHT的常量
b).将FlyingObject的step方法作为抽象方法,同时将6个类的step重写
c)画对象
1)想画对象需要获取对象的图片,每个对象都获取图片
意味着获取图片为共有行为,所以设计在超类中
每个对象获取图片的行为不同(有的一张,两张,五张等等),所以设计为抽象方法
————-在FlyingObject中设计抽象方法getImage()获取对象的图片
2)
获取图片时需要去考虑对象的状态,因为不同状态下获取的图片是不一样的。
对象状态分:活着的、死了的、 删除的
每个对象都有状态,所以讲状态设计到超类中,一般为常量——–FLyingObject中设计LIFE/DEAD/REMOVE常量,state表示当前状态
获取图片时需要去判断对象的状态,而每个对象都能判断状态
所以判断状态为共有行为,设计在FLyingObject中
每个对象判断状态的行为都一样,所以设计为普通方法
3)—-在6个派生类中重写getImage();获取图片
3.1天空Sky直接返回image
3.2子弹Bullet:
3.2.1若活着的,直接返回image
3.2.2若死了的,改为删除状态
3.3英雄机Hero:
3.3.1若活着,返回image[0]image[1]的来回切换
3.4小敌机Airplane:
3.4.1若活着的,返回images[0]
3.4.2若死了,返回images[1]-images[4]轮换,4后删除
3.5大敌机与小敌机一致
3.6小蜜蜂与小敌机一致
17下 2:11:17
4)前3步获取图片,则可以开画了,每个对象都能画
画对象图片行为都是一样的,所以将画对象的行为设计在超类中,且设计为普通方法paintObject()实现画对象
————-在超类中设计普通方法paintObject()实现画对象
5)天空需要画两张图,所以需要在Sky中重写paintObject()方法,画两张图片
6)画对象行为写好后,在窗口上调用即可。
七).敌人入场、子弹入场、飞行物移动
1. 敌人入场
a) 敌人是由窗口产生的,所以在World类中设计了nextOne()生成敌人
b) 敌人入场是定时发生的,所以在run中调用了enterAction()实现敌人入场
enterAction:
每400毫秒,获取敌人对象,enemies扩容,装到最后一个元素上
2. 子弹入场:
a)子弹由英雄机发射出来的,所以在Hero类中生成了shoot()生成子弹
b)子弹入场是定时发生的,所以在run中调用了shootAction()实现子弹入场
shootAction:
每300毫秒获取子弹对象,bullets扩容,数组的追加
3. 飞行物移动:
a) 飞行物移动行,为为共有行为,所以在超类中设计超类抽象方法Step,在派生类重写step()
b) 飞行物移动是定时发生的,所以在run中调用stepAction()实现天空、敌人、子弹移动,
计算机中的坐标:
X坐标:左-右
Y坐标:上-下
来一个敌人,给enemies扩一个容,将敌人装到最后一个元素上
假设enemies中有5个敌人
来一个敌人,
Paint()的调用方式:
Frame.setVisible(true);
Repaint();
敌人入场:是由窗口产生的(World类中)
子弹入场:是由英雄机发射的,属于特有行为(Hero类)
飞行物移动:是共有行为,放在超类中
定时器:定时自动发生
Timer
Timertask
Timer.schedule(new Timertask(){
},10,10);TimerTask,long,long,使用匿名内部类
第一个10:从程序启动到第一次出发的时间间隔
第二个10:从第一次触发到第二次触发的时间间隔
从第二次触发到第二次触发的时间间隔
从第三次触发到第二次触发的时间间隔
…
八).实现英雄机随鼠标移动,删除越界的敌人和子弹,设计两个接口
1.英雄机随着鼠标动
1)英雄机随着鼠标动为英雄机的行为,在Hero中设计moveTo()
2)英雄机随着鼠标动为事件触发的,所以在侦听器重写mouseMoved(),mouseMoved中获取鼠标的x和y坐标,英雄机动
2.删除越界的敌人和子弹:
1)在超类中设计outOfBounds()检测敌人是否越界,在Bullet中重写outOfBounds()检测子弹是否越界
2)删除越界是定时发生的,在run()中调用outOfBoundsAction():
声明不越界的敌人或子弹数组,遍历敌人或子弹数组,获取对象
判读若对象不越界,则将对象添加到不越界的敌人或是子弹数组中
将不越界的敌人或子弹数组复制到enemies/bullets中
3.设计接口
设计Enemy得分接口,在小敌机大敌机中实现接口
设计Award奖励接口,在小蜜蜂中实现接口
9.子弹与敌人的碰撞
a)在FlyingObject中设计hit()实现碰撞检测
b)在FlyingObject中设计goDead()实现飞行物去死
c)在Hero中设计addLife()增命或者增加火力值
以下是源码:
package cn.tedu.shoot;//小敌机类
import java.awt.image.BufferedImage;//引入存图片类
//定义小敌机,飞行物,小敌机的移动方式通过y坐标来移动
public class Airplane extends FlyingObject implements Enemy {
private static BufferedImage[] images;//定义图片类数组
static {
images = new BufferedImage[5];
for(int i=0;i<images.length;i ) {
images[i] = loadImage(“airplane” i “.png”);
}
}
private int speed;//移动速度
//构造开始
public Airplane(){//构造函数
super(49,36);
speed = 2;//飞行物移动数值
}
//重写step,代表飞行物移动方式
public void step() {
y =speed;//y 代表向下
}
//重写getImage()获取方法
int index = 1;//死了的下标
public BufferedImage getImage() {//每10毫秒走一次,来回切换
if(isLife()) {
return images[0];
}else if(isDead()) {
BufferedImage img = images[index ];
if(index == images.length) {
state =REMOVE;
}
return img;
}
return null;//删除状态,返回null
}
//实现接口
public int getScore() {
return 1;//子弹打掉小敌机,得1分
}
}
大敌机:
package cn.tedu.shoot;
import java.awt.image.BufferedImage;//引入存图片类
//大敌机的移动方式通过y坐标来移动
public class BigAirplane extends FlyingObject implements Enemy {
private static BufferedImage[] images;//定义图片类数组
static {
images = new BufferedImage[5];
for(int i=0;i<images.length;i ) {
images[i] = loadImage(“bigAirplane” i “.png”);
}
}
private int speed;//移动速度
public BigAirplane(){
super(69,99);
speed = 2;//飞行物移动数值
}
//重写step,代表飞行物移动方式
public void step() {
y =speed;//y 代表向下
}
int index = 1;//死了的下标
public BufferedImage getImage() {//每10毫秒走一次,来回切换
if(isLife()) {
return images[0];
}else if(isDead()) {
BufferedImage img = images[index ];//获取爆破图
if(index == images.length) {//若到了最后一张,执行删除
state =REMOVE;
}
return img;//REMOVE之后以上循环不满足,直接返回null
}
return null;//删除状态,返回null
}
//实现接口
public int getScore() {
return 3;//子弹打大敌机之后,得3分
}
}
小密峰:
package cn.tedu.shoot;
import java.awt.image.BufferedImage;//引入存图片类
import java.util.Random;
//小蜜蜂的移动方式通过xy坐标来移动
public class Bee extends FlyingObject implements Award {
private static BufferedImage[] images;//定义图片类数组
static {
images = new BufferedImage[5];
for(int i=0;i<images.length;i ) {
images[i] = loadImage(“bee” i “.png”);
}
}
private int xSpeed;//x坐标的移动速度
private int ySpeed;//y坐标的移动速度
private int awardType;//奖励机制
//构造方法//
public Bee(){
super(60,50);
xSpeed = 1;
ySpeed = 2;
Random rand = new Random();
awardType = rand.nextInt(2);//产生0-1之间的随机数,随机获得奖励
}
//重写step方法
public void step() {
x =xSpeed;//向左或者向右移动
y =ySpeed;//向下移动
if(x<=0 || x>=World.WIDTH-this.width) {//判断小蜜蜂是否越界,若越界则反方向移动
xSpeed*=-1;//改变正负,数值不变
}
}
int index = 1;//死了的下标
public BufferedImage getImage() {//每10毫秒走一次,来回切换
if(isLife()) {
return images[0];
}else if(isDead()) {
BufferedImage img = images[index ];
if(index == images.length) {
state =REMOVE;
}
return img;
}
return null;//删除状态,返回null
}
//奖励机制
public int getAwardType() {
return awardType;//返回奖励类型
}
}
子弹:
package cn.tedu.shoot;
import java.awt.image.BufferedImage;//引入存图片类
//子弹,是飞行物,利用y坐标进行移动
public class Bullet extends FlyingObject {
private static BufferedImage image;//定义图片类
static {
image = loadImage(“bullet.png”);
}
private int speed;
//构造方法
public Bullet(int x,int y){
super(8,14,x,y);
speed = 3;
}
//重写step
public void step() {
y-=speed;//子弹向上移动
}
//重写抽象类getImage
public BufferedImage getImage() {
if(isLife()) {
return image;
}else if(isDead()){
state =REMOVE;}
return null;
}
//重写outOfBounds(),判断敌人是否越界(子弹)
public boolean outOfBounds(){
return this.y<=-this.height;//子弹的y小于等于负的子弹的高,即为越界
}
}
天空:
package cn.tedu.shoot;
import java.awt.Graphics;
import java.awt.image.BufferedImage;//引入存图片类
//天空,飞行物体,利用y跟y1进行移动
public class Sky extends FlyingObject {
private static BufferedImage image;//定义图片类
static {
image = loadImage(“background.png”);
}
private int speed;//移动速度
private int y1; //界面有两张图,第2张图的y坐标
//构造开始
public Sky(){
super(World.WIDTH,World.HEIGHT,0,0);
speed = 1;
y1 = -World.WIDTH;
}
public void step() {
y =speed;//两张图片往下移动
y1 =speed;
if(y>=World.WIDTH) {//当y>=窗口的高
y = -World.WIDTH;//则修改y的值为负的窗口的高
}
if(y1>=World.WIDTH) {
y1 = -World.WIDTH;
}
}
//重写抽象类getImage
public BufferedImage getImage() {
return image;//直接返回iamge
}
/*重写画笔方法,画对象,画对象的方式都是一样的,所以为普通方法*/
public void paintObject(Graphics g){
g.drawImage(getImage(),x,y,null);//不要求掌握
g.drawImage(getImage(),x,y1,null);
}
}
英雄机:
package cn.tedu.shoot;
import java.awt.image.BufferedImage;//引入存图片类
//英雄飞机
public class Hero extends FlyingObject {
private static BufferedImage[] images;//定义图片类数组
static {
images = new BufferedImage[2];
images[0] = loadImage(“hero0.png”);
images[1] = loadImage(“hero1.png”);
}
private int life;//表示生命值
private int doubleFire;//表示火力值
//构造开始
public Hero() {
super(97,124,140,400);
life = 3;
doubleFire = 0;
}
//英雄飞机随着鼠标动x,y:鼠标的x坐标和y坐标
public void moveTo(int x,int y) {
this.x = x-this.width/2;//以英雄机的中心为鼠标点
this.y = y-this.height/2;
}
public void step() {
}
int index = 0;//活着的下标
public BufferedImage getImage() {//每10毫秒走一次,来回切换
if(isLife()) {return images[index %images.length];}//实现hero0-1的切换,两张图的切换.
return null;
}
//英雄机发射子弹(生成子弹对象)
public Bullet[] shoot() {
int xStep = this.width/4;// 1/4英雄机的宽
int yStep = 20;//固定的20
if(doubleFire>0) {//双倍火力,2发子弹
Bullet[] bs = new Bullet[2];
bs[0] = new Bullet(this.x 1*xStep,this.y-yStep);//给子弹的位置赋值
bs[0] = new Bullet(this.x 3*xStep,this.y-yStep);
doubleFire-=2;
return bs;
}else {//单倍火力,1发子弹
Bullet[] bs = new Bullet[1];
bs[0] = new Bullet(this.x 2*xStep,this.y-yStep);
return bs;
}
}
//增加命
public void addLife() {
life ;
}
//增加火力值
public void addBouleFire() {
doubleFire =40;
}
}
超类:
package cn.tedu.shoot;
import java.util.Random;//引用随机数类
import java.awt.image.BufferedImage;//引入存图片类
import javax.imageio.ImageIO;//读写图片
import java.awt.Graphics;//画对象类
/*飞行物开始*/
public abstract class FlyingObject {//超类成员需要被派生类访问,用protected
static final int LIFE = 0;//活着的状态
static final int DEAD = 1;//死了的
static final int REMOVE = 2;//删除的
protected int state = LIFE;//表示当前状态,默认为活着的
protected int width;//宽
protected int height;//高
protected int x;//x坐标
protected int y;//y坐标
//给小敌机、大敌机、小蜜蜂提供的
public FlyingObject(int width,int height){
this.width = width;
this.height = height;
Random rand = new Random();//随机数对象
x = rand.nextInt(World.WIDTH-this.width);//x的值在0-(WIDHT-width)直接随机出现
y = -this.height;//负的小敌机的高,进场在外边
}
//给英雄机、天空、子弹提供的
public FlyingObject(int width,int height,int x,int y){
this.width = width;
this.height = height;
this.x = x;
this.y = y;
}
//飞行物移动
public abstract void step();
//获取图片方法,只看参数,不看对象,得传参
public static BufferedImage loadImage(String fileName){
//异常处理
try {//试异常
BufferedImage img =ImageIO.read(FlyingObject.class.getResource(fileName));//同包下读取图片
return img;
}catch(Exception e) {//抓异常
e.printStackTrace();
throw new RuntimeException();
}
}
//读取图片 方法
public abstract BufferedImage getImage();
//判断对象的状态是否是活着的
public boolean isLife() {
return state==LIFE;//当前状态是LIFE说明活着的
}//判断对象是否死了
public boolean isDead() {//当前状态是DEAD说明是死了的
return state==DEAD;
}//判断对象是否删除
public boolean isRemove() {//当前的状态是REMOVE是删除的
return state==REMOVE;
}
/*画笔方法,画对象,画对象的方式都是一样的,所以为普通方法*/
public void paintObject(Graphics g){
g.drawImage(this.getImage(),this.x,this.y,null);//不要求掌握
}
//判断敌人是否越界(小敌机,大敌机,小蜜蜂)
public boolean outOfBounds(){
return this.y>=World.HEIGHT;//敌人的y大于等于窗口高,即为越界
}
//检测当前敌人与子弹/英雄机碰撞other
public boolean hit(FlyingObject 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-子弹/英雄机的y
int y2 = this.y this.height;//y2:敌人的y 敌人的高
int x = other.x;//子弹/英雄机的x
int y = other.y;//子弹/英雄机的y
return x>=x1&&x<=x2&&y>=y1&&y<=y2;//x在x1、x2之间,y在y1y2之间时即为碰撞
}
//碰撞后飞行物去死
public void goDead() {
state = DEAD;//将对象状态改为DEAD
}
}
主程序:
package cn.tedu.shoot;
import javax.swing.JFrame;//框架
import javax.swing.JPanel;//面板
import java.awt.Graphics;//画对象类
import java.util.TimerTask;//定时任务
import java.util.Timer;//引入定时器
import java.util.Random;
import java.util.Arrays;//数组扩容
import java.awt.event.MouseAdapter;//监听器,无须理解
import java.awt.event.MouseEvent;//事件处理,无须理解
public class World extends JPanel {//把这些内容放在面板上
static final int WIDTH = 400;
static final int HEIGHT = 700;
private Sky sky = new Sky();//天空
private Hero hero = new Hero();//英雄机
private FlyingObject[] enemies = {};/*Airplane[] as; BigAirplane[] bas;Bee[] bs;有相同功能写成FlyingObject的数组,发生向上造型*/
private Bullet[] bullets = {};//子弹数组
/*产生敌人对象,小蜜蜂大敌机小敌机*/
public FlyingObject nextOne(){//产生随机的敌人,并返回
Random random = new Random();
int type =random.nextInt(20);//控制概率,产生0-19的数字
if(type<5){
return new Bee();
}else if(type<13){
return new Airplane();
}else{
return new BigAirplane();
}
}
//敌人入场计数
int enterIndex = 0;
public void enterAction(){//敌人入场方法,10毫秒走一次
enterIndex ;//每10毫秒增1
if(enterIndex`==0){//每400(10*40)毫秒走一次
FlyingObject obj = nextOne();//获取敌人,有返回值的方法必须要接受=
enemies = Arrays.copyOf(enemies,enemies.length 1);//每增加一个敌人,数组扩容1
enemies[enemies.length-1] = obj;//将obj赋值给enemies的最后一个元素
}
}
//子弹入场
int shootIndex = 0;//子弹入场计数
public void shootAction() {
shootIndex ;//每10个毫秒增1
if(shootIndex5==0) {//每300(10*30)毫秒走一次
Bullet[] bs = hero.shoot();
bullets = Arrays.copyOf(bullets,bullets.length bs.length);//每增加一个敌人,数组扩容(bs的长度)
System.arraycopy(bs,0,bullets,bullets.length-bs.length,bs.length);//数组的追加
}
}
//飞行物移动
public void stepAction() {//10毫秒走一次
sky.step();//天空移动
for(int i=0;i<enemies.length;i ) {
enemies[i].step();
}
for(int i=0;i<bullets.length;i ) {
bullets[i].step();
}
}
//删除越界的子弹和敌人
public void outOfBoundsAction(){//每10个毫秒走一次
int index = 0;//控制下标,表示不越界敌人的个数
FlyingObject[] enemyLives = new FlyingObject[enemies.length];
for(int i=0;i<enemies.length;i ){//遍历所有的敌人
FlyingObject f = enemies[i];//创建一个新的数组对象
if(!f.outOfBounds()){//表示不越界
enemyLives[index] = f;//将不越界的敌人装到另一个数组中
index ;
}
}
enemies = Arrays.copyOf(enemyLives,index);//将不越界敌人的数组enemyLives复制给enemies数组
//删除子弹
index = 0;
Bullet[] bulletsLives = new Bullet[bullets.length];
for(int i =0;i<bullets.length;i ){
Bullet bs = bullets[i];
if(!bs.outOfBounds()){
bulletsLives[index] = bs;
index ;
}
}
bullets = Arrays.copyOf(bulletsLives,index);
}
//子弹与敌人的碰撞
public void bulletBangAction() {//每10个毫秒走一次
for(int i=0;i<bullets.length;i ) {//遍历所有的子弹
Bullet b = bullets[i];//获取每个子弹
for(int j=0;j<enemies.length;j ) {//遍历所有的敌人
FlyingObject f = enemies[j];//获取所有敌人
if(b.isLife() &&f.isLife() && f.hit(b)) {//要是
b.goDead();
f.goDead();
}
}
}
}
public void action() {//测试代码
//无需掌握
MouseAdapter l = new MouseAdapter(){//匿名内部类,侦听器对象
public void mouseMoved(MouseEvent e){//重写mouseMoved(),鼠标移动
int x = e.getX();//获取鼠标的xy坐标
int y = e.getY();
hero.moveTo(x, y);
}
};
this.addMouseListener(l);//处理鼠标的操作事件
this.addMouseMotionListener(l);//处理鼠标滑动
Timer timer = new Timer();//创建定时器对象
int interval = 10;//以10毫秒为单位
timer.schedule(new TimerTask(){
public void run(){//重写匿名内部类里的抽象方法,表示定时干的事情(每10毫秒)
enterAction();//敌人入场
shootAction();//子弹入场
stepAction();//飞行物移动
outOfBoundsAction();//飞行物越界删除
bulletBangAction();//子弹与敌人的碰撞
//System.out.println(bullets.length);
repaint();//调用paint,将敌人画出来
}
},interval,interval);//设定定时任务
}
//将敌人画出
public void paint(Graphics g){//重写JPnel的paint方法
sky.paintObject(g);//画天空
hero.paintObject(g);//画英雄机
for(int i=0;i<enemies.length;i ){//画敌人
enemies[i].paintObject(g);
}
for(int i=0;i<bullets.length;i ){//画子弹
bullets[i].paintObject(g);
}
}
public static void main(String[] args) {
JFrame frame = new JFrame();
World world = new World();//静态main无法访问实例变量,所以要先new个对象
frame.add(world);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(World.WIDTH,World.HEIGHT);
frame.setLocationRelativeTo(null);
frame.setVisible(true);//1)设计窗口可见 2)尽快调用paint()
world.action();//启动程序的执行
}
}
奖励接口:
package cn.tedu.shoot;
//奖励接口
public interface Award {
public int DOUBLE_FIRE = 0;//火力值
public int LIFE = 1;//命
//奖励方法
public int getAwardType();//返回0或1,得到火力值或者是命
}
得分接口:
package cn.tedu.shoot;
//得分接口
public interface Enemy {
public int getScore();//得分方法
}