这是我最近开始学习Java接触的第一个正式的项目,前面有一个贪吃蛇的太简单了不算,那个后面优化一下再放上来,这篇文章我会从项目需求开始说起,再到类的设计,最后是主类的设计。写这篇文章也是为了整理我这几天所学的知识,加深我对面向对象基础的理解。
运行基本界面
1)游戏包括
敌机:大敌机、小敌机、侦察机、boss机
我方:英雄机
背景:天空
2)各个对象所具备的功能和特征
大敌机:能发射单倍子弹,有三条生命,从天空上方出现往下走,被击毁后英雄机加三分
小敌机:能发射单倍子弹,有一条生命,从天空上方出现往下走,被击毁后英雄机加一分
侦察机:不能发射子弹,有一条生命,从天空上方出现往下斜着走、碰到边界改变x方向,被击毁后给英雄机随机增加一条生命或者火力
boss机:英雄机100分以后出现,能发射双倍子弹,从天空上方出现往下走,有十条生命,被击毁后英雄机加50分和给英雄机随机增加一条生命或者火力
英雄机:初始生命有三条,初始火力为零,英雄机随着鼠标的位置移动,火力大于零后能发射三发子弹、大于1000后能发射五发。
天空:一个向下移动的背景板什么功能也没有
注:
1.程序运行后有背景音,暂停时没有(鼠标移出游戏界面暂停)
2.一发子弹可以打去一条命
3.敌人在被击毁后有爆炸(包括声音和画面)
4.英雄机在被子弹击中后和死亡都有音效
1)对各个类进行分析,以此来设计超类
大敌机:有坐标 x、y,宽,高,生命数,向下移动速度的属性。发射子弹,移动,越界,碰撞,返回分数等行为,具体下面会写。
小敌机:有坐标 x、y,宽,高,生命数,向下移动速度的属性。发射子弹,移动,越界,碰撞,返回分数等行为
侦察机:有坐标 x、y,宽,高,生命数,x、y方向移动速度的属性。发射子弹,移动,越界,碰撞,返会奖励等行为
boss机:有坐标 x、y,宽,高,生命数,向下移动速度的属性。发射子弹,移动,越界,碰撞,返回分数和奖励等行为
英雄机:有坐标 x、y,宽,高,生命数,分数,火力值属性。发射子弹,移动,碰撞等行为
天空:有坐标 x、y,宽,高,向下移动速度的属性。移动行为。
子弹:有坐标 x、y,宽,高,向下或者向上移动速度的属性。移动行为,碰撞,越界等。
可以看出上面的属性和行为有太多的重复,在此设计一个超类有上面共有的属性和方法
下面我直接贴代码了代码有注释的
首先确定,所有的飞机天空子弹都是一张张背景透明的图片,每一此改变它的坐标在将它画到画板上以此形成了连续移动的感觉,就和电影是一个原理。Java的画板类JPanel就为我们提供了将图片显示出来的功能。
设计一个加载图片的类,将图片加载到方法区,这样图片只用读取一次
/*
* 加载图片的类,实现了将各个对象的图片加载到方法区,不用每次都要去读取
*/
public class Images {
public static BufferedImage sky;
public static BufferedImage[] bullets;
public static BufferedImage[] bossairplanes;
public static BufferedImage[] heros;
public static BufferedImage[] airplanes;
public static BufferedImage[] bigairplanes;
public static BufferedImage[] bees;
static{
//天空图片的加载
sky = readImage("background1.png");
//英雄机图片的加载
heros = new BufferedImage[2];
heros[0] = readImage("hero0.png");
heros[1] = readImage("hero1.png");
//子弹图片的加载
bullets = new BufferedImage[2];
bullets[0] = readImage("bullet0.png");
bullets[1] = readImage("bullet1.png");
//boss机图片的加载
bossairplanes = new BufferedImage[5];
bossairplanes[0] = readImage("boss.png");
//小敌机图片的加载
airplanes = new BufferedImage[5];
airplanes[0] = readImage("airplane0.png");
//大敌机的加载
bigairplanes = new BufferedImage[5];
bigairplanes[0] = readImage("bigairplane0.png");
//侦察机图片的加载
bees = new BufferedImage[5];
bees[0] = readImage("bee0.png");
//爆破图片的加载
for(int i=1;i<5;i++){
bees[i] = readImage("bom"+i+".png");
airplanes[i] = readImage("bom"+i+".png");
bigairplanes[i] = readImage("bom"+i+".png");
bossairplanes[i] = readImage("bom"+i+".png");
}
}
//读取图片到内存
public static BufferedImage readImage(String fileName){
try{
BufferedImage img = ImageIO.read(FlyingObject.class.getResource(fileName));
return img;
}catch(Exception e){
e.printStackTrace();
throw new RuntimeException();
}
}
}
public abstract class FlyingObject {
//共同属性
protected int width;
protected int height;
protected int x;
protected int y;
protected int life;
//状态常量
public static final int LIFE = 0;
public static final int DEAD = 1;
public static final int REMOVE = 2;
//当前状态
protected int state = LIFE;
//为敌人提供的构造方法,
FlyingObject(int width,int height,int life){
this.width = width;
this.height = height;
this.x = (int)(Math.random()*(World.WIDTH-width));
this.y = -height;
this.life = life;
}
//为天空和英雄机子弹提供的构造方法
FlyingObject(int width,int height,int x,int y){
this.width = width;
this.height = height;
this.x = x;
this.y = y;
}
//生成子弹组数的方法
public Bullet[] shoot(){
return new Bullet[0];
}
//判断碰撞的方法
public boolean hit(FlyingObject other){
int x1 = other.x - this.width;
int x2 = other.x + other.width;
int y1 = other.y - this.height;
int y2 = other.y + other.height;
return x>=x1 && x<=x2 && y>=y1 && y<=y2;
}
//生命减一
public void subtractLife(){
life--;
}
//判断是否活着
public boolean isLife(){
return state==LIFE;
}
//判断是否死了,后面有用
public boolean isDead(){
return state==DEAD;
}
//判断状态是否为移除
public boolean isRemove(){
return state==REMOVE;
}
//将状态改为DEAD
public void goDead(){
state = DEAD;
}
//在画板上画出图片,getImage()是后面自己写的一个获取图片数据的方法
public void paintObject(Graphics g){
g.drawImage(getImage(),x,y,null);
}
//移动抽象方法,因为每个对象移动的方法不一样,又都有移动,所以写为抽象方法
public abstract void step();
//获取图片的抽象方法,为什么设置为抽象方法如上
public abstract BufferedImage getImage();
//判断是否越界的抽象方法,如上
public abstract boolean outBround();
}
/*
* 继承FlyingObject
*/
public class Sky extends FlyingObject {
//增加速度属性和y1坐标,因为要保证天空移动的连续,一张图片走了,另一张接着走要不然会出现空白
private int speed;
private int y1;
/*
* 天空的构造方法,在画板中坐标是最坐上角的点,整个天空是作为背景的,所以坐标为(0,0)
* 天空的的宽高既窗口的宽高,World.WIDTH(400)和World.HEIGHT(700),World类里确定
*/
Sky(){
super(World.WIDTH,World.HEIGHT,0,0);
y1 = -this.height;
speed = 1;
}
//天空向下移动,y和y1都增加speed的值
public void step() {
y += speed;
y1 += speed;
//保证连续,有图片走出框后将它移到最上方
if(y>=World.HEIGHT){
y = -this.height;
}
if(y1>=World.HEIGHT){
y1 = -this.height;
}
}
//画两次天空图片,一张在下面,一张在上面,保证背景的连续
public void paintObject(Graphics g){
g.drawImage(getImage(), x, y, null);
g.drawImage(getImage(), x, y1, null);
}
//获取图片
public BufferedImage getImage() {
return Images.sky;
}
//天空没有越界行为直接返回false
public boolean outBround() {
return false;
}
}
/*
* 小敌机类,继承FlyingObject,实现小敌机的移动,死亡后爆破
*/
public class Airplane extends FlyingObject implements Enemy {
private int speed;
Airplane(){
super(48,50,1);
speed = 2;
}
public void step() {
y += speed;
}
/*
* 获取小敌机的图片,状态为LIFE时返回小敌机图片
* 状态为DEAD时返回4张爆破图片,全部返回后将状态改为REMOVE,返回null
*/
int index = 1;
public BufferedImage getImage() {
if(isLife()){
return Images.airplanes[0];
}else if(isDead()){
if(index == 5){
state = REMOVE;
return null;
}
return Images.airplanes[index++];
}
return null;
}
//生成一颗子弹的子弹数组子弹的初始坐标为当前小敌机的下面,子弹方向向下
public Bullet[] shoot(){
Bullet[] res = new Bullet[1];
res[0] = new Bullet(x+this.width/2,y+this.height+10,"down");
return res;
}
//小敌机的y坐标大于窗口的高返回ture
public boolean outBround() {
return y>=World.HEIGHT;
}
//返回小敌机的分数
public int getScore() {
return 1;
}
}
/*
* 大敌机类,实现大敌机的移动和生成子弹数组以及返回分数
*/
public class BigAirplane extends FlyingObject implements Enemy {
private int speed;
BigAirplane(){
super(66,89,3);
speed = 2;
}
public void step() {
y += speed;
}
/*
* 获取大敌机的图片,状态为LIFE时返回大敌机图片
* 状态为DEAD时返回4张爆破图片,全部返回后将状态改为REMOVE,返回null
*/
int index = 1;
public BufferedImage getImage() {
if(isLife()){
return Images.bigairplanes[0];
}else if(isDead()){
if(index==5){
state = REMOVE;
return null;
}
return Images.bigairplanes[index++];
}
return null;
}
//生成一颗子弹的子弹数组子弹的初始坐标为当前小敌机的下面,子弹方向向下
public Bullet[] shoot(){
Bullet[] res = new Bullet[1];
res[0] = new Bullet(x+this.width/2,y+this.height+10,"down");
return res;
}
//大敌机的y坐标大于窗口的高返回ture
public boolean outBround() {
return y>=World.HEIGHT;
}
//直接返回大敌机的分数
public int getScore() {
return 3;
}
}
/*
* 侦察机类,实现侦察机的移动
*/
public class Bee extends FlyingObject implements Award {
private int xSpeed;
private int ySpeed;
private int awardType;
Bee(){
super(60,51,2);
xSpeed = 1;
ySpeed = 2;
awardType = (int)(Math.random()*2);
}
//返回奖励的类型
public int getType() {
return awardType;
}
//当侦察机碰到边界时改变x方向的移动
public void step() {
x += xSpeed;
y += ySpeed;
if(x<=0 || x>=World.WIDTH-this.width){
xSpeed = -xSpeed;
}
}
/*
* 获取侦察机的图片,状态为LIFE时返回侦察机图片
* 状态为DEAD时返回4张爆破图片,全部返回后将状态改为REMOVE,返回null
*/
int index = 1;
public BufferedImage getImage() {
if(isLife()){
return Images.bees[0];
}else if(isDead()){
if(index==5){
state = REMOVE;
return null;
}
return Images.bees[index++];
}
return null;
}
//侦察机的y坐标大于窗口的高返回ture
public boolean outBround() {
return y>=World.HEIGHT;
}
}
/*
* boss机类,实现boss机的移动,生成子弹
*/
public class BossAirplane extends FlyingObject implements Enemy,Award {
private int speed;
BossAirplane(){
super(150,113,10);
speed = 2;
}
//返回boss机的分数
public int getScore() {
return 100;
}
//boss机的移动
public void step() {
y += speed;
}
/*
* 获取boss机的图片,状态为LIFE时返回boss机图片
* 状态为DEAD时返回4张爆破图片,全部返回后将状态改为REMOVE,返回null
*/
int index = 1;
public BufferedImage getImage() {
if(isLife()){
return Images.bossairplanes[0];
}else if(isDead()){
if(index==5){
state = REMOVE;
return null;
}
return Images.bossairplanes[index++];
}
return null;
}
//生成两颗子弹
public Bullet[] shoot(){
Bullet[] res = new Bullet[2];
res[0] = new Bullet(x+this.width/3,y+this.height+10,"down");
res[1] = new Bullet(x+2*this.width/3,y+this.height+10,"down");
return res;
}
//判断是否越界
public boolean outBround() {
return y>=World.HEIGHT;
}
//返回奖励类型
@Override
public int getType() {
return (int)(Math.random()*2);
}
}
/*
* 英雄机类,实现了英雄机随鼠标的坐标移动
* 根据火力发射不同火力的子弹
*/
public class Hero extends FlyingObject {
//火力值
private int doubleFire;
Hero(){
super(46,66,170,400);
life = 3;
doubleFire = 10000;
}
//随着鼠标的x,y移动
public void moveTo(int x,int y){
this.x = x - this.width/2;
this.y = y - this.height/2;
}
//根据火力生成想上的子弹数组
public Bullet[] shoot(){
int xStep = this.width/4;
int yStep = -20;
if(doubleFire>1000){
Bullet[] b = new Bullet[5];
for(int i=0;i<5;i++){
b[i] = new Bullet(this.x+i*xStep,y + yStep,"up");
}
doubleFire -= 2;
return b;
}else if(doubleFire>0){
Bullet[] b = new Bullet[3];
for(int i=0;i<3;i++){
b[i] = new Bullet(this.x+i*2*xStep,y + yStep,"up");
}
doubleFire -= 2;
return b;
}else{
Bullet[] b = new Bullet[1];
b[0] = new Bullet(this.x+2*xStep,this.y+yStep,"up");
return b;
}
}
//返回生命数
public int getLife(){
return life;
}
//返回火力值
public int getDoubleFire(){
return doubleFire;
}
//火力增加50
public void addDoubleFire(){
doubleFire += 50;
}
//生命数加一
public void addLife(){
life++;
}
//清空火力值
public void clearDoubleFire(){
doubleFire = 0;
}
public void step() {
}
//英雄机活着时每次返回不同的图片实现英雄机的喷火
private int index = 0;
public BufferedImage getImage() {
if(isLife()){
return Images.heros[index++%2];
}
return null;
}
//英雄机不存在越界行为
public boolean outBround() {
return false;
}
}
/*
* 子弹类,实现子弹的移动
*/
public class Bullet extends FlyingObject {
//dir为不同的子弹图片标记,0为向上,1为向下
private int speed;
private int dir;
Bullet(int x,int y,String direction){
super(8,20,x,y);
if(direction.equals("up")){
speed = -2;
dir = 0;
}else if(direction.equals("down")){
speed = 3;
dir = 1;
}
}
//子弹移动
public void step() {
y += speed;
}
/*
* 返回子弹图片,Images.bullets[0]是向上的,Images.bullets[1]是向下的
* 当状态为DEAD时,改变状态为REMOVE
*/
public BufferedImage getImage() {
if(isLife()){
return Images.bullets[dir];
}else if(isDead()){
state = REMOVE;
return null;
}
return null;
}
//判断是否越界
public boolean outBround() {
return y<=0 || y>=World.HEIGHT;
}
}
返回分数接口
/*
* 返回分数接口
*/
public interface Enemy {
public int getScore();
}
返回奖励类型接口
/*
* 返回奖励类型接口
*/
public interface Award {
public int DOUBLE_FIRE = 0;
public int LIFE = 1;
public int getType();
}
各个对象类都设计完成了,接下来我对先从要实现的功能来进行分析,然后再来设计。
一、英雄机能随鼠标移动和发射子弹
二、英雄机发射的子弹击中敌机后让敌机生命减一同时会有声音提示,英雄机生命小于等于零后游戏结束
三、敌机能发射子弹
四、敌机的子弹击中英雄机后英雄机生命减一,敌机生命小于等于零后爆破后移除
五、敌机、子弹在状态为REMOVE和越界后要删除掉
六、游戏运行后有bgm
启动页面,单击后运行
运行页面
鼠标移出窗口暂停
英雄机生命小于等于零游戏结束,单击后重新进入启动界面
//设置四个状态常量,一个当前状态变量
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;
//窗口的宽和高
public static final int WIDTH = 400;
public static final int HEIGHT = 700;
声明java.applet.AudioClip类型的引用用来储存音频
java.applet.AudioClip all_bomb,enemy_bomb,bg,hero_bomb,hero_bullet;
//声明并创建天空对象,声明并创建英雄机对象
private Sky sky = new Sky();
private Hero hero = new Hero();
/*
*声明敌人数组enemies数组向上造型为FlyingObject超类方面使用
*声明英雄机的子弹数组heroBullets
*声明敌人的子弹数组enemiesBullets
*/
private FlyingObject[] enemies = {};
private Bullet[] heroBullets = {};
private Bullet[] enemiesBullets = {};
//声明图片类静态变量:启动图、暂停图、和游戏结束图并赋值
public static BufferedImage start;
public static BufferedImage pause;
public static BufferedImage gameover;
static{
start = Images.readImage("start.png");
pause = Images.readImage("pause.png");
gameover = Images.readImage("gameover.png");
}
//在构造方法里为各音频赋值
public World(){
try{
all_bomb = JApplet.newAudioClip(new File("music/all_bomb.wav").toURI().toURL());
enemy_bomb = JApplet.newAudioClip(new File("music/enemy_bomb.wav").toURI().toURL());
bg = JApplet.newAudioClip(new File("music/bg.wav").toURI().toURL());
hero_bomb = JApplet.newAudioClip(new File("music/hero_bomb.wav").toURI().toURL());
hero_bullet = JApplet.newAudioClip(new File("music/hero_bomb.wav").toURI().toURL());
}catch(MalformedURLException e){
e.printStackTrace();
}
}
//随机生成大敌机、小敌机、侦察机的对象
public FlyingObject nextOne(){
int n = (int)(Math.random()*100);
if(n>70){
return new Bee();
}else if(n>40){
return new BigAirplane();
}else{
return new Airplane();
}
}
/*
*每隔一段时间调用nextOne方法生成一个敌人添加到敌人数组当中
*当分数大于100后隔更长一段时间生成boss机添加到敌人数组
*/
private int enemiesIndex = 0;
public void enterAction(){
enemiesIndex++;
if(enemiesIndex%30==0){
enemies = Arrays.copyOf(enemies, enemies.length+1);
enemies[enemies.length-1] = nextOne();
}
if(enemiesIndex%1000==0 && score>100){
enemies = Arrays.copyOf(enemies, enemies.length+1);
enemies[enemies.length-1] = new BossAirplane();
}
}
//每隔一段时间遍历敌人敌人数组让没一个敌人数组发射子弹添加到敌人子弹数组
private int enemiesShootIndex = 0;
public void enemiesShoot(){
enemiesShootIndex++;
if(enemiesShootIndex%100==0){
//遍历敌人数组
for(int i=0;i
public void hitAction(){
//遍历敌人数组
for(int i=0;i
private int shootIndex = 0;
public void shootAction(){
shootIndex++;
if(shootIndex%40==0){
//英雄机调用shoot方法生成子弹数组
Bullet[] b = hero.shoot();
//将子弹数组添加到英雄机子弹数组中
heroBullets = Arrays.copyOf(heroBullets, heroBullets.length+b.length);
System.arraycopy(b, 0, heroBullets, heroBullets.length-b.length,b.length);
}
}
//初始化分数为0
int score = 0;
public void bangAction(){
//遍历敌人数组
for(int i=0;i1){
//如果敌人的生命大于1生命减一,子弹状态改变为DEAD
f.subtractLife();
b.goDead();
}else{
//播放爆破音效
enemy_bomb.play();
//当敌人的生命不大于一时改变子弹和敌人的状态为DEAD
f.goDead();
b.goDead();
if(f instanceof Enemy){
//如果是实现了加分的接口则给score加分
Enemy e = (Enemy) f;
score += e.getScore();
}
if(f instanceof Award){
//如果是加奖励则添加对应的奖励
Award a = (Award) f;
int type = a.getType();
switch(type){
case Award.DOUBLE_FIRE:
hero.addDoubleFire();
break;
case Award.LIFE:
hero.addLife();
break;
}
}
}
}
}
}
}
public void stepAction(){
//天空动起来
sky.step();
//敌人动起来
for(int i=0;i
//防止对象过多发生内存泄漏
public void outOfBoundsAction(){
//创建一个FlyingObject的数组来储存没REMOVE和没超出窗口的敌人
int index = 0;
FlyingObject[] fs = new FlyingObject[enemies.length];
for(int i=0;i
public void checkGameOverAction(){
if(hero.getLife()<0){
hero_bomb.play();
state = GAME_OVER;
}
}
public void paint(Graphics g){
//画天空
sky.paintObject(g);
//画英雄机
hero.paintObject(g);
//画敌人
for(int i=0;i
public void action(){
//创建监听鼠标事件匿名内部类
MouseAdapter l = new MouseAdapter(){
//鼠标移动事件
public void mouseMoved(MouseEvent e){
//状态为RUNNING时获取鼠标坐标,英雄机调用moveTo方法让英雄机随着鼠标移动
if(state==RUNNING){
int x=e.getX();
int y=e.getY();
hero.moveTo(x, y);
}
}
//鼠标单击事件
public void mouseClicked(MouseEvent e){
//状态为START时单击状态改变为RUNNING并且播放背景音乐
if(state==START){
//bg.loop为循环播放音乐
bg.loop();
state = RUNNING;
}else if(state==GAME_OVER){
//状态为GAME_OVER时改变状态为START
state = START;
//分数重置为0
score = 0;
//重置所有对象为下一盘游戏做准备
enemies = new FlyingObject[0];
heroBullets = new Bullet[0];
enemiesBullets = new Bullet[0];
hero = new Hero();
}
}
//鼠标移出窗口事件
public void mouseExited(MouseEvent e){
//当状态为RUNNING时改变状态为PAUSE
if(state==RUNNING){
state = PAUSE;
//停止背景音乐播放
bg.stop();
}
}
//鼠标进入窗口事件
public void mouseEntered(MouseEvent e){
//如果状态为PAUSE改变状态为RUNNING并播放背景音乐
if(state==PAUSE){
bg.loop();
state = RUNNING;
}
}
};
//为监听鼠标对象添加Java的监听
this.addMouseListener(l);
this.addMouseMotionListener(l);
//创建计时器定时运行上面的各种方法让程序运行
Timer timer = new Timer();
//intervel 为启动延时和以后运行时间间隔,以毫秒为单位
int intervel = 10;
timer.schedule(new TimerTask(){
public void run() {
//如果状态为RUNNING时开始运行
if(state==RUNNING){
enterAction();
enemiesShoot();
shootAction();
stepAction();
bangAction();
outOfBoundsAction();
hitAction();
checkGameOverAction();
}
//每次都重画保证刷新率
repaint();
}
}, intervel,intervel);
}
//这里实现里监听键盘的接口KeyListener,重写按键时间
@Override
public void keyPressed(KeyEvent e) {
//当状态为RUNNING时
if(state==RUNNING){
//判断是否按下空格键
switch(e.getKeyCode()){
case KeyEvent.VK_SPACE:
//按下空格键后播放爆炸音效同时清空所有地方对象
all_bomb.play();
enemies = new FlyingObject[0];
enemiesBullets = new Bullet[0];
break;
}
}
}
因为播放音效会阻塞程序,而使用java.applet.AudioClip类又要在线程里才能播放出声音
所以这里就实现了Runnable接口,重写run方法运行程序
@Override
public void run() {
action();
}
public static void main(String[] args) {
//创建World对象,World类继承了画板类JPanel
World world = new World();
//创建窗口对象
JFrame frame = new JFrame();
//将画板添加到窗口中去
frame.add(world);
//设置窗口默认关闭操作
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//设置窗口大小
frame.setSize(WIDTH, HEIGHT);
//这个不知道,有没有都没事
frame.setLocationRelativeTo(null);
//设置窗口可见
frame.setVisible(true);
//添加键盘监听
frame.addKeyListener(world);
//创建线程对象
Thread t=new Thread(world);
//启动线程
t.start();
System.out.println("111");
}
感谢阅读,如果有人的话
链接:https://pan.baidu.com/s/1l5u-Ibikf9M_PO_0HU31HA
提取码:0i3i