Java坦克大战
目錄
項目git地址:https://gitee.com/java-course-design_1/java-programming-tank-game
項目簡介:
項目技術:
功能需求分析
功能架構圖:
編寫思路及部分代碼
游戲窗口初始化
?坦克類的定義
地圖類
游戲常量類
優化 :添加背景音樂和音效
?操作演示視頻
項目git地址:https://gitee.com/java-course-design_1/java-programming-tank-game
項目簡介:
這是一個功能相對全的Java版坦克大戰,界面繪制是通過Java的圖形化用戶界面swing完成的,包括了菜單界面和游戲界面。其中菜單界面可以供玩家選擇重新開始游戲、暫停、繼續、等操作;游戲界面繪制了坦克、草地、墻壁、鷹碉堡等經典坦克場景,玩家在游戲界面操作自己的坦克開始對戰。
項目技術:
本游戲使用的主要技術有Swing編程、面向對象編程、多線程編程。
功能需求分析
游戲實現的主要功能有:
1、我方坦克默認可以渡河,碰到墻壁不能走,鷹碉堡被擊中游戲結束
2、坦克可以上下左右、以及左上左下右上右下八個方向移動,移動時添加音效
3、坦克可以發子彈(可以連發),發射時添加音效
4、擊中對方坦克時,坦克消失,顯示爆炸效果;子彈擊中墻壁時,子彈消失
5、我方坦克吃到血塊時,生命值加30(可以自己設定);我方被擊中時每次血量減50
6、移動過程中檢測碰撞,包括坦克與坦克,坦克與草地、河流、墻壁等
7、聲音處理(開始音樂、背景音樂、移動音效、爆炸音效等)
8、菜單處理(重新開始、暫停/繼續游戲、是否播放背景音樂、設置游戲難度、幫助等
功能架構圖:
app包下的GameMain類是游戲的入口函數。
map包下是游戲地圖的繪制
?game包下的類是關于繪制可視化圖像,使用Graphics類繪制游戲窗口。
tank包下的類是tank的實體類,tank類分為我方坦克MyTank類、敵方坦克EnemyTank類。兩個坦克類都繼承了坦克父類tank類。
utils包下有工具類、爆炸對象池、子彈對象池、游戲常量對象
編寫思路及部分代碼
游戲窗口初始化
1:對游戲的窗口進行初始化,設置
(主要代碼截圖)
package com.lwj.game;import com.lwj.tank.EnemyTank; import com.lwj.tank.MyTank; import com.lwj.tank.Tank; import com.lwj.util.MusicUtil; import com.lwj.util.MyUtil; import com.lwj.map.GameMap;import java.awt.*; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.List;import static com.lwj.util.Constant.*;/*** 游戲的主窗口類。* 所有的游戲展示的內容都要在該類中實現。*/ public class GameFrame extends Frame implements Runnable{//第一次使用的時候加載,而不是類加載的時候加載,private Image overImg = null;//1:定義一張和屏幕大小一致的圖片private BufferedImage bufImg = new BufferedImage(FRAME_WIDTH,FRAME_HEIGHT,BufferedImage.TYPE_4BYTE_ABGR);//游戲狀態private static int gameState;//菜單指向private static int menuIndex;//標題欄的高度public static int titleBarH;//定義坦克對象private static Tank myTank;//敵人的坦克容器private static List<Tank> enemies = new ArrayList<>();//用來記錄本關卡產生了多少個敵人private static int bornEnemyCount;public static int killEnemyCount;//定義地圖相關的內容private static GameMap gameMap = new GameMap();;/*** 對窗口進行初始化*/public GameFrame() {initFrame();initEventListener();//啟動用于刷新窗口的線程new Thread(this).start();}/*** 進入下一關的方法*/private void nextLevel() {startGame(LevelInof.getInstance().getLevel()+1);}//開始過關動畫public static int flashTime;public static final int RECT_WIDTH = 40;public static final int RECT_COUNT = FRAME_WIDTH/RECT_WIDTH+1;public static boolean isOpen = false;public static void startCrossLevel(){gameState = STATE_CROSS;flashTime = 0;isOpen = false;}//繪制過關動畫public void drawCross(Graphics g){gameMap.drawBk(g);myTank.draw(g);gameMap.drawCover(g);g.setColor(Color.BLACK);//關閉百葉窗的效果if(!isOpen) {for (int i = 0; i < RECT_COUNT; i++) {g.fillRect(i * RECT_WIDTH, 0, flashTime, FRAME_HEIGHT);}//所有的葉片都關閉了if (flashTime++ - RECT_WIDTH > 5) {isOpen = true;//初始化下一個地圖gameMap.initMap(LevelInof.getInstance().getLevel()+1);}}else{//開百葉窗的效果for (int i = 0; i < RECT_COUNT; i++) {g.fillRect(i * RECT_WIDTH, 0, flashTime, FRAME_HEIGHT);}if(flashTime-- == 0){startGame(LevelInof.getInstance().getLevel());}}}/*** 對游戲進行初始化*/private void initGame() {gameState = STATE_MENU;}/*** 屬性進行初始化*/private void initFrame() {//設置標題setTitle(GAME_TITLE);//設置窗口大小setSize(FRAME_WIDTH, FRAME_HEIGHT);//設置窗口的左上角的坐標setLocation(FRAME_X, FRAME_Y);//設置窗口大小不可改變setResizable(false);//設置窗口可見setVisible(true);//求標題欄的高度titleBarH = getInsets().top;}/**標題,大小,坐標,窗口的事件的監聽
2:游戲菜單的顯示,以及游戲菜單的按鍵的控制
游戲被分成了若干個游戲的狀態: gameState
游戲菜單
游戲幫助
游戲關于
游戲中
游戲結束
在不同的狀態下,繪制不同的內容,按鍵有不同的處理的方式
3:調整整個游戲窗口的重繪的機制。
FPS:frame per second
要每秒固定的刷新我們的整個窗口。fps==33幀
每隔30ms 刷新一次(對整個窗口進行重繪)。repaint()
單獨啟動一個線程用于窗口的重繪。
運行截圖:
?坦克類的定義
4.坦克類的定義,和繪制
5.坦克的四方向行走,以及邊界的控制
6:坦克發射子彈
7: 坦克的繪制使用圖片
8: 解決屏幕閃爍的問題? 雙緩沖解決
9.敵人坦克的控制
10: 隨機的在屏幕的左上角和右上角產生敵人的坦克
11: 坦克相關的類:父類:Tank 子類:MyTank、 EnemyTank
12:?敵人坦克AI:每隔五秒讓敵人坦克隨機獲得一個狀態(站立、行走)
敵人發射子彈的AI:游戲的每一幀都隨機(0.05 概率)判斷敵人是否發射子彈
13:添加爆炸類,爆炸效果的控制
14:使用對象池來管理爆炸對象
15: 給坦克添加血條和名字
16:敵人的坦克對象池管理
17:自己的坦克死亡的處理,切換游戲狀態,游戲結束。
提供兩個選項:回到主菜單,退出游戲、重置游戲數據、開始新游戲
(相關代碼如下)
package com.lwj.tank;import com.lwj.game.GameFrame; import com.lwj.game.LevelInof; import com.lwj.util.Constant; import com.lwj.util.EnemyTanksPool; import com.lwj.util.MyUtil;import java.awt.*;/*** 敵人坦克類*/ public class EnemyTank extends Tank {public static final int TYPE_GREEN = 0;public static final int TYPE_YELLOW = 1;private int type = TYPE_GREEN;private static Image[] greenImg;private static Image[] yellowImg;//記錄5秒開始的時間private long aiTime;static {greenImg = new Image[4];greenImg[0] = MyUtil.createImage("res/ul.png");greenImg[1] = MyUtil.createImage("res/dl.png");greenImg[2] = MyUtil.createImage("res/ll.png");greenImg[3] = MyUtil.createImage("res/rl.png");yellowImg = new Image[4];yellowImg[0] = MyUtil.createImage("res/u.png");yellowImg[1] = MyUtil.createImage("res/d.png");yellowImg[2] = MyUtil.createImage("res/l.png");yellowImg[3] = MyUtil.createImage("res/r.png");}private EnemyTank(int x, int y, int dir) {super(x, y, dir);//敵人一旦創建就計時aiTime = System.currentTimeMillis();type = MyUtil.getRandomNumber(0,2);}public EnemyTank(){type = MyUtil.getRandomNumber(0,2);aiTime = System.currentTimeMillis();}//用于創建一個敵人的坦克public static Tank createEnemy(){int x = MyUtil.getRandomNumber(0,2) == 0 ? RADIUS :Constant.FRAME_WIDTH-RADIUS;int y = GameFrame.titleBarH + RADIUS;int dir = DIR_DOWN;EnemyTank enemy = (EnemyTank)EnemyTanksPool.get();enemy.setX(x);enemy.setY(y);enemy.setDir(dir);enemy.setEnemy(true);enemy.setState(STATE_MOVE);//根據游戲的難度設置敵人的血量int maxHp = Tank.DEFAULT_HP*LevelInof.getInstance().getLevelType();enemy.setHp(maxHp);enemy.setMaxHP(maxHp);//通過關卡信息中的敵人類型,來設置當前出生的敵人的類型int enemyType = LevelInof.getInstance().getRandomEnemyType();enemy.setType(enemyType);return enemy;}public int getType() {return type;}public void setType(int type) {this.type = type;}public void drawImgTank(Graphics g){ai();g.drawImage(type == TYPE_GREEN ? greenImg[getDir()] :yellowImg[getDir()],getX()-RADIUS,getY()-RADIUS,null);}/*** 敵人的AI*/private void ai(){if(System.currentTimeMillis() - aiTime > Constant.ENEMY_AI_INTERVAL){//間隔5秒隨機一個狀態setDir(MyUtil.getRandomNumber(DIR_UP,DIR_RIGHT+1));setState(MyUtil.getRandomNumber(0,2) == 0 ? STATE_STAND : STATE_MOVE);aiTime = System.currentTimeMillis();}//比較小的概率去開火if(Math.random() < Constant.ENEMY_FIRE_PERCENT){fire();}}} package com.lwj.tank;import com.lwj.util.MyUtil;import java.awt.*;/*** 自己的坦克*/ public class MyTank extends Tank{//坦克的圖片數組private static Image[] tankImg;//靜態代碼塊中對它進行初始化static{tankImg = new Image[4];for (int i = 0; i <tankImg.length ; i++) {tankImg[i] = MyUtil.createImage("res/tank1_"+i+".png");}}public MyTank(int x, int y, int dir) {super(x, y, dir);}@Overridepublic void drawImgTank(Graphics g) {g.drawImage(tankImg[getDir()],getX()-RADIUS,getY()-RADIUS,null);}} package com.lwj.tank; import com.lwj.game.Bullet; import com.lwj.game.Explode; import com.lwj.game.GameFrame; import com.lwj.map.MapTile; import com.lwj.util.*;import java.awt.*; import java.util.ArrayList; import java.util.List;/*** 坦克類*/ public abstract class Tank {//四個方向public static final int DIR_UP = 0;public static final int DIR_DOWN = 1;public static final int DIR_LEFT = 2;public static final int DIR_RIGHT = 3;//半徑public static final int RADIUS = 20;//默認速度 每幀 30mspublic static final int DEFAULT_SPEED = 4;//坦克的狀態public static final int STATE_STAND = 0;public static final int STATE_MOVE = 1;public static final int STATE_DIE = 2;//坦克的初始生命public static final int DEFAULT_HP = 100;private int maxHP = DEFAULT_HP;private int x,y;private int hp = DEFAULT_HP;private String name;private int atk;public static final int ATK_MAX = 25;public static final int ATK_MIN = 15;private int speed = DEFAULT_SPEED;private int dir;private int state = STATE_STAND;private Color color;private boolean isEnemy = false;private BloodBar bar = new BloodBar();//炮彈private List<Bullet> bullets = new ArrayList();//使用容器來保存當前坦克上的所有的爆炸的效果private List<Explode> explodes = new ArrayList<>();public Tank(int x, int y, int dir) {this.x = x;this.y = y;this.dir = dir;initTank();}public Tank(){initTank();}private void initTank(){color = MyUtil.getRandomColor();name = MyUtil.getRandomName();atk = MyUtil.getRandomNumber(ATK_MIN,ATK_MAX);}/*** 繪制坦克* @param g*/public void draw(Graphics g){logic();drawImgTank(g);drawBullets(g);drawName(g);bar.draw(g);}private void drawName(Graphics g){g.setColor(color);g.setFont(Constant.SMALL_FONT);g.drawString(name, x - RADIUS ,y - 35);}/*** 使用圖片的方式去繪制坦克* @param g*/public abstract void drawImgTank(Graphics g);/*** 使用系統的方式去繪制坦克* @param g*/private void drawTank(Graphics g){g.setColor(color);//繪制坦克的圓g.fillOval(x-RADIUS,y-RADIUS,RADIUS<<1,RADIUS<<1);int endX = x;int endY = y;switch(dir){case DIR_UP:endY = y - RADIUS*2;g.drawLine(x-1,y,endX-1,endY);g.drawLine(x+1,y,endX+1,endY);break;case DIR_DOWN:endY = y + RADIUS*2;g.drawLine(x-1,y,endX-1,endY);g.drawLine(x+1,y,endX+1,endY);break;case DIR_LEFT:endX = x - 2 * RADIUS;g.drawLine(x,y-1,endX,endY-1);g.drawLine(x,y+1,endX,endY+1);break;case DIR_RIGHT:endX = x + 2 * RADIUS;g.drawLine(x,y-1,endX,endY-1);g.drawLine(x,y+1,endX,endY+1);break;}g.drawLine(x,y,endX,endY);}//坦克的邏輯處理private void logic(){switch(state){case STATE_STAND:break;case STATE_MOVE:move();break;case STATE_DIE:break;}}private int oldX = -1, oldY = -1;//坦克移動的功能private void move(){oldX = x;oldY = y;switch (dir){case DIR_UP:y -= speed;if(y < RADIUS + GameFrame.titleBarH){y = RADIUS + GameFrame.titleBarH;}break;case DIR_DOWN:y += speed;if(y > Constant.FRAME_HEIGHT-RADIUS){y = Constant.FRAME_HEIGHT-RADIUS;}break;case DIR_LEFT:x -= speed;if(x < RADIUS){x = RADIUS;}break;case DIR_RIGHT:x += speed;if(x > Constant.FRAME_WIDTH-RADIUS){x = Constant.FRAME_WIDTH-RADIUS;}break;}}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 getHp() {return hp;}public void setHp(int hp) {this.hp = hp;}public int getAtk() {return atk;}public void setAtk(int atk) {this.atk = atk;}public int getSpeed() {return speed;}public void setSpeed(int speed) {this.speed = speed;}public int getDir() {return dir;}public void setDir(int dir) {this.dir = dir;}public int getState() {return state;}public void setState(int state) {this.state = state;}public Color getColor() {return color;}public void setColor(Color color) {this.color = color;}public List getBullets() {return bullets;}public void setBullets(List bullets) {this.bullets = bullets;}public boolean isEnemy() {return isEnemy;}public void setEnemy(boolean enemy) {isEnemy = enemy;}public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic String toString() {return "Tank{" +"x=" + x +", y=" + y +", hp=" + hp +", atk=" + atk +", speed=" + speed +", dir=" + dir +", state=" + state +'}';}//上一次開火的時間private long fireTime;//子彈發射的最小的間隔public static final int FIRE_INTERVAL = 200;/*** 坦克的功能,坦克開火的方法* 創建了一個子彈對象,子彈對象的屬性信息通過坦克的信息獲得* 然后將創建的子彈添加到坦克管理的容器中。*/public void fire(){if(System.currentTimeMillis() - fireTime >FIRE_INTERVAL) {int bulletX = x;int bulletY = y;switch (dir) {case DIR_UP:bulletY -= RADIUS;break;case DIR_DOWN:bulletY += RADIUS;break;case DIR_LEFT:bulletX -= RADIUS;break;case DIR_RIGHT:bulletX += RADIUS;break;}//從對象池中獲取子彈對象Bullet bullet = BulletsPool.get();//設置子彈的屬性bullet.setX(bulletX);bullet.setY(bulletY);bullet.setDir(dir);bullet.setAtk(atk);bullet.setColor(color);bullet.setVisible(true);bullets.add(bullet);//發射子彈之后,記錄本次發射的時間fireTime = System.currentTimeMillis();// MusicUtil.playBomb();}}/*** 將當前坦克的發射的所有的子彈繪制出來*/private void drawBullets(Graphics g){ // for (Bullet bullet : bullets) { // bullet.draw(g); // }for (int i = 0; i < bullets.size(); i++) {Bullet bullet = bullets.get(i);bullet.draw(g);}//是遍歷所有的子彈,將不可見的子彈移除,并還原回對象池for (int i = 0; i < bullets.size(); i++) {Bullet bullet = bullets.get(i);if(!bullet.isVisible()) {Bullet remove = bullets.remove(i);i--;BulletsPool.theReturn(remove);}}}/*** 坦克銷毀的時候處理坦克的所有的子彈*/public void bulletsReturn(){ // for (Bullet bullet : bullets) { // BulletsPool.theReturn(bullet); // }for (int i = 0; i < bullets.size(); i++) {Bullet bullet = bullets.get(i);BulletsPool.theReturn(bullet);}bullets.clear();}//坦克和敵人的子彈碰撞的方法。public void collideBullets(List<Bullet> bullets){for (int i = 0; i < bullets.size(); i++) {Bullet bullet = bullets.get(i); // }// 遍歷所有的子彈,依次和當前的坦克進行碰撞的檢測 // for (Bullet bullet : bullets) {int bulletX = bullet.getX();int bulletY = bullet.getY();//子彈和坦克碰上了。if(MyUtil.isCollide(this.x,y,RADIUS,bulletX,bulletY)){//子彈消失bullet.setVisible(false);//坦克受到傷害hurt(bullet);//添加爆炸效果addExplode(x,y+RADIUS);}}}private void addExplode(int x,int y){//添加爆炸效果,以當前被擊中的坦克的坐標為參考Explode explode = ExplodesPool.get();explode.setX(x);explode.setY(y);explode.setVisible(true);explode.setIndex(0);explodes.add(explode);}//坦克收到傷害private void hurt(Bullet bullet){int atk = bullet.getAtk();System.out.println("atk = "+atk);hp -= atk;if(hp < 0){hp = 0;die();}}//坦克死亡需要處理的內容private void die(){//敵人if(isEnemy){GameFrame.killEnemyCount ++;//敵人坦克被消滅了 歸還對象池EnemyTanksPool.theReturn(this);//本關是否結束? TODOif (GameFrame.isCrossLevel()){//判斷游戲是否通關了?if(GameFrame.isLastLevel()){//通關了GameFrame.setGameState(Constant.STATE_WIN);}else {//TODO 進入下一關GameFrame.startCrossLevel();}}}else{delaySecondsToOver(3000);}}/*** 判斷當前的坦克是否死亡* @return*/public boolean isDie(){return hp <= 0;}/*** 繪制當前坦克上的所有的爆炸的效果* @param g*/public void drawExplodes(Graphics g){ // for (Explode explode : explodes) {for (int i = 0; i < explodes.size(); i++) {Explode explode = explodes.get(i);explode.draw(g);}//將不可見的爆炸效果刪除,還回對象池for (int i = 0; i < explodes.size(); i++) {Explode explode = explodes.get(i);if(!explode.isVisible()){Explode remove = explodes.remove(i);ExplodesPool.theReturn(remove);i--;}}}//內部類,來表示坦克的血條class BloodBar{public static final int BAR_LENGTH = 50;public static final int BAR_HEIGHT = 3;public void draw(Graphics g){//填充底色g.setColor(Color.YELLOW);g.fillRect(x - RADIUS , y-RADIUS-BAR_HEIGHT*2,BAR_LENGTH,BAR_HEIGHT);//紅色的當前血量g.setColor(Color.RED);g.fillRect(x - RADIUS , y-RADIUS-BAR_HEIGHT*2,hp*BAR_LENGTH/maxHP,BAR_HEIGHT);//藍色的邊框g.setColor(Color.WHITE);g.drawRect(x - RADIUS , y-RADIUS-BAR_HEIGHT*2,BAR_LENGTH,BAR_HEIGHT);}}//坦克的子彈和地圖所有的塊的碰撞public void bulletsCollideMapTiles(List<MapTile> tiles){//foreach遍歷容器中的元素,//在遍歷的過程中只能使用迭代器的刪除方式刪除元素//都切換為使用基本的for循環 // for (MapTile tile : tiles) {for (int i = 0; i < tiles.size(); i++) {MapTile tile = tiles.get(i);if(tile.isCollideBullet(bullets)){//添加爆炸效果addExplode(tile.getX()+MapTile.radius,tile.getY() +MapTile.tileW);//地圖水泥塊沒有擊毀的處理if(tile.getType() == MapTile.TYPE_HARD)continue;//設置地圖塊銷毀tile.setVisible(false);//歸還對象池MapTilePool.theReturn(tile);//當老巢被擊毀之后,一秒鐘切換到游戲結束的畫面if(tile.isHouse()){delaySecondsToOver(3000);}}}// }}/*** 延遲 若干毫秒 切換到 游戲結束* @param millisSecond*/private void delaySecondsToOver(int millisSecond){new Thread(){public void run() {try {Thread.sleep(millisSecond);} catch (InterruptedException e) {e.printStackTrace();}GameFrame.setGameState(Constant.STATE_LOST);}}.start();}/*** 一個地圖塊和當前的坦克碰撞的方法* 從tile 中提取8個點 來判斷 8個點是否有任何一個點和當前的坦克有了碰撞* 點的順序從左上角的點開始,順時針遍歷*/public boolean isCollideTile(List<MapTile> tiles){final int len = 2;for (int i = 0; i < tiles.size(); i++) {MapTile tile = tiles.get(i); // } // for (MapTile tile : tiles) {//如果塊不可見,或者是遮擋塊就不進行碰撞的檢測if(!tile.isVisible() || tile.getType() == MapTile.TYPE_COVER)continue;//點-1 左上角int tileX = tile.getX();int tileY = tile.getY();boolean collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);//如果碰上了就直接返回,否則繼續判斷下一個點if(collide){return true;}//點-2 中上點tileX += MapTile.radius;collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);if(collide){return true;}//點-3 右上角點tileX += MapTile.radius;collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);if(collide){return true;}//點-4 右中點tileY += MapTile.radius;collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);if(collide){return true;}//點-5 右下點tileY += MapTile.radius;collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);if(collide){return true;}//點-6 下中點tileX -= MapTile.radius;collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);if(collide){return true;}//點-7 左下點tileX -= MapTile.radius;collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);if(collide){return true;}//點-8 左中點tileY -= MapTile.radius;collide = MyUtil.isCollide(x, y, RADIUS, tileX, tileY);if(collide){return true;}}return false;}/*** 坦克回退的方法*/public void back(){x = oldX;y = oldY;}public int getMaxHP() {return maxHP;}public void setMaxHP(int maxHP) {this.maxHP = maxHP;} }地圖類
20: 地圖? 地圖元素類
設計:上、左右,距離窗口的寬度為1.5倍的坦克寬度。
21:實現一張地圖的繪制顯示
22:優化了地圖隨機初始化。子彈和地圖碰撞
23:坦克和地圖的碰撞
24:敵人坦克和地圖的碰撞
25:玩家的老巢,老巢被擊毀 游戲結束。延遲若干秒
26:控制玩家的發射炮彈的速度(最小間隔,200毫秒)
27:添加其他種類的地圖元素
不可擊毀的地圖元素
遮擋物
28:添加不同地圖的種類
package com.lwj.map; import com.lwj.game.GameFrame; import com.lwj.game.LevelInof; import com.lwj.tank.Tank; import com.lwj.util.Constant; import com.lwj.util.MapTilePool; import com.lwj.util.MyUtil;import java.awt.*; import java.io.FileInputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Properties;/*** 游戲地圖類*/ public class GameMap {public static final int MAP_X = Tank.RADIUS*3;public static final int MAP_Y = Tank.RADIUS*3 + GameFrame.titleBarH;public static final int MAP_WIDTH = Constant.FRAME_WIDTH-Tank.RADIUS*6;public static final int MAP_HEIGHT = Constant.FRAME_HEIGHT-Tank.RADIUS*8-GameFrame.titleBarH;//地圖元素塊的容器private List<MapTile> tiles = new ArrayList<>();//大本營private TankHouse house;public GameMap() {}/*** 初始化地圖元素塊,level : 第幾關*/public void initMap(int level){tiles.clear();try {loadLevel(level);} catch (Exception e) {e.printStackTrace();}//初始化大本營house = new TankHouse();addHouse();}/*** 加載關卡信息* @param level*/private void loadLevel(int level) throws Exception{//獲得關卡信息類的唯一實例對象LevelInof levelInof = LevelInof.getInstance();levelInof.setLevel(level);Properties prop = new Properties();prop.load(new FileInputStream("level/lv_"+level));//將所有的地圖信息都加載進來int enemyCount = Integer.parseInt(prop.getProperty("enemyCount"));//設置敵人數量levelInof.setEnemyCount(enemyCount);//0,1 對敵人類型解析String[] enemyType = prop.getProperty("enemyType").split(",");int[] type = new int[enemyType.length];for (int i = 0; i < type.length; i++) {type[i] = Integer.parseInt(enemyType[i]);}//設置敵人類型levelInof.setEnemyType(type);//關卡難度//如果沒有設計游戲難度,那么就當默認的處理String levelType = prop.getProperty("levelType");levelInof.setLevelType(Integer.parseInt(levelType==null? "1" : levelType));String methodName = prop.getProperty("method");int invokeCount = Integer.parseInt(prop.getProperty("invokeCount"));//把實參都讀取到數組中來。String[] params = new String[invokeCount];for (int i = 1; i <=invokeCount ; i++) {params[i-1] = prop.getProperty("param"+i);}//使用讀取到的參數,調用對應的方法。invokeMethod(methodName,params);}//根據方法的名字和參數調用對應的方法private void invokeMethod(String name,String[] params){for (String param : params) {//獲得每一行的方法的參數,解析。String[] split = param.split(",");//使用一個int 數組保存解析后的內容int[] arr = new int[split.length];int i;for (i = 0; i < split.length-1; i++) {arr[i] = Integer.parseInt(split[i]);}//塊之間的間隔是地圖塊的倍數final int DIS = MapTile.tileW ;//解析最后一個double值int dis = (int)(Double.parseDouble(split[i])*DIS);switch(name){case "addRow":addRow(MAP_X+arr[0]*DIS,MAP_Y+arr[1]*DIS,MAP_X+MAP_WIDTH-arr[2]*DIS,arr[3],dis);break;case "addCol":addCol(MAP_X+arr[0]*DIS,MAP_Y+arr[1]*DIS,MAP_Y+MAP_HEIGHT-arr[2]*DIS,arr[3],dis);break;case "addRect":addRect(MAP_X+arr[0]*DIS,MAP_Y+arr[1]*DIS,MAP_X+MAP_WIDTH-arr[2]*DIS,MAP_Y+MAP_HEIGHT-arr[3]*DIS,arr[4],dis);break;}}}//將老巢的所有的元素塊添加到地圖的容器中private void addHouse(){tiles.addAll(house.getTiles());}/*** 某一個點確定的地圖塊。是否和 tiles 集合中的所有的塊 有重疊的部分* @param tiles* @param x* @param y* @return 有重疊返回 true,否則 false*/private boolean isCollide(List<MapTile> tiles, int x ,int y){for (MapTile tile : tiles) {int tileX = tile.getX();int tileY = tile.getY();if(Math.abs(tileX-x) < MapTile.tileW && Math.abs(tileY-y) < MapTile.tileW){return true;}}return false;}/*** 只對沒有遮擋效果的塊進行繪制* @param g*/public void drawBk(Graphics g){for (MapTile tile : tiles) {if(tile.getType() != MapTile.TYPE_COVER)tile.draw(g);}}/*** 只繪制有遮擋效果的塊* @param g*/public void drawCover(Graphics g){for (MapTile tile : tiles) {if(tile.getType() == MapTile.TYPE_COVER)tile.draw(g);}}public List<MapTile> getTiles() {return tiles;}/*** 將所有的不可見的地圖塊從容器中移除*/public void clearDestroyTile(){for (int i = 0; i < tiles.size(); i++) {MapTile tile = tiles.get(i);if(!tile.isVisible())tiles.remove(i);}}/***往地圖塊容器中添加一行指定類型的地圖塊* @param startX 添加地圖塊的起始的x坐標* @param startY 添加地圖塊的起始的Y坐標* @param endX 添加地圖塊的結束的x坐標* @param type 地圖塊的類型* @param DIS 地圖塊之間的中心點的間隔 如果是塊的寬度 意味著是連續的,* 如果大于塊的寬度就是不連續的*/public void addRow(int startX,int startY, int endX, int type, final int DIS){int count = (endX - startX +DIS )/(MapTile.tileW+DIS);for (int i = 0; i <count ; i++) {MapTile tile = MapTilePool.get();tile.setType(type);tile.setX(startX + i * (MapTile.tileW+DIS));tile.setY(startY);tiles.add(tile);}}/*** 往地圖元素塊容器中添加一列 元素。* @param startX 該列的起始x坐標* @param startY 該列的起始y坐標* @param endY 改了的結束y坐標* @param type 元素類型* @param DIS 相鄰元素中心點的間距*/public void addCol(int startX,int startY, int endY, int type, final int DIS){int count = (endY - startY +DIS)/(MapTile.tileW+DIS);for (int i = 0; i <count ; i++) {MapTile tile = MapTilePool.get();tile.setType(type);tile.setX(startX );tile.setY(startY + i * (MapTile.tileW+DIS));tiles.add(tile);}}//對指定的矩形區域添加元素塊public void addRect(int startX,int startY,int endX, int endY, int type, final int DIS){int rows = (endY-startY+DIS)/(MapTile.tileW+DIS);for (int i = 0; i <rows ; i++) {addRow(startX,startY+i*(MapTile.tileW+DIS),endX,type,DIS);}}}運行結果如下
游戲常量類
/*** 游戲常量類*/ public class Constant {/****************游戲窗口相關屬性*********************/public static final String GAME_TITLE = "坦克大戰";public static final int FRAME_WIDTH = 1000;public static final int FRAME_HEIGHT = 900;//獲取系統屏幕的寬高public static final int SCREEN_W = Toolkit.getDefaultToolkit().getScreenSize().width;public static final int SCREEN_H = Toolkit.getDefaultToolkit().getScreenSize().height;//游戲窗口頂點坐標public static final int FRAME_X = SCREEN_W - FRAME_WIDTH >> 1;public static final int FRAME_Y = SCREEN_H - FRAME_HEIGHT >> 1;/****************游戲菜單相關屬性********************************/public static final int STATE_MENU = 0;public static final int STATE_HELP = 1;public static final int STATE_ABOUT = 2;public static final int STATE_RUN = 3;public static final int STATE_OVER = 4;public static final String[] MENUS = {"開始游戲","繼續游戲","游戲幫助","游戲相關","退出游戲"};public static final Font GAME_FONT = new Font("宋體", Font.BOLD, 24);//窗口刷新間隔public static final int REPAINT_INTERVAL = 30;//最大敵人數量public static final int ENEMY_MAX_COUNT = 10;//敵方坦克生產間隔public static final int ENEMY_BORN_INTERVAL = 5000;//敵方坦克狀態切換的間隔public static final int ENEMY_AI_INTERVAL = 1000;//敵方坦克每幀發射子彈的概率public static final double ENEMY_AI_PERCENT = 0.10; } Size().width;public static final int SCREEN_H = Toolkit.getDefaultToolkit().getScreenSize().height;//游戲窗口頂點坐標public static final int FRAME_X = SCREEN_W - FRAME_WIDTH >> 1;public static final int FRAME_Y = SCREEN_H - FRAME_HEIGHT >> 1;/****************游戲菜單相關屬性********************************/public static final int STATE_MENU = 0;public static final int STATE_HELP = 1;public static final int STATE_ABOUT = 2;public static final int STATE_RUN = 3;public static final int STATE_OVER = 4;public static final String[] MENUS = {"開始游戲","繼續游戲","游戲幫助","游戲相關","退出游戲"};public static final Font GAME_FONT = new Font("宋體", Font.BOLD, 24);//窗口刷新間隔public static final int REPAINT_INTERVAL = 30;//最大敵人數量public static final int ENEMY_MAX_COUNT = 10;//敵方坦克生產間隔public static final int ENEMY_BORN_INTERVAL = 5000;//敵方坦克狀態切換的間隔public static final int ENEMY_AI_INTERVAL = 1000;//敵方坦克每幀發射子彈的概率public static final double ENEMY_AI_PERCENT = 0.10; }優化 :添加背景音樂和音效
33 : 添加了游戲信息類,獲得了所有的關卡的數量。游戲通關的判斷
34:過關判斷,通關的處理。
35:切換關卡的處理
?操作演示視頻
坦克大戰 2022-06-16 20-40-03
總結
- 上一篇: 详述人工智能在自动驾驶中的应用
- 下一篇: java美元兑换,(Java实现) 美元