今天给大家分享一个使用java编写的坦克大战小游戏,整体还是挺好玩的,通过对这款游戏的简单实现,加深对java基础的深刻理解。
一、设计思路
1.坦克大战小游戏通过java实现,其第一步需要先绘制每一关对应的地图,地图包括河流、草地、砖块、铁块等。
2.需要绘制玩家坦克、敌方坦克、以及坦克移动过程中使用到的碰撞算法,子弹与坦克之间的碰撞,
包括击中敌方坦克后的爆炸效果,通过重绘实现坦克的移动,以及各种道具的随机生成算法。
实际运行效果如下:
二、代码实现
1.首先需要将游戏中涉及到的各种对象梳理清楚,由于Java面向对象的特征,可以将一些对象公共特性抽象出来,比如将坦克抽象出一个超类,代码如下:
public abstract class Tank{int x=0;int y=0;int tempX=0;int tempY=0;int size=32;int direct=Constant.UP;int speed=1;int lives=1;int frame=0;//控制敌方坦克切换方向的时间boolean isAI=false;//是否自动boolean hit;boolean isShooting=false;boolean isDestroyed=false;boolean isProtected=false;Map map;GameMain gameMain;Collision collision;Bullet bullet;int starNum=0; //星星数public Tank(int size,int speed,Collision collision,Map map,GameMain gameMain) {this.size=size;this.collision=collision;this.map=map;this.gameMain=gameMain;this.speed=speed; }public void move() {//如果是AI坦克,在一定时间或碰撞后切换方法if(this.isAI && gameMain.enemyStopTime>0) {return;}this.tempX=this.x;this.tempY=this.y;if(this.isAI) {this.frame++;if(this.frame%100==0 || this.hit) {this.direct=(int)(Math.random()*4);this.hit=false;this.frame=0;}}if(this.direct==Constant.UP) {this.tempY-=this.speed;}else if(this.direct==Constant.DOWN) {this.tempY+=this.speed;}else if(this.direct==Constant.LEFT) {this.tempX-=this.speed;}else if(this.direct==Constant.RIGHT) {this.tempX+=this.speed;}isHit();if(!this.hit) {this.x=this.tempX;this.y=this.tempY;}}public void isHit() {if(this.direct==Constant.LEFT) {if(this.x<=map.offsetX) {this.x=map.offsetX;this.hit=true;}}else if(this.direct==Constant.RIGHT) {if(this.x>=map.offsetX+map.mapWidth-this.size) {this.x=map.offsetX+map.mapWidth-this.size;this.hit=true;}}else if(this.direct==Constant.UP) {if(this.y<=map.offsetY) {this.y=map.offsetY;this.hit=true;}}else if(this.direct==Constant.DOWN) {if(this.y>=map.offsetY+map.mapHeight-this.size) {this.y=map.offsetY+map.mapHeight-this.size;this.hit=true;}}if(!this.hit) {if(collision.tankMapCollision(this, map)) {this.hit=true;}}}public abstract void drawTank(Graphics2D ctx2);public void shoot(int type,Graphics2D ctx2) {if(this.isAI && gameMain.enemyStopTime>0) {return;}if(this.isShooting) {return;}int tempX=this.x;int tempY=this.y;this.bullet=new Bullet(tempX, tempY, this.direct, gameMain, type, map, this, collision);if(!this.isAI) {if(this.starNum==0) {this.bullet.speed=3;this.bullet.fireNum=1;}if(this.starNum>0 && this.starNum<=3) {this.bullet.speed+=this.starNum;this.bullet.fireNum+=(this.starNum-1);}if(this.starNum>3) {this.bullet.speed+=3;this.bullet.fireNum+=3;}}if(this.direct==Constant.UP) {tempX = this.x + this.size/2 - this.bullet.size/2;tempY=this.y-this.bullet.size;}else if(this.direct==Constant.DOWN) {tempX = this.x + this.size/2 - this.bullet.size/2;tempY=this.y+this.bullet.size;}else if(this.direct==Constant.LEFT) {tempX=this.x-this.bullet.size;tempY=this.y + this.size/2 - this.bullet.size/2;}else if(this.direct==Constant.RIGHT) {tempX=this.x+this.bullet.size;tempY=this.y + this.size/2 - this.bullet.size/2;}this.bullet.x=tempX;this.bullet.y=tempY;if(!this.isAI) {//音乐Constant.executor.execute(new Runnable() {@Overridepublic void run() {Constant.ATTACK_AUDIO.play();}});}this.bullet.drawBullet(ctx2);gameMain.bulletArr.add(this.bullet);this.isShooting=true;}public void distroy() {this.isDestroyed=true;gameMain.crackArr.add(new CrackAnimation(gameMain, Constant.CRACK_TYPE_TANK, this));if(this.isAI) {Constant.executor.execute(new Runnable() {@Overridepublic void run() {Constant.TANK_DESTROY_AUDIO.play();}});gameMain.appearEnemy--;}else {Constant.executor.execute(new Runnable() {@Overridepublic void run() {Constant.PLAYER_DESTROY_AUDIO.play();}});this.starNum=0;}}
}
2.坦克子类实现
public class PlayTank extends Tank{int protectedTime = 0;//保护时间int offsetX=0; //坦克2与坦克1的距离int type; //玩家类型public PlayTank(int type,Collision collision, Map map, GameMain gameMain) {super(32, 2, collision, map, gameMain);this.lives = 3;//生命值this.isProtected = true;//是否受保护this.protectedTime = 500;//保护时间this.type=type;}@Overridepublic void drawTank(Graphics2D ctx2) {this.hit = false;if(this.type==1) {ctx2.drawImage(Constant.RESOURCE_IMAGE, this.x, this.y,this.x+this.size,this.y+this.size,Constant.POS.get("player").x+this.offsetX+this.direct*this.size, Constant.POS.get("player").y,Constant.POS.get("player").x+this.offsetX+this.direct*this.size+this.size, Constant.POS.get("player").y+this.size, null);}else {ctx2.drawImage(Constant.RESOURCE_IMAGE, this.x, this.y,this.x+this.size,this.y+this.size,Constant.POS.get("player").x+128+this.offsetX+this.direct*this.size, Constant.POS.get("player").y,Constant.POS.get("player").x+128+this.offsetX+this.direct*this.size+this.size, Constant.POS.get("player").y+this.size, null);}if(this.isProtected) {int temp=((500-protectedTime)/5)%2;ctx2.drawImage(Constant.RESOURCE_IMAGE, this.x, this.y,this.x+this.size,this.y+this.size,Constant.POS.get("protected").x, Constant.POS.get("protected").y+32*temp,Constant.POS.get("protected").x+this.size, Constant.POS.get("protected").y+this.size, null);this.protectedTime--;if(this.protectedTime==0) {this.isProtected=false;}}}/*** 玩家坦克复活* @param player*/public void renascenc(int playerType) {this.lives--;this.direct=Constant.UP;this.isProtected=true;this.protectedTime=500;this.isDestroyed = false;int temp=0;if(playerType==1) {temp=129;}else {temp=256;}this.x = temp + map.offsetX;this.y = 385 + map.offsetY;}}
3.通过子类继承父类实现玩家坦克和敌方坦克的创建,创建完后,接下来就可让坦克动起来,如下:
public void addEnemyTank(Graphics2D ctx2) {if(enemyArr == null || enemyArr.size() >= maxAppearEnemy || maxEnemy == 0){return;}appearEnemy++;Tank objTank=null;int rand=(int) (Math.random()*4);if(rand==0) {objTank=new TankEnemy0(collision, map, this);}else if(rand==1) {objTank=new TankEnemy1(collision, map, this);}else if(rand==2) {objTank=new TankEnemy2(collision, map, this);}else if(rand==3) {objTank=new TankEnemy3(collision, map, this);}enemyArr.add(objTank);this.maxEnemy--;map.clearEnemyNum(maxEnemy, appearEnemy,ctx2);}
4.在移动过程中,涉及到坦克与子弹,坦克与地图之间的碰撞问题,实现如下:
/**
* 坦克与地图间的碰撞
**/
public boolean tankMapCollision(Tank tank,Map map) {//移动检测,记录最后一次的移动方向,根据方向判断+-overlap;int tileNum=0;int rowIndex=0;int colIndex=0;int overlap=3; //允许重叠的大小//根据tank的x、y计算map的row和colif(tank.direct==Constant.UP) {rowIndex=(tank.tempY+overlap-map.offsetY)/map.tileSize;colIndex=(tank.tempX+overlap-map.offsetX)/map.tileSize;}else if(tank.direct==Constant.DOWN) {rowIndex=(tank.tempY-overlap-map.offsetY+tank.size)/map.tileSize;colIndex=(tank.tempX+overlap-map.offsetX)/map.tileSize;}else if(tank.direct==Constant.LEFT) {rowIndex=(tank.tempY+overlap-map.offsetY)/map.tileSize;colIndex=(tank.tempX+overlap-map.offsetX)/map.tileSize;}else if(tank.direct==Constant.RIGHT){rowIndex=(tank.tempY+overlap-map.offsetY)/map.tileSize;colIndex=(tank.tempX-overlap-map.offsetX+tank.size)/map.tileSize;}if(rowIndex>=map.hTileCount || rowIndex<0 || colIndex>=map.wTileCount || colIndex<0) {return true;}if(tank.direct==Constant.UP || tank.direct==Constant.DOWN) {int tempWidth=tank.tempX-map.offsetX-colIndex*map.tileSize+tank.size-overlap;if(tempWidth%map.tileSize==0) {tileNum=tempWidth/map.tileSize;}else {tileNum=tempWidth/map.tileSize+1;}for(int i=0;i<tileNum && colIndex+i<map.wTileCount;i++) {int mapContent=map.mapLevel[rowIndex][colIndex+i];if(mapContent==Constant.WALL || mapContent==Constant.GRID || mapContent==Constant.WATER || mapContent==Constant.HOME || mapContent==Constant.ANOTHER_HOME) {if(tank.direct==Constant.UP) {tank.y=map.offsetY+rowIndex*map.tileSize+map.tileSize-overlap;}else {tank.y=map.offsetY+rowIndex*map.tileSize-tank.size+overlap;}return true;}}}else {int tempHeight=tank.tempY-map.offsetY-rowIndex*map.tileSize+tank.size-overlap;if(tempHeight%map.tileSize==0) {tileNum=tempHeight/map.tileSize;}else {tileNum=tempHeight/map.tileSize+1;}for(int i=0;i<tileNum && rowIndex+i<map.hTileCount;i++) {int mapContent=map.mapLevel[rowIndex+i][colIndex];if(mapContent==Constant.WALL || mapContent==Constant.GRID || mapContent==Constant.WATER || mapContent==Constant.HOME || mapContent==Constant.ANOTHER_HOME) {if(tank.direct==Constant.LEFT) {tank.x=map.offsetX+colIndex*map.tileSize+map.tileSize-overlap;}else {tank.x=map.offsetX+colIndex*map.tileSize-tank.size+overlap;}return true;}}}return false;}
5.绘制界面,通过定时重绘实现,具体代码如下:
public class GameMain extends JPanel{//int enemyNum=12;Map map;Num num;int level=1;Level tankLevel;int gameState=Constant.GAME_STATE_MENU;private boolean isGameOver=false;int maxEnemy = 12;//敌方坦克总数int maxAppearEnemy = 5;//屏幕上一起出现的最大数int appearEnemy = 0; //已出现的敌方坦克int enemyStopTime=0;List<Bullet> bulletArr;List<Tank> enemyArr;List<CrackAnimation> crackArr;List<Integer> keys;Tank player1 = null;//玩家1Tank player2 = null;//玩家2Collision collision;int mainframe = 0;Image offScreenImage;Menu menu;Stage stage;Prop prop;Graphics2D ctx2;int overX = 176;int overY = 384;int propTime = 300; //道具出现频次int homeProtectedTime = -1;int winWaitTime=80;public void initGame(GameMain gameMain) {this.num=new Num(gameMain);this.tankLevel=new Level();this.collision=new Collision(gameMain);}public void initObject() {this.map=new Map(this, num, tankLevel, level);player1=new PlayTank(1,collision, map, this);player1.x = 129 + map.offsetX;player1.y = 385 + map.offsetY;player2 = new PlayTank(2,collision, map, this);player2.x = 256 + map.offsetX;player2.y = 385 + map.offsetY;bulletArr=new ArrayList<>();enemyArr=new ArrayList<>();crackArr=new ArrayList<>();keys=new ArrayList<>();menu=new Menu();stage=new Stage(level, this,this.num);this.isGameOver=false;this.propTime=400;this.homeProtectedTime = -1;this.maxEnemy=12;this.winWaitTime=50;this.appearEnemy=0;this.overY=384;}public void goGameOver() {this.isGameOver=true;}@Overridepublic void paint(Graphics g) {//创建和容器一样大小的Image图片if(offScreenImage==null) {offScreenImage=this.createImage(Constant.SCREEN_WIDTH,Constant.SCREEN_HEIGHT);}//获得该图片的画布Graphics gImage=offScreenImage.getGraphics();//填充整个画布gImage.fillRect(0,0,Constant.SCREEN_WIDTH,Constant.SCREEN_HEIGHT);if(ctx2==null) {ctx2=(Graphics2D)gImage;}if(gameState==Constant.GAME_STATE_MENU) {menu.drawMenu(ctx2);}if(gameState==Constant.GAME_STATE_INIT) {stage.setNum(ctx2);stage.drawStage(ctx2);}if(gameState==Constant.GAME_STATE_START || gameState==Constant.GAME_STATE_OVER) {drawAll(ctx2);}if(gameState==Constant.GAME_STATE_OVER) {gameOver();}if(gameState==Constant.GAME_STATE_WIN) {this.winWaitTime--;drawAll(ctx2);if(this.winWaitTime==0) {nextLevel();}}//将缓冲区绘制好的图形整个绘制到容器的画布中g.drawImage(offScreenImage,0,0,null);}public void initMap(Graphics2D ctx2) {map.setNum(ctx2);map.drawMap(ctx2);}public void drawAll(Graphics2D ctx2) {map.setNum(ctx2);map.drawMap(ctx2);if(player1.lives>0) {player1.drawTank(ctx2);}if(player2.lives>0) {player2.drawTank(ctx2);}if(appearEnemy<maxEnemy){if(mainframe % 100 == 0){addEnemyTank(ctx2);mainframe = 0;}mainframe++;}drawLives(ctx2);drawEnemyTanks(ctx2);map.drawGrassMap(ctx2);drawBullet(ctx2);drawCrack(ctx2);keyEvent();if(propTime<=0){drawProp(ctx2);}else{propTime --;}if(homeProtectedTime > 0){homeProtectedTime --;}else if(homeProtectedTime == 0){homeProtectedTime = -1;homeNoProtected(ctx2);}}public void addEnemyTank(Graphics2D ctx2) {if(enemyArr == null || enemyArr.size() >= maxAppearEnemy || maxEnemy == 0){return;}appearEnemy++;Tank objTank=null;int rand=(int) (Math.random()*4);if(rand==0) {objTank=new TankEnemy0(collision, map, this);}else if(rand==1) {objTank=new TankEnemy1(collision, map, this);}else if(rand==2) {objTank=new TankEnemy2(collision, map, this);}else if(rand==3) {objTank=new TankEnemy3(collision, map, this);}enemyArr.add(objTank);this.maxEnemy--;map.clearEnemyNum(maxEnemy, appearEnemy,ctx2);}public void drawEnemyTanks(Graphics2D ctx2) {if(enemyArr!=null && enemyArr.size()>0) {Iterator<Tank> it=enemyArr.iterator();while(it.hasNext()) {Tank enemyTank=it.next();if(enemyTank.isDestroyed) {it.remove();}else {enemyTank.drawTank(ctx2);}}}if(enemyStopTime > 0){enemyStopTime --;}}/*** 绘制玩家生命数*/public void drawLives(Graphics2D ctx2) {map.drawLives(player1.lives, 1,ctx2);map.drawLives(player2.lives, 2,ctx2);}/*** 绘制子弹* @param ctx2*/public void drawBullet(Graphics2D ctx2) {if(bulletArr != null && bulletArr.size() > 0){Iterator<Bullet> it=bulletArr.iterator();while(it.hasNext()) {Bullet bullet=it.next();if(bullet.isDestroyed) {it.remove();bullet.owner.isShooting=false;}else {//绘制子弹bullet.drawBullet(ctx2);}}}}public void drawCrack(Graphics2D ctx2) {if(crackArr != null && crackArr.size() > 0){Iterator<CrackAnimation> it=crackArr.iterator();while(it.hasNext()) {CrackAnimation crackAnimation=it.next();if(crackAnimation.isOver) {it.remove();if(crackAnimation.crackObj instanceof Tank) {Tank tank=(Tank)crackAnimation.crackObj;if(tank==player1) {PlayTank playerTank1=(PlayTank)player1;playerTank1.renascenc(1);}else if(tank==player2) {PlayTank playerTank2=(PlayTank)player2;playerTank2.renascenc(2);}}}else {//绘制爆炸效果crackAnimation.draw(ctx2);}}}}public void drawProp(Graphics2D ctx2) {double rand=Math.random();if(rand<=0.4 && prop==null) {prop=new Prop(this, map, collision);prop.initProp();}if(prop!=null) {prop.drawProp(ctx2);if(prop.isDestroyed) {prop=null;propTime=600;}}}public void nextLevel() {level++;if(level==17) {level=1;}int oldPlayerNum=menu.playNum;initObject();menu.playNum = oldPlayerNum;//只有一个玩家if(menu.playNum == 1){player2.lives = 0;}map.first=0;stage.init(level);gameState = Constant.GAME_STATE_INIT;}public void preLevel() {level--;if(level == 0){level = 16;}//保存玩家数int oldPlayerNum = menu.playNum;initObject();menu.playNum = oldPlayerNum;//只有一个玩家if(menu.playNum == 1){player2.lives = 0;}stage.init(level);gameState = Constant.GAME_STATE_INIT;}public void gameLoop() {switch (gameState) {case Constant.GAME_STATE_MENU:repaint();break;case Constant.GAME_STATE_INIT://stage.draw();if(stage.isReady == true){gameState = Constant.GAME_STATE_START;}repaint();break;case Constant.GAME_STATE_START://drawAll();if(isGameOver ||(player1.lives <=0 && player2.lives <= 0)){gameState = Constant.GAME_STATE_OVER;map.homeHit(ctx2);Constant.executor.execute(new Runnable() {@Overridepublic void run() {Constant.PLAYER_DESTROY_AUDIO.play();}});}if(appearEnemy == maxEnemy && enemyArr.size() == 0){gameState = Constant.GAME_STATE_WIN;}repaint();break;case Constant.GAME_STATE_WIN:repaint();break;case Constant.GAME_STATE_OVER:repaint();break;}}public void gameOver() {//.clearRect(0, 0, Constant.SCREEN_WIDTH, Constant.SCREEN_HEIGHT);ctx2.drawImage(Constant.RESOURCE_IMAGE, overX+map.offsetX, overY+map.offsetY,overX+map.offsetX+64,overY+map.offsetY+32,Constant.POS.get("over").x, Constant.POS.get("over").y,Constant.POS.get("over").x+64, Constant.POS.get("over").y+32, null);overY-=2;if(overY<=map.mapHeight/2) {initObject();//if(menu.playNum==1) {// player2.lives=0;//}gameState = Constant.GAME_STATE_MENU;}}public void action() {KeyAdapter l=new KeyAdapter() {@Overridepublic void keyPressed(KeyEvent e) {switch (gameState) {case Constant.GAME_STATE_MENU:if(e.getKeyCode()==KeyEvent.VK_ENTER){gameState = Constant.GAME_STATE_INIT;//只有一个玩家if(menu.playNum==1) {player2.lives=0;}}else {int n=0;if(e.getKeyCode()==KeyEvent.VK_DOWN) {n=1;}else if(e.getKeyCode()==KeyEvent.VK_UP) {n=-1;}menu.next(n);}break;case Constant.GAME_STATE_START:if(!keys.contains(e.getKeyCode())){keys.add(e.getKeyCode());}//射击if(e.getKeyCode()==KeyEvent.VK_SPACE && player1.lives > 0){player1.shoot(Constant.BULLET_TYPE_PLAYER, ctx2);}else if(e.getKeyCode()==KeyEvent.VK_ENTER && player2.lives > 0) {player2.shoot(Constant.BULLET_TYPE_PLAYER, ctx2);}else if(e.getKeyCode()==KeyEvent.VK_N) { //下一关nextLevel();}else if(e.getKeyCode() == KeyEvent.VK_P) {preLevel();}break;}}@Overridepublic void keyReleased(KeyEvent e) {// TODO Auto-generated method stub//super.keyReleased(e);if(keys != null && keys.size() > 0){Iterator<Integer> it=keys.iterator();while(it.hasNext()) {Integer key=it.next();if(key.intValue()==e.getKeyCode()) {it.remove();break;}}}}@Overridepublic void keyTyped(KeyEvent e) {//super.keyTyped(e);}};this.addKeyListener(l);this.setFocusable(true);Timer timer=new Timer();int interval=20;timer.schedule(new TimerTask() {@Overridepublic void run() {gameLoop(); }}, interval, interval);}public void keyEvent() {if(keys.contains(KeyEvent.VK_W)){player1.direct = Constant.UP;player1.hit = false;player1.move();}else if(keys.contains(KeyEvent.VK_S)){player1.direct = Constant.DOWN;player1.hit = false;player1.move();}else if(keys.contains(KeyEvent.VK_A)){player1.direct = Constant.LEFT;player1.hit = false;player1.move();}else if(keys.contains(KeyEvent.VK_D)){player1.direct = Constant.RIGHT;player1.hit = false;player1.move();}if(keys.contains(KeyEvent.VK_UP)){player2.direct = Constant.UP;player2.hit = false;player2.move();}else if(keys.contains(KeyEvent.VK_DOWN)){player2.direct = Constant.DOWN;player2.hit = false;player2.move();}else if(keys.contains(KeyEvent.VK_LEFT)){player2.direct = Constant.LEFT;player2.hit = false;player2.move();}else if(keys.contains(KeyEvent.VK_RIGHT)){player2.direct = Constant.RIGHT;player2.hit = false;player2.move();}}public void homeNoProtected(Graphics2D ctx2) {List<Integer[]> mapChangeIndexList=new ArrayList<Integer[]>();mapChangeIndexList.add(new Integer[] {23,11});mapChangeIndexList.add(new Integer[] {23,12});mapChangeIndexList.add(new Integer[] {23,13});mapChangeIndexList.add(new Integer[] {23,14});mapChangeIndexList.add(new Integer[] {24,11});mapChangeIndexList.add(new Integer[] {24,14});mapChangeIndexList.add(new Integer[] {25,11});mapChangeIndexList.add(new Integer[] {25,14});map.updateMap(mapChangeIndexList,Constant.WALL,ctx2);}public static void main(String[] args) {JFrame jf=new JFrame();jf.setTitle("坦克大战");jf.setSize(Constant.SCREEN_WIDTH, Constant.SCREEN_HEIGHT);GameMain gameMain=new GameMain();jf.add(gameMain);jf.setLocationRelativeTo(null);jf.setResizable(false);jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); jf.setVisible(true);gameMain.initGame(gameMain);gameMain.initObject();gameMain.action();}
}
接下来就可运行了。
其中,切换到下一关,按键盘N,上一个按P,空格键射击。
有兴趣的可以试一试。
下载地址:
坦克大战小游戏完整源码