JAVA 飞机大战

        小的时候我们玩过很多小游戏,比如:坦克大战、贪吃蛇、超级玛丽、推箱子、飞机大战等等。今天就用java写一个小游戏,飞机大战。飞机大战的主要知识点就是线程,只要对线程有基本的了解就能完成飞机大战的编程。

        关于飞机大战的编写,接下来分几个步骤来完成。


一、实现敌方飞机的移动

       (用一张球的图片来替代敌方飞机,之后更换图片就好)
       1.首先要有一个图形界面(定义为BallFream类)
          继承JPanel是为了获取它的画笔,这不是最简便的方法。接下来要使用画笔,你用其他的方法获取也可以.

import javax.swing.JFrame;
import javax.swing.JPanel;

public class BallFrame extends JPanel{
	
	public static void main(String[] args) {
		BallFrame bf = new BallFrame();
		bf.showFrame();
	}
	
	public void showFrame() {
		JFrame frame = new JFrame();
		frame.setTitle("球");
		frame.setSize(500,600);
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(3);
		frame.setResizable(false);
		//将JPanel添加到JFrame,this就是JPanel
		frame.add(this);
		frame.setVisible(true);
	
	}

}

       2.定义一个(Ball类)类来存储对象(现在用的图片是球,定义一个球类存储相应的参数)

          同时将球的图片引入进来,之后会使用。引入球的图片用ImageIcon,将图片直接复制到当前包下。通过新建一个对象new ImageIcon(this.getClass().getResource("图片名.类型名")).getImage();

public class Ball {
	private int x,y,width,height;
	private Image image;
	
	public Ball(int x, int y, int width, int height) {
		super();
		this.x = x;
		this.y = y;
		this.width = width;
		this.height = height;
		this.image = new ImageIcon(this.getClass().getResource("ball.png")).getImage();
	}
	
	public int getX() {
		return x;
	}

	public void setX(int x) {
		this.x = x;
	}

	public int getY() {
		return y;
	}

	public void setY(int y) {
		this.y = y;
	}
	
	public int getWidth() {
		return width;
	}

	public void setWidth(int width) {
		this.width = width;
	}

	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}


}

       3.将图片画出来和实现球的移动

          在定义的对象类(Ball类)里写画球方法和移动方法。球并不是真的移动,而是我们改变了球的坐标。画完一个球后,画一个窗体大小的颜色与窗体背景色一样的矩形,会把之前画的球覆盖掉,接下来按照新的坐标再画一次。因为电脑画的非常快,在我们看来就达到了移动的效果。

//画球的方法,把图画出来,给定了位置、大小
public void draw(Graphics2D g) {
	g.drawImage(image, x - 80/2, y - 80/2, 80, 80,null);
	}

//移动的方法,改变坐标y的值,让球看起来在下落
public void move() {
	y+=5;
}

       4.添加监听和继承线程接口(Runnable)

         我们希望在窗体某个位置点击鼠标的时候就生成一个球,并开始运动。这就要添加鼠标监听机制,还要有事件处理类(BallListener类)。

         继承Runnable是为了让移动方法不断执行,这样球的纵坐标就会不断改变,达到移动的效果。

         添加监听方法并启动线程

         在图形界面类(BallFrame类)中添加和启动,启动线程要先与Runnable建立联系,也就是实例化Thread类。同时在实例化事件处理类BallListener是将需要使用的参数传递过去,这里把窗体传递过去,可以通过窗体获取参数。

BallListener bl = new BallListener(this);
this.addMouseListener(bl);
Thread td = new Thread(bl);
td.start();

      5.事件处理类BallListener
      继承MouseAdapter类只需重写用到的抽象方法;

      继承Runnable接口,重写run()方法。

      实现球的移动,需要将以前画的球去掉,留下最新画的球。画一个界面大小的矩形,将以前的球覆盖掉再画新的球。

      在界面上可能不止一个球在移动,而是多个球在移动,就需要把所有的球存储起来。建立一个数组队列用来存储生成的球, 鼠标点击一次就生成一个球,在鼠标点击后就将球存储起来。当线程运行的时候,遍历数组队列,将所有的球都画一遍。

     将球和矩形画出来就需要画笔,而我们已经将窗体传过来了,如果画笔为空,就获取画笔。

import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;

public class BallListener extends MouseAdapter implements Runnable{
	private BallFrame bf;
	private int x,y;
	private Ball ball;
    //创建数组队列,直接通过Ball类来存储
	private ArrayList<Ball> list = new ArrayList<Ball>();
    //你也可以用Graphics,不用Graphics2D
	private Graphics2D g;
	
    //构造函数
	public BallListener(BallFrame bf) {
		this.bf = bf;
	}
	
    //重写鼠标点击方法,获取鼠标点击的坐标,把球的坐标、宽度、高度存储起来
	public void mouseClicked(MouseEvent e) {
		x = e.getX();
		y = e.getY();
		ball =  new Ball(x,y,80,80);
		list.add(ball);
	}
	
    //重写线程的抽象方法
	public void run() {
        //while循环,让代码不断执行
		while(true) {
            //判断画笔是否为空,如果是则获取画笔
			if(g == null) {
				g = (Graphics2D) bf.getGraphics();
                //画笔抗锯齿(让图形边缘变得光滑)
				g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
						RenderingHints.VALUE_ANTIALIAS_ON);
			}
            //获取窗体的背景颜色
			g.setColor(bf.getBackground());
            //画一个窗体大小矩形,去掉移动痕迹
			g.fillRect(0, 0, 500, 600);
            //遍历数组队列,将队列里的球都画出来
			for(int i = 0; i < list.size(); i++) {
                //判断语句,当球移动出窗体可见范围后就将它从数组队列移除,减少内存消耗
				if(y < bf.getHeight()) {
					ball = list.get(i);
                    //调用画球的方法
					ball.draw(g);
                    //调用移动方法
					ball.move();
				}else {
                    //移除不可见的球
					list.remove(ball);
				}
			}
            //线程休眠
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
				
		}
	}
	
}

      6.补充

         关于调用Ball类的移动方法后数组队列里存储的值也会改变问题。

         ball = list.get(i);只是把地址赋给了ball,并不是直接把存储的值给了ball。当ball调用移动方法后改变了y的值,改变的值是数组队列中的值。

      7.总结

        以上实现的功能为:鼠标点击窗体的某一个位置时,生成一个球,点击多次,生成多个球。之后球会向下移动。

        但存在屏闪问题。有可能在画一个球的同时,一个清除移动痕迹的矩形也在绘制,会造成相互冲突。我们希望球在同一时间画出来,接下来就解决这个问题。

二、双缓冲解决屏闪问题

         

        

        注意,显示缓冲区是和显示器一起的,显示器只负责从显示缓冲区取数据显示。我们通常所说的在显示器上画一条直线,其实就是往该显示缓冲区中写入数据。显示器通过不断的刷新(从显示缓冲区取数据),从而使显示缓冲区中数据的改变及时的反映到显示器上。

     这也是显示复杂图形时造成闪烁的原因,比如你现在要显示从屏幕中心向外发射的一簇射线,你开始编写代码用一个循环从0度开始到360度,每隔一定角度画一条从圆心开始向外的直线。你每次画线其实是往显示缓冲区写入数据,如果你还没有画完,显示器就从显示缓冲区取数据显示图形,此时你看到的是一个不完整的图形,然后你继续画线,等到显示器再次取显示缓冲区数据显示时,图形比上次完整了一些,依次下去直到显示完整的图形。你看到图形不是一次性完整地显示出来,而是每次显示一部分,从而造成闪烁。

--------------------- 本文来自 Smith先生 的CSDN 博客 ,全文地址请点击:https://blog.csdn.net/acs713/article/details/16359551?utm_source=copy

 

        所以,我们引入次画布。将数组队列里存储的信息遍历完后将图形在次画布上画出来,再把次画布和次画布上的图形画到窗体上。

        在BallListener类进行操作,对run()方法进行修改。

private BufferedImage bi;//可用来构建次画布

public void run() {
		while(true) {
			//引入次画布,先在次画布上消除移动的痕迹,然后把图形按照数组队列逐一画出来
			BufferedImage bi = new BufferedImage(bf.getWidth(), bf.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
			//获取次画布上的画笔
			ig =(Graphics2D) bi.getGraphics();
			ig.setColor(bf.getBackground());
			//用次画布上的画笔在次画布上消除移动痕迹
			ig.fillRect(0, 0, bf.getWidth(), bf.getHeight());
			for(int i = 0; i < list.size(); i++) {
                //图形移动出窗体可见后就移除,减少占用
				if(x > 0 && x < bf.getWidth() && y > 0 && y < bf.getHeight()) {
					ball = list.get(i);
					//用次画布上的画笔画球
					ball.draw(ig);
					ball.move();
				}else {
					list.remove(ball);
				}
			}
			//如果画笔为空,则获取窗体上的画笔
			if(g == null) {
				g = (Graphics2D) bf.getGraphics();
				g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
						RenderingHints.VALUE_ANTIALIAS_ON);
			}
			//将次画布上的图形画到窗体上
			g.drawImage(bi, null, bf);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
				
		}
	}

三、敌方飞机自动生成和发射子弹

现在我们在前面的基础上开发飞机大战,为了代码的易读,修改了类名,现在再贴一遍代码吧

界面类PlaneFrame

import javax.swing.JFrame;
import javax.swing.JPanel;

public class PlaneFrame extends JPanel{
	
	public static void main(String[] args) {
		PlaneFrame pf = new PlaneFrame();
		pf.showFrame();
	}
	
	public void showFrame() {
		JFrame frame = new JFrame();
		frame.setTitle("球");
		frame.setSize(700,600);
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(3);
		frame.setResizable(false);
		//将JPanel添加到JFrame,this就是JPanel
		frame.add(this);
		frame.setVisible(true);
		
		PlaneListener bl = new PlaneListener(this);
		//添加鼠标监听方法
		this.addMouseListener(bl);
		//与线程建立联系
		Thread td = new Thread(bl);
		//调用启动线程的方法
		td.start();
	
	}
	
}

事件处理类PlaneListener

import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.MouseAdapter;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

import javax.swing.ImageIcon;

public class PlaneListener extends MouseAdapter implements Runnable{
	private PlaneFrame pf;
	private int x,y,width,height;
	private Plane plane;
	private ArrayList<Plane> list = new ArrayList<Plane>();//创建球类数组队列,存储数据
	private Graphics g;
	private BufferedImage bi;//可用来构建次画布
	private Graphics ig;
	private Image background;



	//构造函数,把窗体传递过来
	public PlaneListener(PlaneFrame pf) {
		this.pf = pf;
	}
	
	
	public void run() {
		while(true) {
			//引入次画布,先在次画布上消除移动的痕迹,然后把图形按照数组队列逐一画出来
			BufferedImage bi = new BufferedImage(pf.getWidth(), pf.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
			//获取次画布上的画笔
			ig = bi.getGraphics();
		//	ig.setColor(bf.getBackground());
			//用次画布上的画笔在次画布上消除移动痕迹
		//	ig.fillRect(0, 0, bf.getWidth(), bf.getHeight());
            //引入背景图片,用背景图片替代矩形达到察除移动痕迹的目的
			background = new ImageIcon(this.getClass().getResource("background.jpg")).getImage();;
			ig.drawImage(background, 0, 0, pf.getWidth(), pf.getHeight(),null);
			
			for(int i = 0; i < list.size(); i++) {
				if(x < pf.getWidth() && y < pf.getHeight()) {
					plane = list.get(i);
					
					//用次画布上的画笔画球
					plane.draw(ig);
					plane.move();
					
				}else {
					list.remove(plane);
				}
			}
			//如果画笔为空,则获取窗体上的画笔
			if(g == null) {
				g = pf.getGraphics();
			}
			//将次画布上的图形画到窗体上
			g.drawImage(bi,0,0, null);
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
				
		}
	}
	
}

定义存储类Plane

import java.awt.Graphics;
import java.awt.Image;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.ImageIcon;

public class Plane {
	private int x,y,width,height;
	private Image image,img;
	private int size;
	private PlaneFrame pf;
	private int addx;
	private int speed;
	
	//构造方法
	//获取图片小球的数据
	public Plane(int x, int y, int width, int height,int speed,,PlaneFrame pf,Image image) {
		super();
		this.x = x;
		this.y = y;
		this.width = width;
		this.height = height;
		this.speed = speed;
		this.pf = pf;
		this.image = image;
		
		this.img = new ImageIcon(this.getClass().getResource("bullet.png")).getImage();	
	}
	
	//画图
	public void draw(Graphics g) {
		g.drawImage(image, x-width/2, y-height/2, width, height,null); //画图
	}
	
	//移动
	public void move() {
		y+=speed;	
	}
	

	public int getX() {
		return x;
	}

	public void setX(int x) {
		this.x = x;
	}

	public int getY() {
		return y;
	}

	public void setY(int y) {
		this.y = y;
	}
	
	public int getWidth() {
		return width;
	}

	public void setWidth(int width) {
		this.width = width;
	}

	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}

	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

}

       1.之前我们是点击鼠标才会生成图片,现在我们要让图片自动生成。这就需要新建一个线程,在程序开始执行的时候通过start()方法来启动线程的run()方法来达到我们目的 。

       在PlaneListener类的构造方法里启动新的线程PlaneThread类(未定义),并传递参数。

public PlaneListener(PlaneFrame pf) {
	this.pf = pf;
    //这里的10为产生敌机的数量,可修改
	PlaneThread pt = new PlaneThread(pf,list,10,width,height);
    //启动新线程
	pt.start();
}

       在run()方法里写一个循环,给定size的大小,也就是产生敌方飞机的数量。再利用休眠,每隔一段时间就产生一架敌机,并且利用数组队列存储起来。

接下来是PlaneThread类

import java.awt.Image;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.ImageIcon;

public class PlaneThread extends Thread{
	private int size;
	private PlaneFrame pf;
	private ArrayList<Plane> list;
	private int x,y,width,height;
	private Image image;


	
	//构造方法
	public PlaneThread(PlaneFrame pf,ArrayList<Plane> list,int size,int width, int height) {
		this.pf = pf;
		this.list = list;
		this.size = size;
		this.width = width;
		this.height = height;
        //敌机的图片
		this.image = new ImageIcon(this.getClass().getResource("fighter.png")).getImage();
		
	}
	
	public void run() {
        //循环,利用随机数让敌机生成的横坐标无规律
		for(int i = 0; i < size; i++) {
			Random rand = new Random();
			x = 40 + rand.nextInt(pf.getWidth() - 200) + 1;
			y = 20;
            //利用数组队列将敌机存储起来
            //x-80/2,y-80/2为坐标,80,80为图片的宽度和高度,2为移动速度,image为图片
			Plane ball = new Plane(x-80/2,y-80/2,80,80,2,list,pf,image);
			list.add(ball);
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
}

       2.敌机自动发射子弹,我这里是让敌机每向下移动50单位距离就产生一枚子弹。在Plane类的移动方法里进行操作。当敌机的y坐标除以50没有余数的时候就生成一枚子弹。同时需要将数组队列传过来,把生成的子弹存储起来。传递数组对列这里就不再重复了,通过构造函数,还要将子弹的图片引入,下面是Plane类里移动方法的修改。

public void move(ArrayList<Plane> list) {
    //用speed来指代每次移动距离
	y+=speed;
	//判断是否应该生成子弹
	if(y%50 ==0) {
    //将子弹存储起来
    //x,y为子弹生成的坐标;20,20为子弹图片的大小;6为子弹的移动速度;img为子弹的图片
	Plane bullet = new Plane(x,y,20,20,6,list,pf,img);
	list.add(bullet);
}

       注意:到达这一步运行程序后会发现,敌机每隔50个单位距离会产生一颗子弹,而之前产生的子弹也会每隔50个单位生产一颗子弹。这是因为判断生成子弹的时候只根据距离来判断,没有区分敌机还是子弹,这个问题后面会解决。

       

四、己方飞机和子弹

      1.通过鼠标控制己方飞机,当鼠标在窗体上移动时己方飞机跟着移动,同时还需给定己方飞机的初始位置。创建一个myplane对象来对应己方飞机,实时更新己方飞机的坐标值。在PlaneListener类进行操作

//给定己方飞机的初始坐标值
private int x = 330,y = 520,width,height;
//引入己方飞机的图片
private Image imagemyplane = new ImageIcon(this.getClass().getResource("myplane.png")).getImage();

private Plane myplane;
	
//构造函数,把窗体传递过来
public PlaneListener(PlaneFrame pf) {
	this.pf = pf;
	//存储己方飞机的数据	
	myplane = new Plane(x,y,80,80,0,list,pf,imagemyplane);		
	list.add(myplane);
	//启动生成敌机的线程	
	PlaneThread pt = new PlaneThread(pf,list,50,width,height);
	pt.start();
		
}
	
public void mouseMoved(MouseEvent e){
    //获取鼠标移动时的坐标值
	x = e.getX();
	y = e.getY();
    //将坐标赋给myplane对象
	myplane.setX(x);
	myplane.setY(y);	
}

      2.己方飞机发射不断发射子弹,创建一个己方飞机的子弹线程MyBullet。己方飞机的子弹通过获取己方飞机的坐标来生成子弹。

import java.awt.Image;
import java.util.ArrayList;

import javax.swing.ImageIcon;

public class MyBullet extends Thread{
	
	private PlaneFrame pf;
	private ArrayList<Plane> list;
	private int x,y,width,height;
	private Image imagemybullet;
	private Plane myplane;
	
    //构造函数
	public MyBullet(PlaneFrame pf,ArrayList<Plane> list,int x,int y,int width, int height,Plane myplane) {
		this.pf = pf;
		this.list = list;
		this.x = x;
		this.y = y;
		this.width = width;
		this.height = height;
		this.myplane = myplane;
        //引入己方飞机的子弹图片
		this.imagemybullet = new ImageIcon(this.getClass().getResource("mybullet.png")).getImage();
	}

	public void run() {
		while(true) {
            //获取己方飞机的坐标
			x = myplane.getX();
			y = myplane.getY();
            //存储己方飞机的子弹
            //x,y为坐标,20,20为图片的宽度和高度,-6为移动速度(负号是移动方向)
			Plane mybullet = new Plane(x,y,20,20,-6,list,pf,imagemybullet);
			list.add(mybullet);
            //每隔500毫秒生成一颗子弹
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	
}

 

五、判断飞机大战输赢

       1.为解决子弹每隔50个单位距离生成一颗子弹,用字符对存储的信息进行标识,确认是敌机才会生成子弹。

在Plane类里操作

public void move(ArrayList<Plane> list) {
		y+=speed;
		
		for(int i = 0;i < list.size();i++) {
            //如果是敌机且移动50个单位距离
			if(type.equals("e_p") && y%50 == 0) {
				Plane bullet = new Plane(x,y,20,20,6,list,pf,imagebullet,"e_b");
				list.add(bullet);	
			}break;
		}
		
	}

       2.在Plane类里写一个碰撞crash方法,敌机和敌机子弹碰到己方飞机子弹时会消失(从数组队列里一起移除);敌机和敌机子弹碰到己方飞机时,游戏结束,线程停止。static关键字的运用可定义静态变量,在不同的类可通过类名.对象名调用,根据这个来控制PlaneListener类里的run()方法的

Plane类里的crash()方法

public void crash() {
		for(int j = 0;j < list.size();j++){
			Plane lt = list.get(j);
			if(lt.getType().equals("m_p")) {
				for(int i = 0;i < list.size();i++) {
					Plane temp = list.get(i);
					if(temp.getType().equals("e_p") || temp.getType().equals("e_b")) {
						double xx = lt.getX() - temp.getX();
						double yy = lt.getY() - temp.getY();
						double ww = lt.getWidth() + temp.getWidth();
						if(Math.sqrt(xx*xx + yy*yy) <= ww/2) {
							list.remove(lt);
							list.remove(temp);
							//给静态属性赋值
							PlaneListener.flag=false;
						}
					}
				}
			}
			
			if(lt.getType().equals("m_b")) {
				for(int i = 0;i < list.size();i++) {
					Plane temp = list.get(i);
					if(temp.getType().equals("e_p") || temp.getType().equals("e_b")) {
						double xx = lt.getX() - temp.getX();
						double yy = lt.getY() - temp.getY();
						double ww = lt.getWidth() + temp.getWidth();
						if(Math.sqrt(xx*xx + yy*yy) <= ww/2) {
							list.remove(temp);
							list.remove(lt);
						}
					}
				}
			}
		}
		
			
	}

下面贴上完整代码(此版本的飞机大战很简单,可以在此基础上进行开发,你可以发挥自己的想象,增设关卡、血条、奖励、Boss等等)(可能存在少许未使用代码和注释掉的代码没有删除)

import java.awt.Color;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class PlaneFrame extends JPanel{
	
	public static void main(String[] args) {
		PlaneFrame pf = new PlaneFrame();
		pf.showFrame();
	}
	
	public void showFrame() {
		JFrame frame = new JFrame();
		frame.setTitle("飞机大战");
		frame.setSize(700,600);
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(3);
		frame.setResizable(false);
		//将JPanel添加到JFrame,this就是JPanel
		frame.add(this);
		frame.setVisible(true);
		
		PlaneListener bl = new PlaneListener(this);
		//添加鼠标监听方法
	//	this.addMouseListener(bl);
		this.addMouseMotionListener(bl);
		//与线程建立联系
		Thread td = new Thread(bl);
		//调用启动线程的方法
		td.start();
		//让线程里的代码执行
		bl.setFlag(true);
	
	}
	
}
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;

import javax.swing.ImageIcon;

public class PlaneListener extends MouseAdapter implements Runnable{
	private PlaneFrame pf;
	private int x = 330,y = 520,width,height;
	private Plane plane;
	private ArrayList<Plane> list = new ArrayList<Plane>();//创建飞机类数组队列,存储数据
	private Graphics2D g;
	private BufferedImage bi;//可用来构建次画布
	private Graphics ig;
	private Image background;
	private Image imagemyplane = new ImageIcon(this.getClass().getResource("myplane.png")).getImage();;
	private Plane myplane;
	private String type;
	//设置为静态属性,可在别的类直接调用
	public static boolean flag = true;

	
	//构造函数,把窗体传递过来
	public PlaneListener(PlaneFrame pf) {
		this.pf = pf;
		//创建我的飞机对象,并存储起来
		myplane = new Plane(x,y,80,80,0,list,pf,imagemyplane,"m_p");		
		list.add(myplane);
		//创建敌机线程,让敌机自动出现,把用到的参数传递过去
		PlaneThread pt = new PlaneThread(pf,list,50,width,height);
		pt.start();
		//创建我的飞机的子弹线程,让子弹自动发射并传递参数
		MyBullet mb = new MyBullet(pf,list,x,y,width,height,type,myplane);
		mb.start();
	}
	
	
	public PlaneListener() {

	}
	
	public void mouseMoved(MouseEvent e){
		//获取坐标
		x = e.getX();
		y = e.getY();
		//将坐标赋给我的飞机对象
		myplane.setX(x);
		myplane.setY(y);	
	}

	public void run() {
		while(true) {
			if(flag) {
				//引入次画布,先在次画布上消除移动的痕迹,然后把图形按照数组队列逐一画出来
				BufferedImage bi = new BufferedImage(pf.getWidth(), pf.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
				//获取次画布上的画笔
				ig = bi.getGraphics();
				background = new ImageIcon(this.getClass().getResource("background.jpg")).getImage();;
				ig.drawImage(background, 0, 0, pf.getWidth(), pf.getHeight(),null);
				for(int i = 0; i < list.size(); i++) {
					if(x < pf.getWidth() && y < pf.getHeight()) {
						plane = list.get(i);
						//用次画布上的画笔来画
						plane.draw(ig);	
						plane.move(list);
						plane.judge(pf);
						plane.crash();
					}else {
						list.remove(plane);
					}
				}
				//如果画笔为空,则获取窗体上的画笔
				if(g == null) {
					g = (Graphics2D) pf.getGraphics();
					g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
							RenderingHints.VALUE_ANTIALIAS_ON);
				}
				//将次画布上的图形画到窗体上
				g.drawImage(bi,0,0, null);
				try {
					Thread.sleep(100);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			
		}
	}


	public boolean isFlag() {
		return flag;
	}


	public void setFlag(boolean flag) {
		this.flag = flag;
	}

	
}
import java.awt.Image;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.ImageIcon;

public class PlaneThread extends Thread{
	private int size;
	private PlaneFrame pf;
	private ArrayList<Plane> list;
	private int x,y,width,height;
	private Image imageplane;
	private String type;


	
	
	public PlaneThread(PlaneFrame pf,ArrayList<Plane> list,int size,int width, int height) {
		this.pf = pf;
		this.list = list;
		this.size = size;
		this.width = width;
		this.height = height;
		this.imageplane = new ImageIcon(this.getClass().getResource("plane.png")).getImage();
		
	}
	
	public void run() {
		//自动产生敌机
		for(int i = 0; i < size; i++) {
			Random rand = new Random();
			x = 80 + rand.nextInt(pf.getWidth() - 100);
			y = 20;
			Plane plane = new Plane(x-80/2,y-80/2,80,80,2,list,pf,imageplane,"e_p");
			list.add(plane);
			try {
				Thread.sleep(3000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		
	}
	
	
}
import java.awt.Image;
import java.util.ArrayList;

import javax.swing.ImageIcon;

public class MyBullet extends Thread{
	
	private PlaneFrame pf;
	private ArrayList<Plane> list;
	private int x,y,width,height;
	private Image imagemybullet;
	private String type;
	private Plane myplane;
	
	public MyBullet(PlaneFrame pf,ArrayList<Plane> list,int x,int y,int width, int height,String type,Plane myplane) {
		this.pf = pf;
		this.list = list;
		this.x = x;
		this.y = y;
		this.width = width;
		this.height = height;
		this.type = type;
		this.myplane = myplane;
		this.imagemybullet = new ImageIcon(this.getClass().getResource("mybullet.png")).getImage();
	}

	public void run() {
		//自动产生子弹
		while(true) {
			//通过我的飞机对象来获取子弹发射的坐标
			x = myplane.getX();
			y = myplane.getY();
			Plane mybullet = new Plane(x,y,20,20,-6,list,pf,imagemybullet,"m_b");
			list.add(mybullet);
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	
}
import java.awt.Graphics;
import java.awt.Image;
import java.util.ArrayList;
import java.util.Random;

import javax.swing.ImageIcon;

public class Plane {
	private int x,y,width,height;
	private Image imageplane,imagebullet;
	private int size;
	private ArrayList<Plane> list;
	private PlaneFrame pf;
	private int addx;
	private int speed;
	private String type;
	private PlaneListener pr;
	
	//构造方法
	//获取图片小球的数据
	public Plane(int x, int y, int width, int height,int speed,
			ArrayList<Plane> list,PlaneFrame pf,Image imageplane,String type) {
		super();
		this.x = x;
		this.y = y;
		this.width = width;
		this.height = height;
		this.speed = speed;
		this.list = list;
		this.pf = pf;
		this.imageplane = imageplane;
		this.type = type;
		
		this.imagebullet = new ImageIcon(this.getClass().getResource("bullet.png")).getImage();
        //随机数的运用
		Random rand = new Random();
		addx = -10 + rand.nextInt(20);

	}

	//画图
	public void draw(Graphics g) {
		g.drawImage(imageplane, x-width/2, y-height/2, width, height,null); //画图
	//	g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
	//			RenderingHints.VALUE_ANTIALIAS_ON);
	}
	
	//移动
	public void move(ArrayList<Plane> list) {
        //x+=addx;敌机的运动可以左右随机
//		x+=addx;
		y+=speed;
		
		for(int i = 0;i < list.size();i++) {
			if(type.equals("e_p") && y%50 == 0) {
				Plane bullet = new Plane(x,y,20,20,6,list,pf,imagebullet,"e_b");
				list.add(bullet);	
			}break;
		}
		
	}
	
	//判断是否到达边界
    //敌机左右运动到达边界后反弹
	public void judge(PlaneFrame pf) {
		if(x-40 <= 0) {
			addx = -addx;
		}else if(x+40 >= pf.getWidth()) {
			addx = -addx;
		}
	}
	
	//碰撞
	public void crash() {
		for(int j = 0;j < list.size();j++){
			Plane lt = list.get(j);
			if(lt.getType().equals("m_p")) {
				for(int i = 0;i < list.size();i++) {
					Plane temp = list.get(i);
					if(temp.getType().equals("e_p") || temp.getType().equals("e_b")) {
						double xx = lt.getX() - temp.getX();
						double yy = lt.getY() - temp.getY();
						double ww = lt.getWidth() + temp.getWidth();
						if(Math.sqrt(xx*xx + yy*yy) <= ww/2) {
							list.remove(lt);
							list.remove(temp);
							//给静态属性赋值
							PlaneListener.flag=false;
						}
					}
				}
			}
			
			if(lt.getType().equals("m_b")) {
				for(int i = 0;i < list.size();i++) {
					Plane temp = list.get(i);
					if(temp.getType().equals("e_p") || temp.getType().equals("e_b")) {
						double xx = lt.getX() - temp.getX();
						double yy = lt.getY() - temp.getY();
						double ww = lt.getWidth() + temp.getWidth();
						if(Math.sqrt(xx*xx + yy*yy) <= ww/2) {
							list.remove(temp);
							list.remove(lt);
						}
					}
				}
			}
		}
		
			
	}
	

	public int getX() {
		return x;
	}

	public void setX(int x) {
		this.x = x;
	}

	public int getY() {
		return y;
	}

	public void setY(int y) {
		this.y = y;
	}
	
	public int getWidth() {
		return width;
	}

	public void setWidth(int width) {
		this.width = width;
	}

	public int getHeight() {
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}

	public int getSize() {
		return size;
	}

	public void setSize(int size) {
		this.size = size;
	}

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}


	


}

 

  • 33
    点赞
  • 71
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值