今天繼續昨天的代碼繼續完善坦克大戰這個小遊戲:
主要完成如下的功能:
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}
};
}
效果如下圖所示:
(這是完成地圖繪制之後的成品圖!)
接着,我們開始繪制地圖中的磚塊以及相應顔色填充,建立一個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遊戲地圖的制作有了相應的了解。
希望明天能繼續學到相應的知識。