天天看点

java小游戏坦克大战(二)

今天继续昨天的代码继续完善坦克大战这个小游戏:

主要完成如下的功能:

   5:让坦克可以开火。

 * 6: 现在的子弹的处理方式是否有问题??把飞出屏幕的子弹移除掉。子弹飞出屏幕就不再绘制。

 * 7:添加地图,绘制地图

 * 8:解决闪屏:双缓冲

 * 9:作业:坦克出生在屏幕的左下角、尝试,在屏幕的右上角和左上角,添加两个坦克。

5、第一步我们完成让坦克的开炮功能,首先,我们先定义一个炮弹类Bullet,初步完成子弹的飞行轨迹。

代码如下:

/**
* 炮弹类
* 属性:坐标,速度,方向,伤害,大小,炮弹的敌我,颜色
* @author gc
*
*/
public class Bullet {
	private int x,y;
	private int speed = 8;
	private int dir;
	
	private int atk;
	private int diameter = 4;//diameter
	private boolean isFriendly;//炮弹的敌我属性
	private Color color;//bullet's color
	
	//子弹是否可见
	private boolean visible = true;
	
	public Bullet(int x, int y, int dir, int atk, boolean isFriendly, Color color) {
		super();
		this.x = x;
		this.y = y;
		this.speed = speed;
		this.dir = dir;
		this.atk = atk;
		this.diameter = diameter;
		this.isFriendly = isFriendly;
		this.color = color;
	}
	
	public void draw(Graphics g) {
		//一旦子弹飞出屏幕,这代码就不应该再被执行
		//如果不可见
		if (!visible)return;
		g.setColor(color);
		int w = diameter>>1;
		g.fillArc(x-w, y-w, diameter, diameter, 0, 360);
		//子弹的飞行轨迹
		logic();
		
		
	}

	//子弹的飞行轨迹
	private void logic() {
		switch (dir) {
		case Tank.DIR_UP:
			y -= speed;
			if(y < 0)visible = false;
			break;
		case Tank.DIR_DOWN:
			y += speed;
			if(y > Constant.FRAME_HEIGHT)visible = false;
			break;
		case Tank.DIR_LEFT:
			x -= speed;
			if (x < 0)visible = false;
			break;
		case Tank.DIR_RIGHT:
			x += speed;
			if(x > Constant.FRAME_WIDTH)visible = false;
			break;
		}
		
	}
/
	//增加在其他的类访问子弹是否可见的属性
	public boolean isVisible() {
		return visible;
	}
	public void setVisible(boolean visible) {
		this.visible = visible;
	}

}
           

接着,在Tank的类中,完成让坦克发射炮弹的功能,代码如下:

//坦克发射炮弹功能
		public Bullet fire(){
			int w = width >> 1;
			int bulletX = 0;
			int bulletY = 0;
			//根据坦克(根据方向)的坐标来计算炮弹的坐标
			switch(dir){
			case DIR_UP:
				bulletX = x + w;
				bulletY = y - w;
				break;
			case DIR_DOWN:
				bulletX = x + w;
				bulletY = y + 3*w;
				break;
			case DIR_LEFT:
				bulletX = x - w;
				bulletY = y + w;
				break;
			case DIR_RIGHT:
				bulletX = x + 3*w;
				bulletY = y + w;
				break;
			}
			//生成的子弹的方向和攻击力还有颜色都和坦克一致
			return new Bullet(bulletX, bulletY, dir, atk, true, color);
		}
           

6、优化一下炮弹处理方式,防止容器中的炮弹数量过大,把飞出屏幕的子弹移除掉。子弹飞出屏幕就不再绘制。

代码如下:

public void draw(Graphics g) {
		//一旦子弹飞出屏幕,这代码就不应该再被执行
		//如果不可见
		if (!visible)return;
		g.setColor(color);
		int w = diameter>>1;
		g.fillArc(x-w, y-w, diameter, diameter, 0, 360);
		//子弹的飞行轨迹
		logic();
		
           

7、完成了Tank的发射炮弹的功能,接着我们开始绘制游戏的地图。在制作2d游戏地图时,我们要注意地图有如下的几层:/**

 * 地图类。

 * 1:第一层,地表层,没有碰撞

 * 2:碰撞层。

 * 3:精灵层

 * 4:遮挡层

今天,我们仅仅完成了,第一层,地表层和第二层碰撞层的绘制,首先我们根据定义的窗口,自定义地图的数据,放在MapData类中。具体的数据数组如下:

package com.hbust.entity;
/**
 * 地图碰撞层的数据的类
 * @author gc
 *
 */
public class MapData {
//	800*600  25  cols 32  rows 24 
	//0 代表没有碰撞的块。1代表可以碰撞的地图块
	public static int[][] mapDate0 = {
			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
			{0,0,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,0,0,1,0,0,0},
			{0,0,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,0,0,1,0,0,0},
			{0,0,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,0,0,1,0,0,0},
			{0,0,1,1,1,0,0,0,1,1,1,0,0,1,1,1,0,1,1,1,0,0,0,1,1,1,0,0,1,0,0,0},
			{0,0,1,1,1,0,0,0,1,1,1,0,0,1,1,1,0,1,1,1,0,0,0,1,1,1,0,0,1,0,0,0},
			{0,0,1,1,1,0,0,0,1,1,1,0,0,1,1,1,0,1,1,1,0,0,0,1,1,1,0,0,1,0,0,0},
			{0,0,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,0,0,1,0,0,0},
			{0,0,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,0,0,1,0,0,0},
			{0,0,1,1,1,0,0,0,1,1,1,0,0,0,0,0,0,1,1,1,0,0,0,1,1,1,0,0,1,0,0,0},
			{0,0,1,1,1,0,0,0,1,1,1,0,0,1,1,1,0,1,1,1,0,0,0,1,1,1,0,0,1,0,0,0},
			{0,0,1,1,1,0,0,0,1,1,1,0,0,1,1,1,0,1,1,1,0,0,0,1,1,1,0,0,1,0,0,0},
			{0,0,0,0,0,0,0,0,0,0,0,0,0,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
			{0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0},
			{0,0,0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,0,0},
			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
			{0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0},
			{0,0,0,1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,0,0,0},
			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0},
			{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}	
	};
}
           

效果如下图所示:

java小游戏坦克大战(二)

(这是完成地图绘制之后的成品图!)

接着,我们开始绘制地图中的砖块以及相应颜色填充,创建一个map类,具体代码如下:

/**
 * 地图类。
 * 1:第一层,地表层,没有碰撞
 * 2:碰撞层。
 * 3:精灵层
 * 4:遮挡层
 * @author yhl
 * 属性:宽度高度,单元格的宽度和高度tile,地图数据
 *
 */

public class Map {
	//red block data
	public static final int RED_BLOCK_DATA = 1;
	// map width and height
	private int width , height;
	//地图单元格的宽度,正方形
	public static final int TILE_WIDTH = 25;
	//地图单元格的行数和列数
	private int rowTileCount,colTileCount;
	public Map(int rowTileCount, int colTileCount) {
		super();
		this.rowTileCount = rowTileCount;
		this.colTileCount = colTileCount;
		width = colTileCount * TILE_WIDTH;
		height = rowTileCount * TILE_WIDTH;
	}
	
	public void draw(Graphics g) {
		drawLayer0(g);
		drawLayer1(g);
		
	}

	

	//绘制第一层,地表层
	//单色填充, 应该有地表层的数据
	private void drawLayer0(Graphics g) {
		g.setColor(Color.GREEN);
		g.fillRect(0, Constant.TITLE_BAR_HEIGHT, width, height);
		
	}
	//绘制碰撞层
	private void drawLayer1(Graphics g) {
		for(int i=0;i<rowTileCount;i++){//i 代表行数
			for(int j = 0;j<colTileCount;j++){//j代表列数
				int mapData = MapData.mapDate0[i][j];
				if(mapData == RED_BLOCK_DATA){
					//绘制红色的砖块
					int x = j*TILE_WIDTH;
					int y = i*TILE_WIDTH;
					drawRedTile(g, x, y);
					}
				}
			}
				
	}
//		/**
//		 * 绘制一块砖的方法
//		 */
//	private void drawRedTile(Graphics g, int x, int y) {
//			g.setColor(Color.RED);
//			g.fillRect(x, y, TILE_WIDTH, TILE_WIDTH);
//			g.setColor(Color.BLACK);
//			g.drawRect(x, y, TILE_WIDTH, TILE_WIDTH);
//	}
	/**
	 * 绘制一块砖的方法
	 */
	private void drawRedTile(Graphics g, int x , int y){
		
		g.setColor(Color.RED);
		g.fillRect(x, y, TILE_WIDTH, TILE_WIDTH);
		g.setColor(Color.BLACK);
		g.drawRect(x, y, TILE_WIDTH, TILE_WIDTH);
		
		g.drawLine(x, y+8, x+24, y+8);
		g.drawLine(x+16, y, x+16, y+8);
		g.drawLine(x, y+16, x+24, y+16);
		g.drawLine(x+8, y+8, x+8, y+16);
		g.drawLine(x, y+25, x+24, y+25);
		g.drawLine(x+16, y+16, x+16, y+25);

	}
}
           

8、在运行的过程中,我们发现生成的窗口一直在闪烁,为什么呢?大概是因为绘制的东西太多,而全部刷新地时间不够,于是这里提出一种双缓冲的技术来解决这个问题。具体代码如下:

//地图对象
	private Map map = new Map(Constant.FRAME_HEIGHT/Map.TILE_WIDTH,
			Constant.FRAME_WIDTH/Map.TILE_WIDTH);
	//双缓冲技术:定义一张图片和地图的大小一致。用图片的画笔将需要绘制的内容全部绘制到图片上来。
	//然后,再用系统画笔,将图片一次性的绘制到窗口上来。
	private BufferedImage bufImg = new BufferedImage(Constant.FRAME_WIDTH, 
						Constant.FRAME_HEIGHT, BufferedImage.TYPE_3BYTE_BGR);
	private Graphics ImgG;
           

修改绘图刷新的方法(注意使用update方法):

public void update(Graphics g) {
		if (ImgG  == null) {
			ImgG = bufImg.getGraphics();		}
//		map.draw(g);
//		myTank.draw(g);
//		drawBullets(g);
		map.draw(ImgG);
		myTank.draw(ImgG);
		Tank1.draw(ImgG);
		Tank2.draw(ImgG);
		drawBullets(ImgG);
		//将图片一次性的会知道窗口上使用系统画笔
				g.drawImage(bufImg, 0, 0, null);
//		g.drawString("子弹数量 = "+ bullets.size(),20, 100);
	}
           

9、今天的作业,调整坦克出生的位置好处理,主要我们生成另外的坦克,其实也不难,理解了坦克这么生成的就行,主要修改GameFrame类中的initMytank方法,

myTank = new Tank(20,Constant.FRAME_HEIGHT-Constant.TITLE_BAR_HEIGHT-20,
				Tank.DIR_UP);
		Tank1 = new Tank(20,20,Tank.DIR_DOWN);
		Tank2 = new Tank(Constant.FRAME_WIDTH-Constant.TITLE_BAR_HEIGHT-20,20,
				Tank.DIR_DOWN);
           

接着,在更新的方法中更新创建的坦克:

myTank.draw(ImgG);
		Tank1.draw(ImgG);
		Tank2.draw(ImgG);
           

今天学得东西,比较多,比如双缓冲技术还是不是非常理解,另外对2D游戏地图的制作有了相应的了解。

希望明天能继续学到相应的知识。