打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
老Java程序员花一天时间写了个飞机大战,很舒服!



系列目录

1. Java俄罗斯方块
2. 老Java程序员花2天写了个连连看
3. 老Java程序员花一天时间写了个飞机大战
4. Java五子棋人机版
5. Java植物大战僵尸
6. Java消消乐(天天爱消除)
7. Java贪吃蛇小游戏
8. Java扫雷小游戏
9. Java坦克大战

引言:

前两天我发现CSDN上有两篇飞机大战的文章异常火爆,各种指标都很高(阅读、点赞、评论、收藏等),但都是python写的,竟然不是我大Java,说实话作为老java选手,我心里是有那么一些失落的,难道我大java打飞机不行?就算大java打飞机不行,那我用单身30年的打飞机手速,我肯定行(反正我的代码我做主,就是玩!),于是我决定一展伸手,用java写了一个飞机大战。我就问你们我打飞机行不行,我媳妇都说行,你们呢?欢迎我亲爱的大Java选手们 点赞+评论+收藏!给我冲、冲、冲。。。

代码实现

创建窗口

首先创建一个游戏窗体类GameFrame,继承至JFrame,用来显示在屏幕上(window的对象),每个游戏都有一个窗口,设置好窗口标题、尺寸、布局等就可以。

/*
 * 游戏窗体类
 */public class GameFrame extends JFrame {
	
	public GameFrame() {
		setTitle('飞机大战');//设置标题
		setSize(526, 685);//设定尺寸
		setLayout(new BorderLayout());
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//点击关闭按钮是关闭程序
        setLocationRelativeTo(null);   //设置居中
    	setResizable(false); //不允许修改界面大小
	}}

创建面板容器GamePanel继承至JPanel

package main;import java.awt.Graphics;import javax.swing.JFrame;import javax.swing.JPanel;/*
 * 画布类
 */public class GamePanel extends JPanel{
	GamePanel gamePanel=this;
	private JFrame mainFrame=null;
	//构造里面初始化相关参数
	public GamePanel(JFrame frame){
		this.setLayout(null);
		mainFrame = frame;
		
		mainFrame.setVisible(true);
	}
	
	@Override
	public void paint(Graphics g) {
		
	}}

再创建一个Main类,来启动这个窗口,用来启动。

package main;public class Main {
	//主类
	public static void main(String[] args) {
		GameFrame frame = new GameFrame();
		GamePanel panel = new GamePanel(frame);
		frame.add(panel);
		frame.setVisible(true);//设定显示
	}}

右键执行这个Main类,窗口建出来了

创建菜单及菜单选项

创建菜单

private void  initMenu(){
	// 创建菜单及菜单选项
	jmb = new JMenuBar();
	JMenu jm1 = new JMenu('游戏');
	jm1.setFont(new Font('微软雅黑', Font.BOLD, 15));// 设置菜单显示的字体
	JMenu jm2 = new JMenu('帮助');
	jm2.setFont(new Font('微软雅黑', Font.BOLD, 15));// 设置菜单显示的字体
	
	JMenuItem jmi1 = new JMenuItem('开始新游戏');
	JMenuItem jmi2 = new JMenuItem('退出');
	jmi1.setFont(new Font('微软雅黑', Font.BOLD, 15));
	jmi2.setFont(new Font('微软雅黑', Font.BOLD, 15));
	
	JMenuItem jmi3 = new JMenuItem('操作说明');
	jmi3.setFont(new Font('微软雅黑', Font.BOLD, 15));
	JMenuItem jmi4 = new JMenuItem('胜利条件');
	jmi4.setFont(new Font('微软雅黑', Font.BOLD, 15));
	
	jm1.add(jmi1);
	jm1.add(jmi2);
	
	jm2.add(jmi3);
	jm2.add(jmi4);
	
	jmb.add(jm1);
	jmb.add(jm2);
	mainFrame.setJMenuBar(jmb);// 菜单Bar放到JFrame上
	jmi1.addActionListener(this);
	jmi1.setActionCommand('Restart');
	jmi2.addActionListener(this);
	jmi2.setActionCommand('Exit');
	
	jmi3.addActionListener(this);
	jmi3.setActionCommand('help');
	jmi4.addActionListener(this);
	jmi4.setActionCommand('win');}

实现ActionListener并重写方法actionPerformed


actionPerformed方法的实现

@Overridepublic void actionPerformed(ActionEvent e) {

	String command = e.getActionCommand();
	UIManager.put('OptionPane.buttonFont', new FontUIResource(new Font('宋体', Font.ITALIC, 18)));
	UIManager.put('OptionPane.messageFont', new FontUIResource(new Font('宋体', Font.ITALIC, 18)));
	if ('Exit'.equals(command)) {
		Object[] options = { '确定', '取消' };
		int response = JOptionPane.showOptionDialog(this, '您确认要退出吗', '',
				JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
				options, options[0]);
		if (response == 0) {
			System.exit(0);
		} 
	}else if('Restart'.equals(command)){
		if(startFlag){
			Object[] options = { '确定', '取消' };
			int response = JOptionPane.showOptionDialog(this, '游戏中,您确认要重新开始吗', '',
					JOptionPane.YES_OPTION, JOptionPane.QUESTION_MESSAGE, null,
					options, options[0]);
			if (response == 0) {
				//需要先结束游戏
				realGameEnd(1);
				restart();
			} 
		}else{
			restart();
		}
	}else if('help'.equals(command)){
		JOptionPane.showMessageDialog(null, '游戏开始后,要先动鼠标到飞机处,触发移动效果,然后飞机就会跟随鼠标移动!',
				'提示!', JOptionPane.INFORMATION_MESSAGE);
	}else if('win'.equals(command)){
		JOptionPane.showMessageDialog(null, '得分1000,获得胜利!',
				'提示!', JOptionPane.INFORMATION_MESSAGE);
	}}

创建背景

在GamePanel类中重写paint方法,绘制背景图即可

//绘图方法@Overridepublic void paint(Graphics g) {
	gameHeight = this.getHeight();
	gameWidth = this.getWidth();
	//绘制背景
	g.drawImage((BufferedImage)imageMap.get('bg'), 0, -150, null);}

开启主线程

主线程,用来重绘页面,重绘全部交给主线程,主线程调用 repaint方法就行,要产生动画就要靠这个repaint。

//刷新线程,用来重新绘制页面private class RefreshThread implements Runnable {
	@Override
	public void run() {
		while (startFlag) {
			repaint();
			try {
				Thread.sleep(50);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}}

在GamePanel的构造里面启动这个主线程


有了这个主线程刷新,待会我们更新飞机的位置,飞机就会移动,不需要另外的代码去调用repaint方法了(这是我的做法,仅供参考)。

创建我方飞机

创建MyPlane类,属性有坐标x、y,宽高、图片、是否存活、是否可以移动等;方法主要有绘制、移动、射击等。

public class MyPlane {

	private int x = 0;
	private int y = 0;
	private int width = 0;
	private int height = 0;
	private BufferedImage image = null;
	private GamePanel panel=null;
	private HashMap imageMap=null;
	private boolean alive=true;
	private boolean canMove=false;
	private int key=1;
	private HashMap boomImageMap=null;
	private boolean hitFlag=false;//正在碰撞
	
	public MyPlane(int x,int y,int width,int height,GamePanel panel) {
		this.x=x;
		this.y=y;
		this.width=width;
		this.height=height;
		this.panel=panel;
		this.imageMap=panel.imageMap;
		this.image=(BufferedImage)imageMap.get('myplane1');
		this.boomImageMap=panel.mypalneBoomImageMap;
		
	}
	//绘制
	public void draw(Graphics g) {
		g.drawImage(image, x, y, width,height, null);
	}}

创建(这里只是创建好了飞机对象,需要绘制)

//创建自己飞机private void initMyPlane() {
	myPlane = new MyPlane(200, 530, 132, 86, this);}

在paint方法中绘制

//绘图方法@Overridepublic void paint(Graphics g) {
	gameHeight = this.getHeight();
	gameWidth = this.getWidth();
	//绘制背景
	g.drawImage((BufferedImage)imageMap.get('bg'), 0, -150, null);
	
	//绘制飞机
	if(myPlane!=null){
		myPlane.draw(g);
	}}

鼠标事件监听

加入监听是为了让飞机跟随鼠标移动,我这里定的规则是第一次鼠标必须移动到飞机上,然后飞机才会跟随。

代码里面用一个属性canMove来控制,默认是false,只有鼠标第一次移入到飞机上时,这个属性设置为true,然后就可以跟随鼠标移动了。

//鼠标事件的创建private void createMouseListener() {
	MouseAdapter mouseAdapter = new MouseAdapter() {
		@Override
		public void mouseMoved(MouseEvent e) {
			int x = e.getX();
			int y = e.getY();
			if(myPlane==null) return ;
			//飞机第一次是不允许移动的,只有飞机的canMove为true才去跟随
			if(myPlane.isCanMove()){
				myPlane.move(x,y);
				return;
			}
			//判断鼠标的移入,如果移动到飞机上则canMove设置为true
			if(myPlane.isPoint(x,y)){
				myPlane.setCanMove(true);
			}
		}
	};
	addMouseMotionListener(mouseAdapter);
	addMouseListener(mouseAdapter);}

来实现一下MyPlane的move方法,这里处理了边界,保证飞机不出界,同时保证鼠标在飞机的中间位置

//飞机跟随鼠标移动public void move(int x,int y) {
	//判断范围,当横向移动在窗口范围内
	if(x-width/2>=0 && x<=panel.getWidth()-width/2){
		this.x=x-width/2;
	}
	//判断范围,当纵向移动在窗口范围内
	if(y-height/2>=0 && y<=panel.getHeight()-height/2){
		this.y=y-height/2;
	}}

创建子弹类

属性也就是坐标、宽高这些,给子弹加入移动方法

//移动void move(){
	new Thread(new Runnable() {
		@Override
		public void run() {
			while(alive){
				y-=speed;
				if(y<=0){
					clear();
				}
				
				try {
					Thread.sleep(50);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}).start();}

飞机类加入射击方法,200毫秒创建一发子弹

//射击void shoot() {
	new Thread(new Runnable() {
		@Override
		public void run() {
			while(alive){
				//创建子弹
				createBullet();
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

		private void createBullet() {
			Bullet bullet = new Bullet(x+width/2-10, y, 20, 30, panel);
			panel.bulletList.add(bullet);
			new MusicPlayer('/music/shoot.wav').play();
		}
	}).start();}

别忘记在paint方法里面绘制子弹出来

//绘制子弹Bullet bullet=null;for (int i = 0; i < bulletList.size(); i++) {
	bullet = (Bullet)bulletList.get(i);
	bullet.draw(g);}

创建敌机

创建抽象类Plane

package main;import java.awt.Graphics;public abstract class Plane {
	public abstract void move();
	public abstract void draw(Graphics g);
	public abstract void boom();
	public abstract void clear();
	
	abstract int getX();
	abstract int getY();
	abstract int getWidth();
	abstract int getHeight();}

创建敌机子类

有个特殊一点的地方:因为有4种敌机,这里随机1、2、3、4这4个数字作为属性index,然后根据这个index来展现不同的飞机图片,当然也可以通过这个index来设置敌机不同的移动速度,但是我为了偷懒,就全部一样的移动速度,嘿嘿。
移动就是开启线程让y坐标增加,没什么好讲的,这里加一个飞机碰撞,就是当敌机跟我方飞机如何判断碰撞的问题。

撞机分析(敌机与我机的撞机)




从上面几个图可看出什么?因为图片是方形的,他们的4个顶点一定至少有一个在对方的范围内。再看一下从左边撞击的图:



从上图看到也是这样,其他两个方向的也是一样的道理,为了稳点我还加了一种情况:

1.判断敌机的4个点是否在飞机范围内,如果有则表示碰撞了。
2.如果1不成立,则反过来,判断我机的4个点是否在敌机的范围内,如果是表示碰撞了。

//判断飞机与子弹是否碰撞private boolean isPoint(MyPlane plane) {
	/*
	 * 
	 * 两种情况
	 * 1.需要判断敌机的4个点是否在飞机范围内,如果有则表示碰撞了
	 * 2.如果步骤1不成立,则反过来,判断我机的4个点是否在敌机的范围内,如果是标志碰撞了
	*/
	
	//方式1
	
	//左上角
	int x1 = x;
	int y1 = y;
	//右上角
	int x2 = x+width;
	int y2 = y;
	//右下角
	int x3 = x+width;
	int y3 = y+height;
	//左下角
	int x4 = x;
	int y4 = y+height;
	//只要有一个点在范围内,则判断为碰撞
	if(comparePointMyPlane(x1,y1,plane)|| comparePointMyPlane(x2,y2,plane)||comparePointMyPlane(x3,y3,plane)||comparePointMyPlane(x4,y4,plane) ){
		return true;
	}
	
	//方式1没成立则用方式2判断
	
	//方式2
	x1 = plane.getX();
	y1 = plane.getY();
	//右上角
	x2 = plane.getX()+plane.getWidth();
	y2 = plane.getY();
	//右下角
	x3 = plane.getX()+plane.getWidth();
	y3 =plane.getY()+plane.getHeight();
	//左下角
	x4 = plane.getX();
	y4 = plane.getY()+plane.getHeight();
	if(comparePoint(x1,y1)|| comparePoint(x2,y2)||comparePoint(x3,y3)||comparePoint(x4,y4) ){
		return true;
	}
	return false;}//用敌机的坐标来判断private boolean comparePointMyPlane(int x,int y,MyPlane plane){
	//大于左上角,小于右下角的坐标则肯定在范围内
	if(x>plane.getX() && y >plane.getY()
		&& x<plane.getX()+plane.getWidth() && y <plane.getY()+plane.getHeight()	){
		return  true;
	}
	return false;}//用我机的坐标来判断private boolean comparePoint(int x,int y){
	//大于左上角,小于右下角的坐标则肯定在范围内
	if(x>this.x && y >this.y		&& x<this.x+this.width && y <this.y+this.height){
		return  true;
	}
	return false;}

测试一下效果

忘记说击中敌机的了(原理跟刚才差不多,代码直接放了)

//判断击中敌机protected void hitEnemy() {
	EnemyPlane enemyPlane=null;
	List enemys = panel.enemyList;
	for (int i = 0; i < enemys.size(); i++) {
		try {
			enemyPlane = (EnemyPlane)enemys.get(i);
		} catch (Exception e) {
		}
		if(enemyPlane==null) continue;
		if(this.isPoint(enemyPlane)){
			
			panel.curCount+=enemyPlane.getCount();
			//删除当前子弹
			clear();
			
			//飞机爆炸
			enemyPlane.boom();
			
			if(panel.curCount>=panel.totalCount){
				panel.myPlane.setCanMove(false);
				panel.gameWin();
			}
		}
	}}//判断飞机与子弹是否碰撞private boolean isPoint(EnemyPlane plane) {
	//因为子弹比飞机小,所以只需要判断子弹的4个点是否在飞机范围内,如果有则表示碰撞了
	//左上角
	int x1 = x;
	int y1 = y;
	//右上角
	int x2 = x+width;
	int y2 = y;
	//右下角
	int x3 = x+width;
	int y3 = y+height;
	//左下角
	int x4 = x;
	int y4 = y+height;
	//只要有一个点在范围内,则判断为碰撞
	if(comparePoint(x1,y1,plane)|| comparePoint(x2,y2,plane)||comparePoint(x3,y3,plane)||comparePoint(x4,y4,plane) ){
		return true;
	}
	return false;}private boolean comparePoint(int x,int y,EnemyPlane plane){
	//大于左上角,小于右下角的坐标则肯定在范围内
	if(x>plane.getX() && y >plane.getY()
		&& x<plane.getX()+plane.getWidth() && y <plane.getY()+plane.getHeight()	){
		return  true;
	}
	return false;}

最后加上计分的、胜利、失败等提示就完成了!

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
用 Python 实现微信版飞机大战
使用c语言实现飞机游戏
记事本源代码
容易混淆的英文单词(一)
他击落158架敌机,平均15发子弹干掉一架,希特勒都忍了他2次
八路军天生“神枪手”:一枪击毙敌机驾驶员
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服