zoukankan      html  css  js  c++  java
  • Java坦克大战 (四) 之子弹的产生

    本文来自:小易博客专栏。转载请注明出处:http://blog.csdn.net/oldinaction

    在此小易将坦克大战这个项目分为几个版本,以此对J2SE的知识进行回顾和总结,希望这样也能给刚学完J2SE的小伙伴们一点启示!


    坦克大战V0.4实现功能:

    1、写一个Missile类,产生一个可运动的子弹

    2、让子弹可以通过按键控制发射,并且从坦克中心发射出来

    3、使坦克停下来仍然能发子弹。为坦克添加炮筒,炮筒的方向会随着坦克运动方向而改变

    4、能打出多发炮弹

    5、解决炮弹不消亡的问题,解决坦克出界的问题


    注意事项:

    1、java.awt和java.util包中都有个List,所以当着两个包都使用了是,就要明确指定。

    2、当写好了Missile类后,要在TankCilent中先new出来再将它画出来

    3、根据“坦克打出一发子弹”这句话,来确定Tank中的方法fire,其返回值为Missle

    4、让子弹可以通过按键控制发射,并且从坦克中心发射出来,如下图:


    所以:

    子弹中心点x坐标: x2 = x1 + w1/2 - w2/2;
    子弹中心点y坐标: y2 = y1 + h1/2 - h2/2;

    5、为了解决坦克停下也能打出炮弹的问题—画出炮筒,Tank类增加新的属性ptDir,炮筒的方向

    6、解决炮弹不消亡的问题,步骤:加入控制炮弹生死的量bLive(Missle),当炮弹已经死去就不需要对其重画,当炮弹飞出边界就死亡,当炮弹死亡就从容器中去除。解决坦克出界的问题同样如此

    7、打出多发炮弹,步骤:使用容器装炮弹,每当抬起某键就往容器中加入新的炮弹,逐一画出每一发炮弹


    坦克大战V0.4源代码:

    TankCilent类:

     

    import java.awt.*;
    import java.awt.event.*;
    import java.util.List; //java.awt包中也有个List,所以此处要导包明确
    import java.util.ArrayList;
    
    public class TankClient extends Frame {
    	public static final int GAME_WIDTH = 800;
    	public static final int GAME_HEIGHT = 600;
    
    	Tank myTank = new Tank(50, 50 ,this);
    	List<Missile> missiles = new ArrayList<Missile>(); //定义一个集合来装子弹
    	
    	Image offScreenImage = null; //定义一个屏幕后的虚拟图片
    
    	@Override
    	public void paint(Graphics g) {
    		myTank.drawTank(g);
    		for (int i = 0; i < missiles.size(); i++) { //遍历集合,把其中的子弹画出来
    			Missile m = missiles.get(i);
    			m.drawMissile(g);
    		}
    		g.drawString("Missile Count: " + missiles.size(), 10, 50); //用来记录missiles中子弹的个数
    	}
    	
    	//利用双缓冲消除圆圈移动时屏幕的闪动
    	@Override
    	public void update(Graphics g) {
    		if (offScreenImage == null) {
    			offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT); //判断是为了避免每次重画时都给offScreenImage赋值
    		}
    		Graphics gOffScreen = offScreenImage.getGraphics(); //定义虚拟图片上的画笔gOffScreen
    		Color c = gOffScreen.getColor();
    		gOffScreen.setColor(Color.GREEN);
    		gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); //重画背景,如果没有这句则在屏幕上会保留圆圈的移动路径
    		gOffScreen.setColor(c);
    		paint(gOffScreen); //把圆圈画到虚拟图片上
    		g.drawImage(offScreenImage, 0, 0, null); //再一次性把虚拟图片画到真实屏幕上,在真实屏幕上画则要用真实屏幕的画笔g
    	}
    
    	public void luanchFrame() {
    		this.setLocation(400, 300);
    		this.setSize(GAME_WIDTH, GAME_HEIGHT);
    		this.setTitle("坦克大战 - By:小易 - QQ:381740148");
    		this.setResizable(false); //不允许改变窗口大小
    		this.addWindowListener(new WindowAdapter() {
    			@Override
    			public void windowClosing(WindowEvent e) {
    				System.exit(0);
    			}
    		}); //添加关闭功能,此处使用匿名类比较合适
    		this.setBackground(Color.GREEN);
    		
    		this.addKeyListener(new KeyMonitor());
    		
    		setVisible(true);
    		
    		new Thread(new PaintThread()).start(); //启动线程,实例化线程对象时不要忘了new Thread(Runnable对象);
    	}
    
    	public static void main(String[] args) {
    		TankClient tc = new TankClient();
    		tc.luanchFrame();
    	}
    	
    	//PaintThread只为TankClient服务,所以写成内部类好些
    	public class PaintThread implements Runnable { 
    		
    		public void run() {
    			while (true) {
    				repaint(); //repaint()是TankClient或者他的父类的方法,内部类可以访问外部包装类的成员,这也是内部类的好处
    				try {
    					Thread.sleep(50); //每隔50毫秒重画一次
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    			}			
    		}
    	}
    
    	public class KeyMonitor extends KeyAdapter {
    		
    		@Override
    		public void keyReleased(KeyEvent e) {
    			myTank.keyReleased(e);
    		}
    
    		@Override
    		public void keyPressed(KeyEvent e) {
    			myTank.keyPressed(e);
    		}	
    	}
    	
    }

    Tank类:

     

    import java.awt.*;
    import java.awt.event.*;
    
    public class Tank {
    	public static final int XSPEED = 5; //定义常量X轴速度
    	public static final int YSPEED = 5;
    	
    	public static final int WIDTH = 30;
    	public static final int HEIGHT = 30;
    	
    	TankClient tc;
    	
    	private int x , y; //定义变量画圆圈(坦克)时四边形左上点的x、y左边
    	
    	private boolean bL = false, bU = false, bR = false, bD = false; //定义变量左上右下的按键是否被按下
    	enum Direction {L,LU,U,RU,R,RD,D,LD,STOP}; //定义枚举类型,值为左、左上、上、右上、右、右下、下、左下、停止
    	
    	private Direction dir = Direction.STOP; //定义变量坦克的方向
    	private Direction ptDir = Direction.D; //定义变量坦克炮筒的方向,起初向下
    		
    	public Tank(int x, int y) {
    		this.x = x;
    		this.y = y;
    	}
    	
    	public Tank(int x, int y ,TankClient tc) {
    		this(x,y); //相当于调用上面的构造方法
    		this.tc = tc;
    	}
    	
    	public void drawTank(Graphics g) {
    		Color c = g.getColor(); //取得g(以后称为画笔)的颜色
    		g.setColor(Color.RED);
    		g.fillOval(x, y, WIDTH, HEIGHT); //"画圆",利用填充一个四边形(四边形的内切圆),参数分别代表:四边形左上点的坐标X,Y,宽度,高度
    		g.setColor(c); //用完画笔后把画笔默认的颜色(黑色)设置回去
    		
    		//根据炮筒的方向,画直线代表炮筒
    		switch (ptDir) {
    		case L:
    			g.drawLine(x+Tank.WIDTH/2, y+Tank.HEIGHT/2, x, y+Tank.HEIGHT/2);
    			break;
    		case LU:
    			g.drawLine(x+Tank.WIDTH/2, y+Tank.HEIGHT/2, x, y);
    			break;
    		case U:
    			g.drawLine(x+Tank.WIDTH/2, y+Tank.HEIGHT/2, x+Tank.WIDTH/2, y);
    			break;
    		case RU:
    			g.drawLine(x+Tank.WIDTH/2, y+Tank.HEIGHT/2, x+Tank.WIDTH, y);
    			break;
    		case R:
    			g.drawLine(x+Tank.WIDTH/2, y+Tank.HEIGHT/2, x+Tank.WIDTH, y+Tank.HEIGHT/2);
    			break;
    		case RD:
    			g.drawLine(x+Tank.WIDTH/2, y+Tank.HEIGHT/2, x+Tank.WIDTH, y+Tank.HEIGHT);
    			break;
    		case D:
    			g.drawLine(x+Tank.WIDTH/2, y+Tank.HEIGHT/2, x+Tank.WIDTH/2, y+Tank.HEIGHT);
    			break;
    		case LD:
    			g.drawLine(x+Tank.WIDTH/2, y+Tank.HEIGHT/2, x, y+Tank.HEIGHT);
    			break;
    		}
    		
    		move(); //每次按键都会重画,就会调用drawTank,在这里重画坦克的此时位置
    	}
    	
    	public void keyPressed(KeyEvent e) {
    		int key = e.getKeyCode(); //得到按键的虚拟码,再和下面的KeyEvent.VK_LEFT等虚拟码比较看是否是某按键
    		switch (key) {
    		case KeyEvent.VK_LEFT:
    			bL = true;
    			break;
    		case KeyEvent.VK_UP:
    			bU = true;
    			break;
    		case KeyEvent.VK_RIGHT:
    			bR = true;
    			break;
    		case KeyEvent.VK_DOWN:
    			bD = true;
    			break;
    		}
    		locateDraction();
    		
    		if (dir != Direction.STOP) {
    			ptDir = dir;
    		}
    	}
    	
    	public void keyReleased(KeyEvent e) {
    		int key = e.getKeyCode();
    		switch (key) {
    		case KeyEvent.VK_CONTROL: //按Ctrl就发射子弹调用fire方法
    			fire(); //只有松开Ctrl才能发出子弹
    			break;
    		case KeyEvent.VK_LEFT:
    			bL = false;
    			break;
    		case KeyEvent.VK_UP:
    			bU = false;
    			break;
    		case KeyEvent.VK_RIGHT:
    			bR = false;
    			break;
    		case KeyEvent.VK_DOWN:
    			bD = false;
    			break;
    		}
    		locateDraction();
    	}
    	
    	//通过上右下的按键是否被按下判断坦克要运动的方向
    	void locateDraction() {
    		if(bL && !bU && !bR && !bD) dir =Direction.L;
    		else if(bL && bU && !bR && !bD) dir =Direction.LU;
    		else if(!bL && bU && !bR && !bD) dir =Direction.U;
    		else if(!bL && bU && bR && !bD) dir =Direction.RU;
    		else if(!bL && !bU && bR && !bD) dir =Direction.R;
    		else if(!bL && !bU && bR && bD) dir =Direction.RD;
    		else if(!bL && !bU && !bR && bD) dir =Direction.D;
    		else if(bL && !bU && !bR && bD) dir =Direction.LD;
    		else if(!bL && !bU && !bR && !bD) dir =Direction.STOP;
    	}
    	
    	public void move() {
    		switch (dir) {
    		case L:
    			x -= XSPEED;
    			break;
    		case LU:
    			x -= XSPEED;
    			y -= YSPEED;
    			break;
    		case U:
    			y -= YSPEED;		
    			break;
    		case RU:
    			x += XSPEED;
    			y -= YSPEED;
    			break;
    		case R:
    			x += XSPEED;
    			break;
    		case RD:
    			x += XSPEED;
    			y += YSPEED;
    			break;
    		case D:
    			y += YSPEED;
    			break;
    		case LD:
    			x -= XSPEED;
    			y += YSPEED;
    			break;
    		case STOP:			
    			break;
    		}
    		
    		//防止坦克出界
    		if (x < 0) x = 0;
    		if (y < 25) y = 25; //考虑了标题栏的高度
    		if (x + Tank.WIDTH > TankClient.GAME_WIDTH) x = TankClient.GAME_WIDTH - Tank.WIDTH;
    		if (y + Tank.HEIGHT > TankClient.GAME_HEIGHT) y = TankClient.GAME_HEIGHT - Tank.HEIGHT;
    	}
    	
    	//坦克开火,就new一个子弹出来
    	private Missile fire() {
    		int x = this.x + Tank.WIDTH/2 - Missile.WIDTH/2; //让子弹从坦克中心打出
    		int y = this.y + Tank.HEIGHT/2 - Missile.HEIGHT/2;
    		Missile m = new Missile(x, y, ptDir , this.tc);
    		tc.missiles.add(m); //每new一个Missile对象就把他装到集合中
    		return m; //返回的m,其他地方可调用可不调用
    	}
    }

    Missile类:

     

    import java.awt.*;
    
    public class Missile {
    	public static final int XSPEED = 10;
    	public static final int YSPEED = 10;
    	
    	public static final int WIDTH = 10;
    	public static final int HEIGHT = 10;
    	
    	int x, y;
    	Tank.Direction dir;
    	
    	boolean live = true; //定义一个判断子弹是否出界的变量
    	
    	private TankClient tc;
    	
    	public Missile(int x, int y, Tank.Direction dir) {
    		this.x = x;
    		this.y = y;
    		this.dir = dir;
    	}
    	
    	public Missile(int x, int y, Tank.Direction dir, TankClient tc) {
    		this(x, y, dir);
    		this.tc = tc;
    	}
    	
    	public void drawMissile(Graphics g) {
    		Color c = g.getColor();
    		g.setColor(Color.BLACK);
    		g.fillOval(x, y, WIDTH, HEIGHT);
    		g.setColor(c);
    		
    		move();
    	}
    
    	private void move() {
    		switch (dir) {
    		case L:
    			x -= XSPEED;
    			break;
    		case LU:
    			x -= XSPEED;
    			y -= YSPEED;
    			break;
    		case U:
    			y -= YSPEED;		
    			break;
    		case RU:
    			x += XSPEED;
    			y -= YSPEED;
    			break;
    		case R:
    			x += XSPEED;
    			break;
    		case RD:
    			x += XSPEED;
    			y += YSPEED;
    			break;
    		case D:
    			y += YSPEED;
    			break;
    		case LD:
    			x -= XSPEED;
    			y += YSPEED;
    			break;
    		}
    		
    		if (x < 0 || y < 0 || x > tc.GAME_WIDTH || y > tc.GAME_HEIGHT) {
    			live = false;
    			tc.missiles.remove(this);
    		}
    	}
    	
    	public boolean isLive() {
    		return live;
    	}
    
    }


    知识点回顾:

    1、泛型的使用:数据类型的限定,此处数据类型指封装数据类型和类

    2、MyEclipse生成get和set方法:光标处于该变量名上 - 单击右键 - Source - Generate Getters and Setters

    3、生成成员变量的get和set方法的好处是可以再其他类中访问这个类的此成员变量



    您的资助是我最大的动力!
    金额随意, 欢迎来赏!

    文章出处:http://www.cnblogs.com/oldinaction/ (1)可关注微信公众号:【AEZO】获取更多信息 (2)微信公众号/小程序交流QQ群:303522792(验证码:cnblogs)。

    如果,想给予我更多的鼓励,求打

    因为,我的写作热情也离不开您的肯定支持,感谢您的阅读,【小易Smalle】!

  • 相关阅读:
    修复 Visual Studio Error “No exports were found that match the constraint”
    RabbitMQ Config
    Entity Framework Extended Library
    Navisworks API 简单二次开发 (自定义工具条)
    NavisWorks Api 简单使用与Gantt
    SQL SERVER 竖表变成横表
    SQL SERVER 多数据导入
    Devexpress GridControl.Export
    mongo DB for C#
    Devexress XPO xpPageSelector 使用
  • 原文地址:https://www.cnblogs.com/oldinaction/p/5167496.html
Copyright © 2011-2022 走看看