前言:在一定的java基础上就可以进行飞机大战小游戏的编写了。整个小游戏主要涉及到的基础知识为:类与对象,鼠标事件监听,线程、重绘等。
设计一个初始界面,在开始后出现自己的战机和敌机且自己的战机不断地发射子弹。当子弹碰到敌机后加分,若自己碰到敌机则游戏结束。
需要的类
1.入口类 2.自己的战机类 3.敌机类 4.子弹类 5.背景类 (6.抽象类 7.数据类)
在设计类的时候可以注意到,战机、敌机和子弹都属于飞行物体,它们都拥有几个相同的属性和方法。例如XY坐标,长度、宽度、速度,移动方法,绘制方法等。因此可以建立一个抽象的飞行类作为父类,战机类、敌机类、子弹类继承父类的属性和方法,并重写其中的抽象方法,为我们的代码编写提供方便。
首先,建立一个入口类(MainFrame)继承JPanel.实例化一个窗体,并设置窗体的各种属性。
public static void main(String[] args) {
// TODO Auto-generated method stub
JFrame jf = new JFrame();
MainFrame mf = new MainFrame(); //入口类名为MainFrame
jf.add(mf);
jf.setTitle("飞机大战");
jf.setDefaultCloseOperation(3);
jf.setSize(700,1000);
jf.setLocationRelativeTo(null);
jf.setVisible(true);
mf.iniUI();
}
这时候,我们已经拥有了一个界面。其中,iniUI是一个初始化的方法,其中包括了监听和线程两个内容。当然,可以将监听和线程分开来写成监听类和线程类,但因为这个小游戏中用到的内容不多便写在了一起,避免传参出现不便和错误。
根据思路,在初始化了界面之后我们需要一个背景类。即在界面中绘制一张背景图,通过图像坐标的改变,实现背景不断的移动,造成战机不断前进的状态景象。可以想象,只有一张图片很难在移动后实现循环的绘制,因此采用两张图片。当一张图片移出画面后另一张接上,同时改变前一张图片的坐标。如此形成循环往复。
//读取图片
static {
try {
bgimg1 = ImageIO.read(Sky.class.getResourceAsStream("/picture/背景.jpg"));//相对位置
bgimg2 = ImageIO.read(Sky.class.getResourceAsStream("/picture/背景.jpg"));
}catch(IOException e){
e.printStackTrace();
}
}
//构造方法
public Sky(){
height = 1000;
speed =1;
y1 = 0;
y2 = -height; //从画面外开始画
}
//绘制背景图
public void paint(Graphics g) {
g.drawImage(bgimg1, 0, y1, null);
g.drawImage(bgimg2, 0, y2, null);
}
//背景图的移动
public void move() {
y1+=speed;
y2+=speed;
if(y1>=height) {
y1=-height+1;//此处的1为微小的调整,可根据实际情况修改
}else if(y2>=height) {
y2=-height+1;
}
}
在写完这个天空类之后,将其在入口类中实例化后调用它的move方法,就可以看到滚动的天空背景图片了。
根据思路,总结出战机、敌机和子弹类都具有的几个共同的属性和方法为:
注意抽象类的声明关键字为abstract
public int x;
public int y;//横纵坐标
public int width;
public int height;//宽度高度
//绘画方法
public void paint(Graphics g) {
g.drawImage(getImage(), x, y, null);
}
//判断碰撞方法
public boolean isHit(Flyobject fly) {
int x = obj.x-this.x;
int y = obj.y-this.y;
int r = 100;//判断半径
return (x*x+y*y
战机类、敌机类和子弹类都是飞行体这一抽象类的子类。因此继承飞行体这一父类之后可以直接使用里面定义好的属性值,重写里面的方法即可。
因为子弹的初始位置在战机的机头位置,因此将创建子弹的方法写在战机类里。实例化战机后调用该方法创建子弹。
继承飞行体抽象类。
private static BufferedImage heroimage;
static {
try {
heroimage = ImageIO.read(Hero.class.getResourceAsStream("/picture/LiPlane.png"));
} catch (IOException e) {
e.printStackTrace();
}
}//读取图片
//构造方法
public Hero() {
x = 280;
y = 800;
width = heroimage.getWidth();
height = heroimage.getHeight();
}
public void paint(Graphics g) {
g.drawImage(getImage(), x, y, null);
}
public void move(int x,int y) {
this.x = x - width/2;
this.y = y - height/2;
//此处是左右边界判断
if(this.x >= 700-width) {
this.x = 700-width;
}else if(this.x <= 0){
this.x = 0;
}
//此处是上下边界判断
if(this.y >= 1000-height) {
this.y = 1000-height;
}else if(this.y<=0){
this.y = 0;
}
}
//创建子弹方法
public Bullet creatBullet() {
Bullet b = new Bullet(this.x+this.width/2-2,this.y-2);
//调用Bullet构造方法 具体可见下文中bullet类
return b;
}
@Override
public BufferedImage getImage() {
return heroimage;
}
在创建敌机类之前应该想到,敌机可以用数组或者用ArrayList来存储,且敌机出现的位置应该是纵坐标一致,横坐标随机。
继承飞行体抽象类。
private static BufferedImage enemyimage;
static {
try {
enemyimage = ImageIO.read(Enemy.class.getResourceAsStream("/picture/敌机1.png"));
} catch (IOException e) {
e.printStackTrace();
}
}//创建图片
private int enemyspeed; //飞行速度
//构造方法
public Enemy() {
width = enemyimage.getWidth();
height = enemyimage.getHeight();
Random rand = new Random();
x = (int)(rand.nextInt(700-width)); //随机产生横坐标
y = -height;
enemyspeed = 2; //给定飞行速度为2
}
public void move() {
y+=enemyspeed;
}
//是否出界
public boolean outofbound() {
return y > 1000;
}
@Override
public BufferedImage getImage() {
return enemyimage;
}
子弹类的创建与敌机类是大同小异的,同样需要数组或ArrayList来存储子弹对象。
继承飞行体抽象类。
private static BufferedImage bulletImage;
private int bulletspeed; //子弹速度
//构造方法
public Bullet(int x,int y) {
x=x;
y=y;
height=bulletImage.getHeight();
width=bulletImage.getWidth();
bulletspeed = 3;
}
static {
try {
bulletImage = ImageIO.read(Bullet.class.getResourceAsStream("/picture/zd.png"));
} catch (IOException e) {
e.printStackTrace();
}
}
public void paint(Graphics g) {
g.drawImage(getImage(), x, y, null);
}
public void move() {
this.y-=bulletspeed;
}
public boolean outofbound() {
return this.y
准备工作已经完成,现在可以回到入口类中完成整个代码了。梳理整个过程我们应该可以得知,我们还应该解决至少以下几个疑问:
因此我们需要以下方法来解决这些问题,并通过这些方法的调用,最终完成我们的飞机大战小游戏。
———————————————————————————————————
实例化sky 和 hero(战机)
创建子弹和敌机的动态数组
定义得分 和 子弹与敌机的计数器
定义战机的状态
private Hero hero = new Hero();
private Sky sky = new Sky();
public ArrayList bullets = new ArrayList();
public ArrayList enemies = new ArrayList();
//飞机的得分
private int score = 0;
//子弹和敌机的计数器,初始化设定为0
private int bulletCount = 0;
private int enemiesCount = 0;
//定义战机的状态 开始为0运行为1死亡为2 STATE为当前状态
public static int BEGIN = 0;
public static int RUNNING = 1;
public static int OVER = 2;
public static int STATE = 0;
在这里将移动的方法都写到一个方法里,通过sky对象调用sky类里的move方法。将动态数组中的每个子弹和敌机都取出来,分别调用它们各自的移动方法。
//移动方法
public void moveAction() {
//背景移动
sky.move();
//发射子弹
for(int i=0;i
通过已经实例化的对象调用各种绘画方法
public void paint(Graphics g) {
super.paint(g);
sky.paint(g);
//开始状态时绘制背景图(logoImage)
if(STATE == 0) {
g.drawImage(logoImage, 0, 0, null);
}
//绘制己方飞机
hero.paint(g);
//绘画得分
if(STATE!=0) {
paintScore(g);
}
for(int i= 0 ;i< bullets.size();i++) {
bullets.get(i).paint(g);
}
for(int i= 0;i
创建和移除子弹的方法
因为创建和移除敌机的方法几乎完全相同,因此不再赘述
public void createBullet() {
bulletCount++;
//此处的50即为控制子弹生成的速率的参数
if(bulletCount % 50==0) {
Bullet b = hero.creatBullet();
bullets.add(b); //在动态数组中加入新的子弹
bulletCount=0;
}
}
public void removeBullet() {
for(int i=0;i
处理碰撞情况的方法(自己和敌机碰撞、敌机和子弹碰撞)
//子弹和敌机碰撞
public void bulletHitEnemy() {
for(int i=0;i
最后的准备工作也都已经完成了,现在只需要增加界面的监听并在线程中调用它们即可,在文章最前提到的iniUI 方法将实现它们。
public void iniUI() {
MouseAdapter adapter = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
if(STATE==BEGIN) {
STATE = RUNNING;
}else if(STATE==OVER) {
STATE = BEGIN;
score = 0;
//重新初始化自己的飞机
hero = new Hero();
//重新初始化子弹数组
bullets = new ArrayList();
//重新初始化敌机数组
enemies = new ArrayList();
}
}
public void mouseMoved(MouseEvent e) {
if(STATE==RUNNING) {
int x1 = e.getX();
int y1 = e.getY();
hero.move(x1,y1);
}
}
};
this.addMouseListener(adapter);
this.addMouseMotionListener(adapter);
//定义游戏的定时器
//所要执行的任务 延迟时间(毫秒) 执行各后续任务的时间间隔(毫秒)
Timer timer = new Timer();
timer.schedule(new TimerTask(){
public void run() {
if(STATE == RUNNING) {
moveAction();
createBullet();
removeBullet();
createEnemy();
removeEnemy();
//子弹和敌机碰撞的方法
bulletHitEnemy();
//自己和敌机碰撞的方法
herohitenemy();
}
repaint();
}
},10,10);
}
此处有两点需要特别提示的地方。一是没有新建监听类进行监听。adapter是适配器的意思,也就是使用了MouseAdapter类了以后只需要重写自己需要的监听方法即可,不需要将所有的方法都进行重写。二是没有新建线程类,而是使用了Timer类。其实,Timer类实现了Runnable的接口,使用时相当于一个线程。schedule的具体意义在代码段里的注释中已经注明。
此时,已经完成了所有的编写。当然还有很多值得改进的地方,可以将一些常用的方法和数据都写到各自的类里,使入口类的书写更为清晰简洁。
如有问题,希望各位大佬指正!