坦克大战(TankWar)

花了一个星期的时间把马士兵老师讲的那个单机版坦克大战编好了,在编写过程中经常出错,由于对debug使用还不是很熟练,每次修复bug都要花很长时间,现在终于编好了个demo,给大家分享下

编TankWar,主要功能有 1.能够四处移动、2.-能够打击敌人 3.敌人能够移动 4.能够模拟爆炸 5.能够产生障碍

编程过程中的思想:

* 1.首先先new出一个frame,并设置大小,位置,
 * 2.用户不能改变窗口大小,监听窗口关闭
 * 3.画出一个子弹,设置大小和颜色
 * 4.让坦克动起来,将a位置改变为变量,b启动线程不断重画 ,c每次重画改变Tank位置
 * 5. 使用双缓冲消除闪烁现象,将所有东西画在虚拟图片上,一次性显示出来
 * 6.添加键盘监听器类KeyMonitor,针对不同的键改变坦克的位置,与重画线程结合产生不同方向运动,
 * 7.将坦克单独包装成类,
 * 8.让主战坦克向8个方向行走,
 * 9主战坦克向8个方向行走,并处理键抬起的消息
 * 10.添加子弹类
 * 11.根据主战坦克的方向和位置,打出子弹
 * 12.解决坦克停下也能打出炮弹的问题—画出炮筒,步骤一:Tank类增加新的属性ptDir
        步骤二:每次move后根据Tank新的方向确定炮筒的方向
 步骤三:将炮筒用直线的形式表现出来
 * 13.打出多发炮弹 步骤一:使用容器装炮弹,步骤二:每当抬起Ctrl键就往容器中加入新的炮弹,步骤三:逐一画出每一发炮弹
 * 14.解决炮弹不消亡的问题,解决坦克出界的问题。步骤一:加入控制炮弹生死的量bLive(Missle)
 *   步骤二:当炮弹已经死去就不需要对其重画,步骤三:当炮弹飞出边界就死亡,步骤四:当炮弹死亡就从容器中去除
 *15 画一辆敌人的坦克,步骤一:加入区别敌我的量good,步骤二:根据敌我的不同设置不同的颜色
 *  步骤三:更新Tank的构造函数,加入good,步骤四:TankClient中new 出敌人的坦克并画出
 *16 一颗子弹击中敌人坦克 步骤一:Missle中加入hitTank(Tank)方法,返回布尔类型;步骤二:碰撞检测的辅助类Rectangle
 *步骤三:为Tank和Missle都加入getRect方法 步骤四:当击中敌人坦克时,坦克被打死,子弹也死去
 *步骤五:增加控制Tank生死的量live 步骤六:如果死去就不画了
 *17 加入爆炸:步骤一:添加爆炸类,步骤二:爆炸应存在于集合类中,步骤三:击毙一辆坦克后应产生爆炸
 *18添加多辆坦克,步骤一:用容器来装敌人的Tank,步骤二:向容器中装入多辆敌人Tank,步骤三:画出来
 *19.让敌军坦克更加智能,步骤一:让敌军坦克动起来,步骤二:让敌军坦克向随机方向移动,步骤三:让敌军坦克向随机方向移动随机的步骤
 *   步骤四:让敌军坦克发射炮弹 步骤五:敌军炮火不能太猛烈
 *20添加两堵墙
 *21坦克不能互相穿越
 *22超级炮弹
 *23,主战坦克的生命值
 *24 画出主坦克的血块

这个程序总共五个类:

先发TankClient.java这个类

View Code
  1 package cn.shaoyangjiang.com;
2 import java.awt.*;
3 import java.awt.event.KeyAdapter;
4 import java.awt.event.KeyEvent;
5 import java.awt.event.WindowAdapter;
6 import java.awt.event.WindowEvent;
7 import java.util.List;
8 import java.util.ArrayList;
9
10 public class TankClient extends Frame{
11 //游戏的屏幕宽度
12 public static final int GAME_WIDTH = 800;
13 public static final int GAME_HEIGHT = 600;
14
15 Tank myTank = new Tank(50,200,true,Tank.Driection.STOP,this);
16 //设置两堵墙
17 Wall w1 = new Wall(100, 200, 20, 150, this), w2 = new Wall(300, 100, 300, 20, this);
18 //泛型,new出一个容器放子弹,爆炸,坦克
19 List<Missile> missiles = new ArrayList<Missile>();
20 List<Explode> explodes = new ArrayList<Explode>();
21 List<Tank> tanks = new ArrayList<Tank>();
22
23 Image offScreenImage = null;
24
25 //绘制容器 ,画坦克和子弹
26 public void paint(Graphics g){
27 //当坏坦克数量小于3时,再出来5辆坦克
28 if(tanks.size()<3){
29 for(int i=0; i <5;i++){
30 tanks.add(new Tank(50 + 40*(i+1),50,false ,Tank.Driection.D,this));
31 }
32 }
33 //这句是用来测试missiles里可以放多少子弹
34 g.drawString("missiles count:" + missiles.size(), 10, 50);
35 g.drawString("explodes count:" + explodes.size(), 10, 70);
36 g.drawString("tanks count" + tanks.size(), 10, 90);
37 g.drawString("tank life" + myTank.getLife(),10,110);
38
39 //挨个画ArrayList容器里的子弹,逐一画出每一发炮弹
40 for( int i=0; i<missiles.size();i++){
41 Missile m = missiles.get(i);
42 //TankClient里面的每发子弹都打tanks
43 m.hitTanks(tanks);
44 m.hitTank(myTank);
45 m.hitWall(w1);
46 m.hitWall(w2);
47 m.draw(g);
48 }
49
50 for(int i = 0; i < explodes.size(); i++){
51 Explode e = explodes.get(i);
52 e.draw(g);
53 }
54
55 for(int i = 0;i < tanks.size();i++){
56 Tank t = tanks.get(i);
57 t.collidesWithWall(w1);
58 t.collidesWithWall(w2);
59 t.collidesWithTanks(tanks);
60 t.draw(g);
61 }
62 //画出自己的坦克
63 myTank.draw(g);
64 w1.draw(g);
65 w2.draw(g);
66 }
67
68 // 使用双缓冲消除闪烁现象,将所有东西画在虚拟图片上,一次性显示出来
69 public void update(Graphics g) {
70 if(offScreenImage == null) {
71 offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT);
72 }
73 Graphics gOffScreen = offScreenImage.getGraphics();
74 Color c = gOffScreen.getColor();
75 gOffScreen.setColor(Color.GREEN);
76 gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT);
77 gOffScreen.setColor(c);
78 paint(gOffScreen);
79 g.drawImage(offScreenImage, 0, 0, null);
80 }
81
82 public void lauchFrame(){
83 //增加坏坦克
84 for(int i=0; i <12;i++){
85 tanks.add(new Tank(50 + 40*(i+1),50,false ,Tank.Driection.D,this));
86 }
87
88 this.setTitle("邵洋江 vs tank");
89 this.setSize(GAME_WIDTH, GAME_HEIGHT);
90 this.setLocation(100, 100);
91 this.setBackground(Color.GREEN);
92 //用户不能改变窗口大小
93 this.setResizable(false);
94 this.setVisible(true);
95 this.addWindowListener( new WindowAdapter() {
96 public void windowClosing(WindowEvent e) {
97 System.exit(0);
98 }
99
100 });
101 this.addKeyListener(new KeyMonitor());
102 //4.启动线程
103 new Thread(new PaintThread()).start();
104 }
105
106 public static void main(String[] args) {
107 TankClient tc = new TankClient();
108 tc.lauchFrame();
109 }
110
111 //每100毫秒重画一次
112 private class PaintThread implements Runnable{
113 public void run() {
114 while(true){
115 repaint();
116 try {
117 Thread.sleep(50);
118 } catch (InterruptedException e) {
119 e.printStackTrace();
120 }
121 }
122
123 }
124 }
125
126 //6让坦克动起来,添加键盘监听器类KeyMonitor,针对不同的键改变坦克的位置,与重画线程结合产生不同方向运动,
127 private class KeyMonitor extends KeyAdapter{
128 //按键按下
129 public void keyPressed(KeyEvent e) {
130 myTank.keyPressed(e);
131 }
132 //按键弹起
133 public void keyReleased(KeyEvent e){
134 myTank.keyReleased(e);
135 }
136 }
137
138
139 }

第二个类是Tank.java类

package cn.shaoyangjiang.com;
//可以方便的实现new出100辆坦克
/*
 * 建立Tank类
 为Tank类添加成员变量x y
 添加draw方法,使Tank类独立控制自己的画法
 添加Tank类处理按键的方法
 根据Tank类修改TankClient类

 */

import java.awt.*;
import java.awt.event.*;
import java.util.*;

public class Tank {
 //定义坦克移动常量
 private int x,y;
 private int oldx,oldy;
 public static final int XSPEED = 5;
 public static final int YSPEED = 5;
 public static final int WIDTH = 30;
 public static final int HEIGHT = 30;
 //定义四个按键状态
 private boolean bL = false, bU = false, bR = false,bD = false;
 //添加代表方向的枚举
 enum Driection {L,LU,U,RU,R,RD,D,LD,STOP};
 //指定一个初始的状态
 private Driection dir = Driection.STOP;
 //指定炮筒的初始位置
 private Driection ptDir = Driection.D;
 //持有对象的应用
 TankClient tc;
 //区分是自己的坦克还是敌人的坦克
 private boolean good;
 //坦克的生命值
 private int life = 100;
 //new出血块
 private BloodBar bb = new BloodBar();
 //这个方法是为其他类访问private的life
 public int getLife() {
  return life;
 }
 public void setLife(int life) {
  this.life = life;
 }

 public boolean isGood() {
  return good;
 }
 public void setGood(boolean good) {
  this.good = good;
 }

 //增加控制Tank生死的量live
 private boolean live = true;
 
 private static Random r = new Random();
 
 private int step = r.nextInt(12) + 3;
 
 public Tank(int x, int y, boolean good){
  this.x = x;
  this.y = y;
  this.oldx = x;
  this.oldy = y;
  this.good=good;
 }
 //方法的覆盖
 public Tank(int x, int y,boolean good, Driection dir,TankClient tc) {
  this(x,y,good);
  this.dir = dir;
  this.tc = tc;
 }
 //graphics指画笔,这里是画坦克
 public void draw(Graphics g){
  //如果坦克死了就不画了
  if(!live){
   if(!good){
    tc.tanks.remove(this);
   }
   return;
  }
  //如果是好的就画出血块
  if(good){
   bb.draw(g);
  }
  //取出前景色
  Color c = g.getColor();
  if(good) g.setColor(Color.RED);
  else g.setColor(Color.BLUE);
  //小坦克,前两个参数指定坐标,第三个是宽度,第四个是高度。
  g.fillOval(x, y , WIDTH, HEIGHT);
  //设回前景色
  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();
 }
 
 //坦克回到原来一步
 private void stay(){
  x = oldx;
  y = oldy;
 }
 //坦克移动的方法
 void move() {
  //把坦克的当前位置给oldx,oldy
  this.oldx = x;
  this.oldy = y;
  switch(dir) {
  case L :
   x = x - XSPEED;
   break;
  case LU :
   x = x - XSPEED;
   y = y - YSPEED;
   break;
  case U :
   y = y - YSPEED;
   break;
  case RU :
   x = x + XSPEED;
   y = y - YSPEED;
   break;
  case R :
   x = x + XSPEED;
   break;
  case RD :
   x = x + XSPEED;
   y = y + YSPEED;
   break;
  case D :
   y = y + YSPEED;
   break;
  case LD :
   x = x - XSPEED;
   y = y + YSPEED;
   break;
  case STOP :
   break;
  }
  //当坦克的方向改变时,炮筒的方向也改变。
  if(this.dir!= Tank.Driection.STOP) {
   this.ptDir = this.dir;
  }
  //防止坦克出界
  if(x < 0) x = 0;
  if(y < 30) y = 30;
  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;
  
  if(!good){
   //把enum(枚举)里的方向转化为数组
   Driection[] dirs = Driection.values();
   if(step == 0){
   step = r.nextInt(12)+3;
   //随机产生一个数
   int rn = r.nextInt(dirs.length);
   dir = dirs[rn];
   }
   step--;
   if(r.nextInt(40) > 36) this.fire();
  }
 
  
 }
 
 //6让坦克动起来,添加键盘监听器类KeyMonitor,针对不同的键改变坦克的位置,与重画线程结合产生不同方向运动,
 //根据按键状态改变坦克的方向
   public void keyPressed(KeyEvent e) {
    int key = e.getKeyCode();
    switch(key){
    case KeyEvent.VK_LEFT:
     bL = true;
     break;
    case KeyEvent.VK_RIGHT:
     bR = true;
     break;
    case KeyEvent.VK_UP:
     bU = true;
     break;
    case KeyEvent.VK_DOWN:
     bD = true;
     break;
    }
    locationDircation();
   }
   
  //按键释放时,坦克停止 
   public void keyReleased(KeyEvent e) {
    int key = e.getKeyCode();
    switch(key){
    //按下F2重新出来自己的好坦克
    case KeyEvent.VK_F2:
     if(!this.live){
      this.live = true;
      life = 100;
     }
     break;
    //当ctrl弹起来的时候发出子弹
    case KeyEvent.VK_CONTROL:
     fire();
     break;
    case KeyEvent.VK_LEFT:
     bL = false;
     break;
    case KeyEvent.VK_RIGHT:
     bR = false;
     break;
    case KeyEvent.VK_UP:
     bU = false;
     break;
    case KeyEvent.VK_DOWN:
     bD = false;
     break;
    case KeyEvent.VK_A:
     superfire();
     break;
    }
    locationDircation();
   }
   
 //确定坦克的位置  
 void locationDircation(){
  if( bL && !bU && !bR && !bD ) dir = Driection.L;
  else if( bL && bU && !bR && !bD ) dir = Driection.LU;
  else if( !bL && bU && !bR && !bD ) dir = Driection.U;
  else if( !bL && bU && bR && !bD ) dir = Driection.RU;
  else if( !bL && !bU && bR && !bD ) dir = Driection.R;
  else if( !bL && !bU && bR && bD ) dir = Driection.RD;
  else if( !bL && !bU && !bR && bD ) dir = Driection.D;
  else if( bL && !bU && !bR && bD ) dir = Driection.LD;
  else if( !bL && !bU && !bR && !bD ) dir = Driection.STOP;
 }
 
 //打出子弹的方法
 public Missile fire() {
  if(!live) return null;
  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 ,good,ptDir,this.tc);
  //往容器里装子弹
  tc.missiles.add(m);
  return m;
 }
 //根据dir(有八个方向)的方向发出子弹
 public Missile fire(Driection dir) {
  if(!live) return null;
  int x = this.x + Tank.WIDTH/2 - Missile.WIDTH/2;
  int y = this.y + Tank.HEIGHT/2 - Missile.HEIGHT/2;
  //根据dir的方向
  Missile m = new Missile(x, y, good, dir, this.tc);
  tc.missiles.add(m);
  return m;
 }
 
 //碰撞检测的辅助类Rectangle,并加入getRect方法
 public Rectangle getRect() {
  return new Rectangle(x,y,WIDTH,HEIGHT);
 }
 
 //坦克不能穿越墙
 public boolean collidesWithWall(Wall w) {
  if(this.live && this.getRect().intersects(w.getRect())) {
   //坦克回到上一步停住
   this.stay();
   this.dir = Driection.STOP;
   return true;
  }
  return false;
 }
 
 //坦克不能互相穿越
 public boolean collidesWithTanks(java.util.List<Tank> tanks) {
  for(int i=0; i<tanks.size(); i++) {
   Tank t = tanks.get(i);
   if(this != t) {
    if(this.live && t.isLive() && this.getRect().intersects(t.getRect())) {
     this.stay();
     t.stay();
     return true;
    }
   }
  }
  return false;
 }
 //按八个方向打子弹的方法
 public void superfire(){
  Driection[] dirs = Driection.values();
  for(int i =0;i < 8;i ++){
   fire(dirs[i]);
  }
 }
 //血块
 private class BloodBar {
  public void draw(Graphics g) {
   Color c = g.getColor();
   g.setColor(Color.RED);
   g.drawRect(x, y-10, WIDTH, 10);
   int w = WIDTH * life/100 ;
   g.fillRect(x, y-10, w, 10);
   g.setColor(c);
  }
 }
 
 public boolean isLive() {
  return live;
 }
 
 public void setLive(boolean live) {
  this.live = live;
 }
 
}

第三个类是子弹类,Missile.java类

View Code
  1 package cn.shaoyangjiang.com;
2 import java.awt.*;
3 import java.util.List;
4
5 /*
6 * 子弹的类
7 *
8 */
9 public class Missile {
10
11 public static final int XSPEED = 10;
12 public static final int YSPEED = 10;
13 public static final int WIDTH = 10;
14 public static final int HEIGHT = 10;
15
16 int x,y;
17 Tank.Driection dir;
18 private boolean live = true;
19 private TankClient tc;
20 private boolean good;
21
22 public Missile(int x, int y,Tank.Driection dir) {
23 this.x = x;
24 this.y = y;
25 this.dir = dir;
26 }
27
28 //方法的重载
29 public Missile(int x, int y,boolean good,Tank.Driection dir,TankClient tc){
30 this(x,y,dir);
31 this.good = good;
32 this.tc = tc;
33 }
34 //绘制容器 ,画出一个子弹,设置大小和颜色
35 public void draw(Graphics g){
36 if(!live){
37 tc.missiles.remove(this);
38 return;
39 }
40 //取出前景色
41 Color c = g.getColor();
42 g.setColor(Color.BLACK);
43 //小坦克,前两个参数指定坐标,第三个是宽度,第四个是高度。
44 g.fillOval(x, y , WIDTH, HEIGHT);
45 //设回前景色
46 g.setColor(c);
47 move();
48 }
49
50 //坦克移动的方法
51 private void move() {
52 switch(dir) {
53 case L :
54 x = x - XSPEED;
55 break;
56 case LU :
57 x = x - XSPEED;
58 y = y - YSPEED;
59 break;
60 case U :
61 y = y - YSPEED;
62 break;
63 case RU :
64 x = x + XSPEED;
65 y = y - YSPEED;
66 break;
67 case R :
68 x = x + XSPEED;
69 break;
70 case RD :
71 x = x + XSPEED;
72 y = y + YSPEED;
73 break;
74 case D :
75 y = y + YSPEED;
76 break;
77 case LD :
78 x = x - XSPEED;
79 y = y + YSPEED;
80 break;
81 case STOP :
82 break;
83 }
84 //当子弹飞出边界的时候,把子弹从容器里移除
85 if(x < 0 || y < 0 || x > TankClient.GAME_WIDTH || y > TankClient.GAME_HEIGHT){
86 live = false ;
87
88 }
89 }
90
91 public boolean isLive() {
92 return live;
93 }
94
95 //碰撞检测的辅助类Rectangle,并加入getRect方法
96 public Rectangle getRect() {
97 return new Rectangle(x,y,WIDTH,HEIGHT);
98 }
99
100 //Missle中加入hitTank(Tank)方法,返回布尔类型
101 public boolean hitTank(Tank t){
102 //当击中敌人坦克时,坦克被打死,子弹也死去
103 if(this.live && this.getRect().intersects(t.getRect()) && t.isLive() && this.good != t.isGood()) {
104 //如果坦克是好的,当击中坦克的时候,生命值减20,当生命值小于0的时候,坦克死去,坏坦克打一下就死
105 if(t.isGood()) {
106 t.setLife(t.getLife()-20);
107 if(t.getLife() <= 0) t.setLive(false);
108 } else {
109 t.setLive(false);
110 }
111 this.live = false;
112 //当打中坦克时,new出爆炸。
113 Explode e = new Explode(x,y,tc);
114 //坦克里加入爆炸
115 tc.explodes.add(e);
116 return true;
117 }
118 return false;
119 }
120
121 //打一系列tank的方法
122 public boolean hitTanks(List<Tank> tanks){
123 for(int i = 0;i < tanks.size();i++){
124 if(hitTank(tanks.get(i))){
125 return true;
126 }
127 }
128 return false;
129 }
130
131 //子弹打击墙,当子弹和墙相交时,子弹死去
132 public boolean hitWall(Wall w) {
133 if(this.live && this.getRect().intersects(w.getRect())) {
134 this.live = false;
135 return true;
136 }
137 return false;
138 }
139 }

第四个类是爆炸类,Explode.java

View Code
 1 package cn.shaoyangjiang.com;
2
3 import java.awt.*;
4 //添加爆炸类
5 public class Explode {
6
7 int x,y;
8 //设置圆的直径,给爆炸做效果用
9 int[] diameter = {4,7,12,18,26,32,49,30,14,6};
10 //申明爆炸是否活着
11 private boolean live = true;
12 int step = 0;
13 private TankClient tc;
14
15 Explode(int x,int y,TankClient tc){
16 this.x = x;
17 this.y = y;
18 this.tc = tc;
19 }
20
21 public void draw(Graphics g){
22 //如果爆炸死了,移除爆炸
23 if(!live) {
24 tc.explodes.remove(this);
25 return;
26 }
27 //判断是否到了最后一部
28 if(step ==diameter.length){
29 live = false;
30 step = 0;
31 return;
32 }
33
34 Color c = g.getColor();
35 g.setColor(Color.ORANGE);
36 g.fillOval(x, y, diameter[step],diameter[step]);
37 g.setColor(c);
38
39 step++;
40
41 }
42 }

第五个类是墙壁类,Wall.java

View Code
 1 package cn.shaoyangjiang.com;
2 import java.awt.*;
3 //墙这个类
4 public class Wall {
5 int x, y, w, h;
6 TankClient tc ;
7
8 public Wall(int x, int y, int w, int h, TankClient tc) {
9 this.x = x;
10 this.y = y;
11 this.w = w;
12 this.h = h;
13 this.tc = tc;
14 }
15
16 //画墙这个方法
17 public void draw(Graphics g) {
18 g.fillRect(x, y, w, h);
19 }
20 //碰撞检测的辅助类Rectangle,并加入getRect方法
21 public Rectangle getRect() {
22 return new Rectangle(x, y, w, h);
23 }
24 }

演示效果图:




 

原文地址:https://www.cnblogs.com/shaoyangjiang/p/2370348.html