版本1.7
功能:加入爆炸
步骤:
1)添加爆炸类:Explode
用不同直径的圆模拟爆炸: int[] diameter = {4, 7, 12, 18, 26, 32, 49, 30, 14, 6};
加入live: private boolean live = true;
加入位置属性: int x, y;
加入draw方法
2)爆炸应该存在于集合类中:
TankClient加入集合explodes: List<Explode> explodes=new ArrayList<Explode>();
将集合中的爆炸逐一画出(如果死去就去除):在Missile类的draw方法中,当爆炸live==false的时候,tc.missiles.remove(this);
3)击毙一辆坦克后应产生爆炸:
在Missile类的hitTank中,产生爆炸,Explode e=new Explode(x, y, tc); tc.explodes.add(e);
具体代码实现:
Tank:
1 import java.awt.*; 2 import java.awt.event.*; 3 import java.util.Random; 4 5 public class Tank { 6 // 方便后期更改 7 public static final int XSPEED = 5; 8 public static final int YSPEED = 5; 9 // 将坦克的高度和宽度设置为常量 10 public static final int WIDTH = 30; 11 public static final int HEIGHT = 30; 12 TankClient tc; 13 // 区别是我方坦克还是地方坦克,方便据此进行不同的设置 14 private boolean good; 15 16 //判断坦克生死的变量 17 private boolean live=true; 18 19 20 public boolean isLive() { 21 return live; 22 } 23 24 public void setLive(boolean live) { 25 this.live = live; 26 } 27 28 private int x; 29 private int y; 30 // 添加记录按键状态的布尔量 31 private boolean bL = false; 32 private boolean bR = false; 33 private boolean bU = false; 34 private boolean bD = false; 35 36 // 添加代表方向的量(使用枚举) 37 enum Direction { 38 L, R, U, D, LU, LD, RU, RD, STOP 39 }; 40 41 private Direction dir = Direction.STOP; 42 43 // 定义炮筒的方向,我们想办法将炮筒的方法调整成和坦克移动方向一致; 44 // 我们这里会用一条直线来表示炮筒:模拟炮筒 45 // 我们要根据炮筒的方向画直线表示炮筒 46 Direction ptDir = Direction.D; 47 48 //更改构造函数 49 public Tank(int x, int y,boolean good) { 50 this.x = x; 51 this.y = y; 52 this.good=good; 53 } 54 55 //这个位置的构造函数也相应进行了更改 56 public Tank(int x, int y,boolean good,TankClient tc) { 57 // 调用那个有两个参数的构造方法 58 this(x, y,good); 59 // 在这个位置初始化tc 60 this.tc = tc; 61 } 62 63 // Tank对象的draw方法 64 public void draw(Graphics g) { 65 if(!live){ 66 return; 67 } 68 Color c = g.getColor(); 69 if(good){ 70 g.setColor(Color.RED); 71 } 72 else { 73 g.setColor(Color.PINK); 74 } 75 g.fillOval(x, y, WIDTH, HEIGHT); 76 g.setColor(c); 77 // 根据炮筒的方向画直线来表示我们坦克的炮筒 78 switch (ptDir) { 79 case L: 80 // 画直线:四个参数分别代表:坦克中心点的坐标 直线的另一头的的坐标 81 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 82 + Tank.HEIGHT / 2); 83 break; 84 case R: 85 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 86 y + Tank.HEIGHT / 2); 87 88 break; 89 case U: 90 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 91 / 2, y); 92 93 break; 94 case D: 95 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 96 / 2, y + Tank.HEIGHT); 97 98 break; 99 case LU: 100 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y); 101 break; 102 case LD: 103 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 104 + Tank.HEIGHT); 105 106 break; 107 case RU: 108 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 109 y); 110 111 break; 112 case RD: 113 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 114 y + Tank.HEIGHT); 115 116 break; 117 /* 118 * case STOP: break; 119 */ 120 } 121 move(); 122 } 123 124 public void move() { 125 switch (dir) { 126 case L: 127 x -= XSPEED; 128 break; 129 case R: 130 x += XSPEED; 131 break; 132 case U: 133 y -= YSPEED; 134 break; 135 case D: 136 y += YSPEED; 137 break; 138 case LU: 139 x -= XSPEED; 140 y -= YSPEED; 141 break; 142 case LD: 143 x -= XSPEED; 144 y += YSPEED; 145 break; 146 case RU: 147 x += XSPEED; 148 y -= YSPEED; 149 break; 150 case RD: 151 x += XSPEED; 152 y += YSPEED; 153 break; 154 155 case STOP: 156 break; 157 } 158 // 如果坦克不是停着的,则将炮筒调整至和坦克移动的方向相同 159 if (this.dir != Direction.STOP) { 160 this.ptDir = this.dir; 161 } 162 if (x < 0) { 163 x = 0; 164 } 165 // 因为我们的游戏界面有那个missileCount标签,所以在y轴方向不能用y是否<0进行判断 166 // 否则的话我们的坦克可以从上面出去 167 if (y < 50) { 168 y = 50; 169 } 170 if (x + Tank.WIDTH > TankClient.GAME_WIDTH) { 171 x = TankClient.GAME_WIDTH - Tank.WIDTH; 172 } 173 if (y + Tank.HEIGHT > TankClient.GAME_HEIGHT) { 174 y = TankClient.GAME_HEIGHT - Tank.HEIGHT; 175 } 176 } 177 178 public void locateDirection() { 179 if (bL && !bU && !bR && !bD) 180 dir = Direction.L; 181 else if (bL && bU && !bR && !bD) 182 dir = Direction.LU; 183 else if (!bL && bU && !bR && !bD) 184 dir = Direction.U; 185 else if (!bL && bU && bR && !bD) 186 dir = Direction.RU; 187 else if (!bL && !bU && bR && !bD) 188 dir = Direction.R; 189 else if (!bL && !bU && bR && bD) 190 dir = Direction.RD; 191 else if (!bL && !bU && !bR && bD) 192 dir = Direction.D; 193 else if (bL && !bU && !bR && bD) 194 dir = Direction.LD; 195 else if (!bL && !bU && !bR && !bD) 196 dir = Direction.STOP; 197 198 } 199 200 // 坦克自己向哪个方向移动,它自己最清楚; 201 public void KeyPressed(KeyEvent e) { 202 // 获得所按下的键所对应的虚拟码: 203 // Returns the integer keyCode associated with the key in this event 204 int key = e.getKeyCode(); 205 // 判断不同的按键,指挥坦克的运动方向 206 switch (key) { 207 case KeyEvent.VK_LEFT: 208 bL = true; 209 break; 210 case KeyEvent.VK_UP: 211 bU = true; 212 break; 213 case KeyEvent.VK_RIGHT: 214 bR = true; 215 break; 216 case KeyEvent.VK_DOWN: 217 bD = true; 218 break; 219 } 220 locateDirection(); 221 } 222 223 public void keyReleased(KeyEvent e) { 224 int key = e.getKeyCode(); 225 // 判断不同的按键,指挥坦克的运动方向 226 // 哪个键按下了,就把对应方向的布尔类型置为false 227 switch (key) { 228 // 为了防止一直按着Ctrl键的时候,炮弹太过于密集 229 // 因此我们定义在Ctrl键抬起的时候才发炮弹 230 // 这样炮弹不至于太过密集 231 case KeyEvent.VK_CONTROL: 232 fire(); 233 break; 234 case KeyEvent.VK_LEFT: 235 bL = false; 236 break; 237 case KeyEvent.VK_UP: 238 bU = false; 239 break; 240 case KeyEvent.VK_RIGHT: 241 bR = false; 242 break; 243 case KeyEvent.VK_DOWN: 244 bD = false; 245 break; 246 } 247 // 重新定位一下 248 locateDirection(); 249 } 250 251 public Missile fire() { 252 // 计算子弹的位置,使得子弹从坦克的中间发出来 253 int x = this.x + Tank.WIDTH / 2 - Missile.WIDTH / 2; 254 int y = this.y + Tank.HEIGHT / 2 - Missile.HEIGHT / 2; 255 // 这个时候我们就根据炮筒的方向来发炮弹了,之前是根据坦克的方向来发炮弹 256 Missile m = new Missile(x, y, ptDir, tc); 257 // 将新产生的炮弹放置到List容器中 258 tc.missiles.add(m); 259 return m; 260 } 261 262 //拿到包围坦克的那个方块 263 public Rectangle getRect() { 264 265 return new Rectangle(x,y,WIDTH,HEIGHT); 266 } 267 }
Missile:
1 import java.awt.Color; 2 import java.awt.Graphics; 3 import java.awt.Rectangle; 4 5 public class Missile { 6 // 炮弹的移动速度,不要比坦克的移动速度慢,不然你看到的是满屏的坦克追着炮弹跑 7 public static final int XSPEED = 10; 8 public static final int YSPEED = 10; 9 // 将子弹的高度和宽度设置为常量 10 public static final int WIDTH = 10; 11 public static final int HEIGHT = 10; 12 // 炮弹自己的三个属性 13 int x; 14 int y; 15 Tank.Direction dir; 16 17 // 定义一个布尔类型的变量来判断炮弹是否已经消亡 18 private boolean live = true; 19 //我们在Missile类中也持有一个TankClient的引用 20 //在炮弹出界的时候就可以从装炮弹的missiles集合中去除该炮弹,不再对其重画 21 private TankClient tc; 22 23 public boolean isLive() { 24 return live; 25 } 26 27 public Missile(int x, int y, Tank.Direction dir) { 28 this.x = x; 29 this.y = y; 30 this.dir = dir; 31 } 32 public Missile(int x,int y,Tank.Direction dir,TankClient tc){ 33 this(x, y, dir); 34 this.tc=tc; 35 } 36 37 // 炮弹自己的draw方法 38 public void draw(Graphics g) { 39 //炮弹消亡就不需要再画出来了 40 if(!live){ 41 tc.missiles.remove(this); 42 return; 43 } 44 Color c = g.getColor(); 45 g.setColor(Color.BLACK); 46 // 炮弹形状不要比坦克大,这里设置成10,10; 47 g.fillOval(x, y, WIDTH, HEIGHT); 48 g.setColor(c); 49 move(); 50 } 51 52 public void move() { 53 switch (dir) { 54 case L: 55 x -= XSPEED; 56 break; 57 case R: 58 x += XSPEED; 59 break; 60 case U: 61 y -= YSPEED; 62 break; 63 case D: 64 y += YSPEED; 65 break; 66 case LU: 67 x -= XSPEED; 68 y -= YSPEED; 69 break; 70 case LD: 71 x -= XSPEED; 72 y += YSPEED; 73 break; 74 case RU: 75 x += XSPEED; 76 y -= YSPEED; 77 break; 78 case RD: 79 x += XSPEED; 80 y += YSPEED; 81 break; 82 // 炮弹就没有STOP这个枚举类型的值了 83 /* 84 * case STOP: break; 85 */ 86 } 87 // 判断炮弹出边界则消亡 88 // 注意x,y只有正数值,x向右递增,y向下递增 89 if (x < 0 || y < 0 || x > TankClient.GAME_WIDTH 90 || y > TankClient.GAME_HEIGHT) { 91 live = false; 92 } 93 } 94 public boolean hitTank(Tank t){ 95 //炮弹的方框和坦克的方框碰在一起了并且坦克是存活着的 96 if(this.getRect().intersects(t.getRect())&&t.isLive()){ 97 t.setLive(false); 98 this.live=false; 99 100 //炮弹击中坦克,发生爆炸 101 Explode e=new Explode(x, y, tc); 102 tc.explodes.add(e); 103 return true; 104 } 105 return false; 106 } 107 //碰撞检测类Rectangle 108 //拿到包围在炮弹周围的小方块 109 public Rectangle getRect(){ 110 return new Rectangle(x,y,WIDTH,HEIGHT); 111 } 112 }
Explode:
1 import java.awt.*; 2 3 public class Explode { 4 //爆炸的位置 5 int x, y; 6 //爆炸是否存在 7 private boolean live = true; 8 9 //持有一个Tankclient的引用 10 private TankClient tc ; 11 12 //定义不同直径大小的爆炸 13 int[] diameter = {4, 7, 12, 18, 26, 32, 49, 30, 14, 6}; 14 //爆炸发生到哪一个阶段了,对应相应大小的直径 15 int step = 0; 16 17 public Explode(int x, int y, TankClient tc) { 18 this.x = x; 19 this.y = y; 20 this.tc = tc; 21 } 22 23 public void draw(Graphics g) { 24 if(!live) { 25 //爆炸发生,将相应直径的爆炸圆从集合explodes中去除 26 tc.explodes.remove(this); 27 return; 28 } 29 30 if(step == diameter.length) { 31 live = false; 32 step = 0; 33 return; 34 } 35 36 Color c = g.getColor(); 37 g.setColor(Color.ORANGE); 38 39 //把不同的圆画出来 40 g.fillOval(x, y, diameter[step], diameter[step]); 41 g.setColor(c); 42 43 step ++; 44 } 45 }
TankClient:
1 import java.awt.*; 2 import java.awt.event.*; 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class TankClient extends Frame { 7 // 设置成常量,方便以后的改动 8 public static final int GAME_WIDTH = 800; 9 public static final int GAME_HEIGHT = 600; 10 11 // 将当前的TankClient对象传递给myTank; 12 // 目的是方便我们在Tank这个类中访问m(炮弹对象)这个成员变量 13 // 其实就是在Tank类中持有TankClient类对象的一个引用 14 15 //我们这里new我们自己的坦克,用下面的方式 16 Tank myTank = new Tank(50, 50, true,this); 17 18 //新建敌方坦克 19 Tank enemyTank=new Tank(100,100,false,this); 20 //定义爆炸 21 Explode e=new Explode(70, 70, this); 22 //定义一个集合,多个爆炸点 23 List<Explode> explodes=new ArrayList<Explode>(); 24 25 //使用容器装炮弹 26 List<Missile> missiles=new ArrayList<Missile>(); 27 28 // 定义虚拟图片,方便后期的一次性显示 29 Image offScreenImage = null; 30 31 32 public void paint(Graphics g) { 33 //记录屏幕上的子弹数目 34 g.drawString("missiles count:" + missiles.size(), 10, 50); 35 //添加上方标记栏,记录爆炸次数 36 g.drawString("explodes count:" + explodes.size(), 10, 70); 37 38 39 //遍历结合,发出多发炮弹 40 for(int i=0;i<missiles.size();i++){ 41 Missile m=missiles.get(i); 42 //子弹打敌方坦克 43 m.hitTank(enemyTank); 44 45 m.draw(g); 46 } 47 48 for(int i=0;i<explodes.size();i++){ 49 Explode e=explodes.get(i); 50 e.draw(g); 51 } 52 // 不改变前景色 53 myTank.draw(g); 54 //敌方坦克调用draw方法 55 enemyTank.draw(g); 56 } 57 58 // 刷新操作 59 public void update(Graphics g) { 60 if (offScreenImage == null) { 61 offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT); 62 } 63 Graphics gOffScreen = offScreenImage.getGraphics(); 64 Color c = gOffScreen.getColor(); 65 gOffScreen.setColor(Color.GREEN); 66 gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); 67 gOffScreen.setColor(c); 68 paint(gOffScreen); 69 g.drawImage(offScreenImage, 0, 0, null); 70 } 71 72 public void lauchFrame() { 73 // this.setLocation(400, 300); 74 this.setSize(GAME_WIDTH, GAME_HEIGHT); 75 this.setTitle("TankWar"); 76 this.addWindowListener(new WindowAdapter() { 77 public void windowClosing(WindowEvent e) { 78 System.exit(0); 79 } 80 }); 81 this.setResizable(false); 82 this.setBackground(Color.GREEN); 83 84 this.addKeyListener(new KeyMonitor()); 85 86 setVisible(true); 87 88 new Thread(new PaintThread()).start(); 89 } 90 91 public static void main(String[] args) { 92 TankClient tc = new TankClient(); 93 tc.lauchFrame(); 94 } 95 96 private class PaintThread implements Runnable { 97 98 public void run() { 99 while (true) { 100 repaint(); 101 try { 102 //为了爆炸效果,改成1000 103 Thread.sleep(50); 104 } catch (InterruptedException e) { 105 e.printStackTrace(); 106 } 107 } 108 } 109 } 110 111 // 创建键盘时间监听 112 private class KeyMonitor extends KeyAdapter { 113 114 // 直接调用myTank自己的方法根据相应的按键信息进行移动 115 public void keyPressed(KeyEvent e) { 116 myTank.KeyPressed(e); 117 // 添加了处理键抬起的事件,可以控制坦克起步以后的状态 118 // 而不是一直按照一个方向走下去 119 } 120 121 public void keyReleased(KeyEvent e) { 122 myTank.keyReleased(e); 123 } 124 125 } 126 }
版本1.8
功能:添加多辆坦克
步骤:
1)用容器来装敌人的Tank:在Tankclient类中List<Tank> tanks=new ArrayList<Tank>();
2)向容器中装入多辆敌人Tank
3)画出来
4)运行,不能打掉
添加hitTanks方法,打一系列Tank:
1 public boolean hitTanks(List<Tank> tanks){ 2 for(int i=0;i<tanks.size();i++){ 3 if(hitTank(tanks.get(i))){ 4 return true; 5 } 6 } 7 return false; 8 9 }
TankClient里面每发子弹都可以打tanks:
具体代码实现:
Tank:
1 import java.awt.*; 2 import java.awt.event.*; 3 import java.util.Random; 4 5 public class Tank { 6 // 方便后期更改 7 public static final int XSPEED = 5; 8 public static final int YSPEED = 5; 9 // 将坦克的高度和宽度设置为常量 10 public static final int WIDTH = 30; 11 public static final int HEIGHT = 30; 12 TankClient tc; 13 // 区别是我方坦克还是地方坦克,方便据此进行不同的设置 14 private boolean good; 15 16 //判断坦克生死的变量 17 private boolean live=true; 18 19 20 public boolean isLive() { 21 return live; 22 } 23 24 public void setLive(boolean live) { 25 this.live = live; 26 } 27 28 private int x; 29 private int y; 30 // 添加记录按键状态的布尔量 31 private boolean bL = false; 32 private boolean bR = false; 33 private boolean bU = false; 34 private boolean bD = false; 35 36 // 添加代表方向的量(使用枚举) 37 enum Direction { 38 L, R, U, D, LU, LD, RU, RD, STOP 39 }; 40 41 private Direction dir = Direction.STOP; 42 43 // 定义炮筒的方向,我们想办法将炮筒的方法调整成和坦克移动方向一致; 44 // 我们这里会用一条直线来表示炮筒:模拟炮筒 45 // 我们要根据炮筒的方向画直线表示炮筒 46 Direction ptDir = Direction.D; 47 48 //更改构造函数 49 public Tank(int x, int y,boolean good) { 50 this.x = x; 51 this.y = y; 52 this.good=good; 53 } 54 55 //这个位置的构造函数也相应进行了更改 56 public Tank(int x, int y,boolean good,TankClient tc) { 57 // 调用那个有两个参数的构造方法 58 this(x, y,good); 59 // 在这个位置初始化tc 60 this.tc = tc; 61 } 62 63 // Tank对象的draw方法 64 public void draw(Graphics g) { 65 if(!live){ 66 //如果死亡的是敌方坦克,在tanks集合中去除该坦克 67 if(!good){ 68 tc.tanks.remove(this); 69 } 70 //如果是我方坦克,直接返回 71 return; 72 } 73 Color c = g.getColor(); 74 if(good){ 75 g.setColor(Color.RED); 76 } 77 else { 78 g.setColor(Color.PINK); 79 } 80 g.fillOval(x, y, WIDTH, HEIGHT); 81 g.setColor(c); 82 // 根据炮筒的方向画直线来表示我们坦克的炮筒 83 switch (ptDir) { 84 case L: 85 // 画直线:四个参数分别代表:坦克中心点的坐标 直线的另一头的的坐标 86 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 87 + Tank.HEIGHT / 2); 88 break; 89 case R: 90 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 91 y + Tank.HEIGHT / 2); 92 93 break; 94 case U: 95 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 96 / 2, y); 97 98 break; 99 case D: 100 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 101 / 2, y + Tank.HEIGHT); 102 103 break; 104 case LU: 105 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y); 106 break; 107 case LD: 108 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 109 + Tank.HEIGHT); 110 111 break; 112 case RU: 113 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 114 y); 115 116 break; 117 case RD: 118 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 119 y + Tank.HEIGHT); 120 121 break; 122 /* 123 * case STOP: break; 124 */ 125 } 126 move(); 127 } 128 129 public void move() { 130 switch (dir) { 131 case L: 132 x -= XSPEED; 133 break; 134 case R: 135 x += XSPEED; 136 break; 137 case U: 138 y -= YSPEED; 139 break; 140 case D: 141 y += YSPEED; 142 break; 143 case LU: 144 x -= XSPEED; 145 y -= YSPEED; 146 break; 147 case LD: 148 x -= XSPEED; 149 y += YSPEED; 150 break; 151 case RU: 152 x += XSPEED; 153 y -= YSPEED; 154 break; 155 case RD: 156 x += XSPEED; 157 y += YSPEED; 158 break; 159 160 case STOP: 161 break; 162 } 163 // 如果坦克不是停着的,则将炮筒调整至和坦克移动的方向相同 164 if (this.dir != Direction.STOP) { 165 this.ptDir = this.dir; 166 } 167 if (x < 0) { 168 x = 0; 169 } 170 // 因为我们的游戏界面有那个missileCount标签,所以在y轴方向不能用y是否<0进行判断 171 // 否则的话我们的坦克可以从上面出去 172 if (y < 50) { 173 y = 50; 174 } 175 if (x + Tank.WIDTH > TankClient.GAME_WIDTH) { 176 x = TankClient.GAME_WIDTH - Tank.WIDTH; 177 } 178 if (y + Tank.HEIGHT > TankClient.GAME_HEIGHT) { 179 y = TankClient.GAME_HEIGHT - Tank.HEIGHT; 180 } 181 } 182 183 public void locateDirection() { 184 if (bL && !bU && !bR && !bD) 185 dir = Direction.L; 186 else if (bL && bU && !bR && !bD) 187 dir = Direction.LU; 188 else if (!bL && bU && !bR && !bD) 189 dir = Direction.U; 190 else if (!bL && bU && bR && !bD) 191 dir = Direction.RU; 192 else if (!bL && !bU && bR && !bD) 193 dir = Direction.R; 194 else if (!bL && !bU && bR && bD) 195 dir = Direction.RD; 196 else if (!bL && !bU && !bR && bD) 197 dir = Direction.D; 198 else if (bL && !bU && !bR && bD) 199 dir = Direction.LD; 200 else if (!bL && !bU && !bR && !bD) 201 dir = Direction.STOP; 202 203 } 204 205 // 坦克自己向哪个方向移动,它自己最清楚; 206 public void KeyPressed(KeyEvent e) { 207 // 获得所按下的键所对应的虚拟码: 208 // Returns the integer keyCode associated with the key in this event 209 int key = e.getKeyCode(); 210 // 判断不同的按键,指挥坦克的运动方向 211 switch (key) { 212 case KeyEvent.VK_LEFT: 213 bL = true; 214 break; 215 case KeyEvent.VK_UP: 216 bU = true; 217 break; 218 case KeyEvent.VK_RIGHT: 219 bR = true; 220 break; 221 case KeyEvent.VK_DOWN: 222 bD = true; 223 break; 224 } 225 locateDirection(); 226 } 227 228 public void keyReleased(KeyEvent e) { 229 int key = e.getKeyCode(); 230 // 判断不同的按键,指挥坦克的运动方向 231 // 哪个键按下了,就把对应方向的布尔类型置为false 232 switch (key) { 233 // 为了防止一直按着Ctrl键的时候,炮弹太过于密集 234 // 因此我们定义在Ctrl键抬起的时候才发炮弹 235 // 这样炮弹不至于太过密集 236 case KeyEvent.VK_CONTROL: 237 fire(); 238 break; 239 case KeyEvent.VK_LEFT: 240 bL = false; 241 break; 242 case KeyEvent.VK_UP: 243 bU = false; 244 break; 245 case KeyEvent.VK_RIGHT: 246 bR = false; 247 break; 248 case KeyEvent.VK_DOWN: 249 bD = false; 250 break; 251 } 252 // 重新定位一下 253 locateDirection(); 254 } 255 256 public Missile fire() { 257 // 计算子弹的位置,使得子弹从坦克的中间发出来 258 int x = this.x + Tank.WIDTH / 2 - Missile.WIDTH / 2; 259 int y = this.y + Tank.HEIGHT / 2 - Missile.HEIGHT / 2; 260 // 这个时候我们就根据炮筒的方向来发炮弹了,之前是根据坦克的方向来发炮弹 261 Missile m = new Missile(x, y, ptDir, tc); 262 // 将新产生的炮弹放置到List容器中 263 tc.missiles.add(m); 264 return m; 265 } 266 267 //拿到包围坦克的那个方块 268 public Rectangle getRect() { 269 270 return new Rectangle(x,y,WIDTH,HEIGHT); 271 } 272 }
Missile:
1 import java.awt.Color; 2 import java.awt.Graphics; 3 import java.awt.Rectangle; 4 import java.util.List; 5 6 public class Missile { 7 // 炮弹的移动速度,不要比坦克的移动速度慢,不然你看到的是满屏的坦克追着炮弹跑 8 public static final int XSPEED = 10; 9 public static final int YSPEED = 10; 10 // 将子弹的高度和宽度设置为常量 11 public static final int WIDTH = 10; 12 public static final int HEIGHT = 10; 13 // 炮弹自己的三个属性 14 int x; 15 int y; 16 Tank.Direction dir; 17 18 // 定义一个布尔类型的变量来判断炮弹是否已经消亡 19 private boolean live = true; 20 //我们在Missile类中也持有一个TankClient的引用 21 //在炮弹出界的时候就可以从装炮弹的missiles集合中去除该炮弹,不再对其重画 22 private TankClient tc; 23 24 public boolean isLive() { 25 return live; 26 } 27 28 public Missile(int x, int y, Tank.Direction dir) { 29 this.x = x; 30 this.y = y; 31 this.dir = dir; 32 } 33 public Missile(int x,int y,Tank.Direction dir,TankClient tc){ 34 this(x, y, dir); 35 this.tc=tc; 36 } 37 38 // 炮弹自己的draw方法 39 public void draw(Graphics g) { 40 //炮弹消亡就不需要再画出来了 41 if(!live){ 42 tc.missiles.remove(this); 43 return; 44 } 45 Color c = g.getColor(); 46 g.setColor(Color.BLACK); 47 // 炮弹形状不要比坦克大,这里设置成10,10; 48 g.fillOval(x, y, WIDTH, HEIGHT); 49 g.setColor(c); 50 move(); 51 } 52 53 public void move() { 54 switch (dir) { 55 case L: 56 x -= XSPEED; 57 break; 58 case R: 59 x += XSPEED; 60 break; 61 case U: 62 y -= YSPEED; 63 break; 64 case D: 65 y += YSPEED; 66 break; 67 case LU: 68 x -= XSPEED; 69 y -= YSPEED; 70 break; 71 case LD: 72 x -= XSPEED; 73 y += YSPEED; 74 break; 75 case RU: 76 x += XSPEED; 77 y -= YSPEED; 78 break; 79 case RD: 80 x += XSPEED; 81 y += YSPEED; 82 break; 83 // 炮弹就没有STOP这个枚举类型的值了 84 /* 85 * case STOP: break; 86 */ 87 } 88 // 判断炮弹出边界则消亡 89 // 注意x,y只有正数值,x向右递增,y向下递增 90 if (x < 0 || y < 0 || x > TankClient.GAME_WIDTH 91 || y > TankClient.GAME_HEIGHT) { 92 live = false; 93 } 94 } 95 public boolean hitTank(Tank t){ 96 //炮弹的方框和坦克的方框碰在一起了并且坦克是存活着的 97 if(this.getRect().intersects(t.getRect())&&t.isLive()){ 98 t.setLive(false); 99 this.live=false; 100 101 //炮弹击中坦克,发生爆炸 102 Explode e=new Explode(x, y, tc); 103 tc.explodes.add(e); 104 return true; 105 } 106 return false; 107 } 108 //碰撞检测类Rectangle 109 //拿到包围在炮弹周围的小方块 110 public Rectangle getRect(){ 111 return new Rectangle(x,y,WIDTH,HEIGHT); 112 } 113 114 //添加hitTanks方法 115 public boolean hitTanks(List<Tank> tanks){ 116 for(int i=0;i<tanks.size();i++){ 117 if(hitTank(tanks.get(i))){ 118 return true; 119 } 120 } 121 return false; 122 123 } 124 }
Explode:
1 import java.awt.*; 2 3 public class Explode { 4 //爆炸的位置 5 int x, y; 6 //爆炸是否存在 7 private boolean live = true; 8 9 //持有一个Tankclient的引用 10 private TankClient tc ; 11 12 //定义不同直径大小的爆炸 13 int[] diameter = {4, 7, 12, 18, 26, 32, 49, 30, 14, 6}; 14 //爆炸发生到哪一个阶段了,对应相应大小的直径 15 int step = 0; 16 17 public Explode(int x, int y, TankClient tc) { 18 this.x = x; 19 this.y = y; 20 this.tc = tc; 21 } 22 23 public void draw(Graphics g) { 24 if(!live) { 25 //爆炸发生,将相应直径的爆炸圆从集合explodes中去除 26 tc.explodes.remove(this); 27 return; 28 } 29 30 if(step == diameter.length) { 31 live = false; 32 step = 0; 33 return; 34 } 35 36 Color c = g.getColor(); 37 g.setColor(Color.ORANGE); 38 39 //把不同的圆画出来 40 g.fillOval(x, y, diameter[step], diameter[step]); 41 g.setColor(c); 42 43 step ++; 44 } 45 }
TankClient:
1 import java.awt.*; 2 import java.awt.event.*; 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class TankClient extends Frame { 7 // 设置成常量,方便以后的改动 8 public static final int GAME_WIDTH = 800; 9 public static final int GAME_HEIGHT = 600; 10 11 // 将当前的TankClient对象传递给myTank; 12 // 目的是方便我们在Tank这个类中访问m(炮弹对象)这个成员变量 13 // 其实就是在Tank类中持有TankClient类对象的一个引用 14 15 //我们这里new我们自己的坦克,用下面的方式 16 Tank myTank = new Tank(50, 50, true,this); 17 18 /*//新建敌方坦克,(不再需要这个了) 19 Tank enemyTank=new Tank(100,100,false,this);*/ 20 //定义爆炸 21 Explode e=new Explode(70, 70, this); 22 //定义一个集合,多个爆炸点 23 List<Explode> explodes=new ArrayList<Explode>(); 24 25 //使用容器装炮弹 26 List<Missile> missiles=new ArrayList<Missile>(); 27 28 //用容器来装敌人的Tank 29 List<Tank> tanks=new ArrayList<Tank>(); 30 // 定义虚拟图片,方便后期的一次性显示 31 Image offScreenImage = null; 32 33 34 public void paint(Graphics g) { 35 //记录屏幕上的子弹数目 36 g.drawString("missiles count:" + missiles.size(), 10, 50); 37 //添加上方标记栏,记录爆炸次数 38 g.drawString("explodes count:" + explodes.size(), 10, 70); 39 //记录现在屏幕上一共有多少敌方坦克 40 g.drawString("tanks count:" + tanks.size(), 10, 90); 41 42 43 //遍历结合,发出多发炮弹 44 for(int i=0;i<missiles.size();i++){ 45 Missile m=missiles.get(i); 46 //对于每一发炮弹,都可以将tanks集合中的敌方炮弹干掉 47 m.hitTanks(tanks); 48 m.draw(g); 49 } 50 51 for(int i=0;i<explodes.size();i++){ 52 Explode e=explodes.get(i); 53 e.draw(g); 54 } 55 56 for(int i=0;i<tanks.size();i++){ 57 Tank t=tanks.get(i); 58 t.draw(g); 59 } 60 // 不改变前景色 61 myTank.draw(g); 62 } 63 64 // 刷新操作 65 public void update(Graphics g) { 66 if (offScreenImage == null) { 67 offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT); 68 } 69 Graphics gOffScreen = offScreenImage.getGraphics(); 70 Color c = gOffScreen.getColor(); 71 gOffScreen.setColor(Color.GREEN); 72 gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); 73 gOffScreen.setColor(c); 74 paint(gOffScreen); 75 g.drawImage(offScreenImage, 0, 0, null); 76 } 77 78 public void lauchFrame() { 79 80 //添加多辆坦克 81 for(int i=0;i<10;i++){ 82 tanks.add(new Tank(50+40*(i+1),50,false)); 83 } 84 // this.setLocation(400, 300); 85 this.setSize(GAME_WIDTH, GAME_HEIGHT); 86 this.setTitle("TankWar"); 87 this.addWindowListener(new WindowAdapter() { 88 public void windowClosing(WindowEvent e) { 89 System.exit(0); 90 } 91 }); 92 this.setResizable(false); 93 this.setBackground(Color.GREEN); 94 95 this.addKeyListener(new KeyMonitor()); 96 97 setVisible(true); 98 99 new Thread(new PaintThread()).start(); 100 } 101 102 public static void main(String[] args) { 103 TankClient tc = new TankClient(); 104 tc.lauchFrame(); 105 } 106 107 private class PaintThread implements Runnable { 108 109 public void run() { 110 while (true) { 111 repaint(); 112 try { 113 //为了爆炸效果,改成1000 114 Thread.sleep(50); 115 } catch (InterruptedException e) { 116 e.printStackTrace(); 117 } 118 } 119 } 120 } 121 122 // 创建键盘时间监听 123 private class KeyMonitor extends KeyAdapter { 124 125 // 直接调用myTank自己的方法根据相应的按键信息进行移动 126 public void keyPressed(KeyEvent e) { 127 myTank.KeyPressed(e); 128 // 添加了处理键抬起的事件,可以控制坦克起步以后的状态 129 // 而不是一直按照一个方向走下去 130 } 131 132 public void keyReleased(KeyEvent e) { 133 myTank.keyReleased(e); 134 } 135 136 } 137 }
版本1.9
功能:让敌军坦克更加智能
步骤:
1)让敌军坦克动起来
构造函数中可以指定方向
new敌军坦克的时候指定敌军坦克的方向
2)让敌军坦克向随机方向移动
(Tank)静态的,添加随机数产生器 java.util.Random
move完成后,如果是敌军坦克的,随机产生一个数,来设定坦克下一个方向
Direction.values();
3)让敌军坦克向随机方向移动随机的步骤
添加变量,记录随机步骤
当==0时,改变方向,否则,只是随机步骤递减
4)让敌军坦克发射炮弹
本军炮弹不打本军
炮弹添加好坏bGood,根据好坏画不同颜色
修改炮弹的构造方法
修改Tank的fire方法
修改hitTank方法,好不能打好,坏不能打坏
5)敌军炮火不能太猛烈
Tank:
1 import java.awt.*; 2 import java.awt.event.*; 3 import java.util.Random; 4 5 public class Tank { 6 // 方便后期更改 7 public static final int XSPEED = 5; 8 public static final int YSPEED = 5; 9 // 将坦克的高度和宽度设置为常量 10 public static final int WIDTH = 30; 11 public static final int HEIGHT = 30; 12 TankClient tc; 13 // 区别是我方坦克还是地方坦克,方便据此进行不同的设置 14 private boolean good; 15 16 public boolean isGood() { 17 return good; 18 } 19 20 public void setGood(boolean good) { 21 this.good = good; 22 } 23 24 // 判断坦克生死的变量 25 private boolean live = true; 26 27 public boolean isLive() { 28 return live; 29 } 30 31 public void setLive(boolean live) { 32 this.live = live; 33 } 34 35 private int x; 36 private int y; 37 38 // 随机数产生器,方便敌方坦克可以任意移动 39 private static Random r = new Random(); 40 // 添加记录按键状态的布尔量 41 private boolean bL = false; 42 private boolean bR = false; 43 private boolean bU = false; 44 private boolean bD = false; 45 46 // 添加代表方向的量(使用枚举) 47 enum Direction { 48 L, R, U, D, LU, LD, RU, RD, STOP 49 }; 50 51 private Direction dir = Direction.STOP; 52 53 // 定义炮筒的方向,我们想办法将炮筒的方法调整成和坦克移动方向一致; 54 // 我们这里会用一条直线来表示炮筒:模拟炮筒 55 // 我们要根据炮筒的方向画直线表示炮筒 56 Direction ptDir = Direction.D; 57 58 // 为了让敌方坦克在一定方向运动移动时间再自动变换方向 59 private int step = r.nextInt(12) + 3; 60 61 // 更改构造函数 62 public Tank(int x, int y, boolean good) { 63 this.x = x; 64 this.y = y; 65 this.good = good; 66 } 67 68 // 这个位置的构造函数也相应进行了更改 69 public Tank(int x, int y, boolean good, Direction dir, TankClient tc) { 70 // 调用那个有两个参数的构造方法 71 this(x, y, good); 72 this.dir = dir; 73 // 在这个位置初始化tc 74 this.tc = tc; 75 } 76 77 // Tank对象的draw方法 78 public void draw(Graphics g) { 79 if (!live) { 80 // 如果死亡的是敌方坦克,在tanks集合中去除该坦克 81 if (!good) { 82 tc.tanks.remove(this); 83 } 84 // 如果是我方坦克,直接返回 85 return; 86 } 87 Color c = g.getColor(); 88 if (good) { 89 g.setColor(Color.RED); 90 } else { 91 g.setColor(Color.PINK); 92 } 93 g.fillOval(x, y, WIDTH, HEIGHT); 94 g.setColor(c); 95 // 根据炮筒的方向画直线来表示我们坦克的炮筒 96 switch (ptDir) { 97 case L: 98 // 画直线:四个参数分别代表:坦克中心点的坐标 直线的另一头的的坐标 99 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 100 + Tank.HEIGHT / 2); 101 break; 102 case R: 103 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 104 y + Tank.HEIGHT / 2); 105 106 break; 107 case U: 108 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 109 / 2, y); 110 111 break; 112 case D: 113 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 114 / 2, y + Tank.HEIGHT); 115 116 break; 117 case LU: 118 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y); 119 break; 120 case LD: 121 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 122 + Tank.HEIGHT); 123 124 break; 125 case RU: 126 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 127 y); 128 129 break; 130 case RD: 131 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 132 y + Tank.HEIGHT); 133 134 break; 135 /* 136 * case STOP: break; 137 */ 138 } 139 move(); 140 } 141 142 public void move() { 143 switch (dir) { 144 case L: 145 x -= XSPEED; 146 break; 147 case R: 148 x += XSPEED; 149 break; 150 case U: 151 y -= YSPEED; 152 break; 153 case D: 154 y += YSPEED; 155 break; 156 case LU: 157 x -= XSPEED; 158 y -= YSPEED; 159 break; 160 case LD: 161 x -= XSPEED; 162 y += YSPEED; 163 break; 164 case RU: 165 x += XSPEED; 166 y -= YSPEED; 167 break; 168 case RD: 169 x += XSPEED; 170 y += YSPEED; 171 break; 172 173 case STOP: 174 break; 175 } 176 // 如果坦克不是停着的,则将炮筒调整至和坦克移动的方向相同 177 if (this.dir != Direction.STOP) { 178 this.ptDir = this.dir; 179 } 180 if (x < 0) { 181 x = 0; 182 } 183 // 因为我们的游戏界面有那个missileCount标签,所以在y轴方向不能用y是否<0进行判断 184 // 否则的话我们的坦克可以从上面出去 185 if (y < 50) { 186 y = 50; 187 } 188 if (x + Tank.WIDTH > TankClient.GAME_WIDTH) { 189 x = TankClient.GAME_WIDTH - Tank.WIDTH; 190 } 191 if (y + Tank.HEIGHT > TankClient.GAME_HEIGHT) { 192 y = TankClient.GAME_HEIGHT - Tank.HEIGHT; 193 } 194 // 在move方法中判断如果是敌方坦克 195 if (!good) { 196 Direction[] dirs = Direction.values(); 197 // 定义敌方坦克的移动 198 if (step == 0) { 199 step = r.nextInt(12) + 3; 200 int rn = r.nextInt(dirs.length); 201 dir = dirs[rn]; 202 } 203 // 使得敌方坦克每隔一定时间就变化方向,values:方向枚举转化为数组 204 205 step--; 206 //用随机数,使得敌方坦克可以发炮弹,但是不要太猛烈 207 if(r.nextInt(40)>38){ 208 this.fire(); 209 } 210 } 211 } 212 213 public void locateDirection() { 214 if (bL && !bU && !bR && !bD) 215 dir = Direction.L; 216 else if (bL && bU && !bR && !bD) 217 dir = Direction.LU; 218 else if (!bL && bU && !bR && !bD) 219 dir = Direction.U; 220 else if (!bL && bU && bR && !bD) 221 dir = Direction.RU; 222 else if (!bL && !bU && bR && !bD) 223 dir = Direction.R; 224 else if (!bL && !bU && bR && bD) 225 dir = Direction.RD; 226 else if (!bL && !bU && !bR && bD) 227 dir = Direction.D; 228 else if (bL && !bU && !bR && bD) 229 dir = Direction.LD; 230 else if (!bL && !bU && !bR && !bD) 231 dir = Direction.STOP; 232 233 } 234 235 // 坦克自己向哪个方向移动,它自己最清楚; 236 public void KeyPressed(KeyEvent e) { 237 // 获得所按下的键所对应的虚拟码: 238 // Returns the integer keyCode associated with the key in this event 239 int key = e.getKeyCode(); 240 // 判断不同的按键,指挥坦克的运动方向 241 switch (key) { 242 case KeyEvent.VK_LEFT: 243 bL = true; 244 break; 245 case KeyEvent.VK_UP: 246 bU = true; 247 break; 248 case KeyEvent.VK_RIGHT: 249 bR = true; 250 break; 251 case KeyEvent.VK_DOWN: 252 bD = true; 253 break; 254 } 255 locateDirection(); 256 } 257 258 public void keyReleased(KeyEvent e) { 259 int key = e.getKeyCode(); 260 // 判断不同的按键,指挥坦克的运动方向 261 // 哪个键按下了,就把对应方向的布尔类型置为false 262 switch (key) { 263 // 为了防止一直按着Ctrl键的时候,炮弹太过于密集 264 // 因此我们定义在Ctrl键抬起的时候才发炮弹 265 // 这样炮弹不至于太过密集 266 case KeyEvent.VK_CONTROL: 267 fire(); 268 break; 269 case KeyEvent.VK_LEFT: 270 bL = false; 271 break; 272 case KeyEvent.VK_UP: 273 bU = false; 274 break; 275 case KeyEvent.VK_RIGHT: 276 bR = false; 277 break; 278 case KeyEvent.VK_DOWN: 279 bD = false; 280 break; 281 } 282 // 重新定位一下 283 locateDirection(); 284 } 285 286 public Missile fire() { 287 if(!live){ 288 return null; 289 } 290 // 计算子弹的位置,使得子弹从坦克的中间发出来 291 int x = this.x + Tank.WIDTH / 2 - Missile.WIDTH / 2; 292 int y = this.y + Tank.HEIGHT / 2 - Missile.HEIGHT / 2; 293 // 这个时候我们就根据炮筒的方向来发炮弹了,之前是根据坦克的方向来发炮弹 294 Missile m = new Missile(x, y, good,ptDir, tc); 295 // 将新产生的炮弹放置到List容器中 296 tc.missiles.add(m); 297 return m; 298 } 299 300 // 拿到包围坦克的那个方块 301 public Rectangle getRect() { 302 303 return new Rectangle(x, y, WIDTH, HEIGHT); 304 } 305 }
Missile:
1 import java.awt.Color; 2 import java.awt.Graphics; 3 import java.awt.Rectangle; 4 import java.util.List; 5 6 public class Missile { 7 // 炮弹的移动速度,不要比坦克的移动速度慢,不然你看到的是满屏的坦克追着炮弹跑 8 public static final int XSPEED = 10; 9 public static final int YSPEED = 10; 10 // 将子弹的高度和宽度设置为常量 11 public static final int WIDTH = 10; 12 public static final int HEIGHT = 10; 13 // 炮弹自己的三个属性 14 int x; 15 int y; 16 Tank.Direction dir; 17 18 //同一阵营的的坦克发出的子弹不能伤害自己人 19 private boolean good; 20 // 定义一个布尔类型的变量来判断炮弹是否已经消亡 21 private boolean live = true; 22 //我们在Missile类中也持有一个TankClient的引用 23 //在炮弹出界的时候就可以从装炮弹的missiles集合中去除该炮弹,不再对其重画 24 private TankClient tc; 25 26 public boolean isLive() { 27 return live; 28 } 29 30 public Missile(int x, int y, Tank.Direction dir) { 31 this.x = x; 32 this.y = y; 33 this.dir = dir; 34 } 35 public Missile(int x,int y,boolean good,Tank.Direction dir,TankClient tc){ 36 this(x, y, dir); 37 this.good=good; 38 this.tc=tc; 39 } 40 41 // 炮弹自己的draw方法 42 public void draw(Graphics g) { 43 //炮弹消亡就不需要再画出来了 44 if(!live){ 45 tc.missiles.remove(this); 46 return; 47 } 48 Color c = g.getColor(); 49 g.setColor(Color.BLACK); 50 // 炮弹形状不要比坦克大,这里设置成10,10; 51 g.fillOval(x, y, WIDTH, HEIGHT); 52 g.setColor(c); 53 move(); 54 } 55 56 public void move() { 57 switch (dir) { 58 case L: 59 x -= XSPEED; 60 break; 61 case R: 62 x += XSPEED; 63 break; 64 case U: 65 y -= YSPEED; 66 break; 67 case D: 68 y += YSPEED; 69 break; 70 case LU: 71 x -= XSPEED; 72 y -= YSPEED; 73 break; 74 case LD: 75 x -= XSPEED; 76 y += YSPEED; 77 break; 78 case RU: 79 x += XSPEED; 80 y -= YSPEED; 81 break; 82 case RD: 83 x += XSPEED; 84 y += YSPEED; 85 break; 86 // 炮弹就没有STOP这个枚举类型的值了 87 /* 88 * case STOP: break; 89 */ 90 } 91 // 判断炮弹出边界则消亡 92 // 注意x,y只有正数值,x向右递增,y向下递增 93 if (x < 0 || y < 0 || x > TankClient.GAME_WIDTH 94 || y > TankClient.GAME_HEIGHT) { 95 live = false; 96 } 97 } 98 public boolean hitTank(Tank t){ 99 //炮弹的方框和坦克的方框碰在一起了并且坦克是存活着的,后面的判断我们是一伙的我就不打你了 100 if(this.live&&this.getRect().intersects(t.getRect())&&t.isLive()&&this.good!=t.isGood()){ 101 t.setLive(false); 102 this.live=false; 103 104 //炮弹击中坦克,发生爆炸 105 Explode e=new Explode(x, y, tc); 106 tc.explodes.add(e); 107 return true; 108 } 109 return false; 110 } 111 //碰撞检测类Rectangle 112 //拿到包围在炮弹周围的小方块 113 public Rectangle getRect(){ 114 return new Rectangle(x,y,WIDTH,HEIGHT); 115 } 116 117 //添加hitTanks方法 118 public boolean hitTanks(List<Tank> tanks){ 119 for(int i=0;i<tanks.size();i++){ 120 if(hitTank(tanks.get(i))){ 121 return true; 122 } 123 } 124 return false; 125 126 } 127 }
Explode(这个类没有变化):
1 import java.awt.*; 2 3 public class Explode { 4 //爆炸的位置 5 int x, y; 6 //爆炸是否存在 7 private boolean live = true; 8 9 //持有一个Tankclient的引用 10 private TankClient tc ; 11 12 //定义不同直径大小的爆炸 13 int[] diameter = {4, 7, 12, 18, 26, 32, 49, 30, 14, 6}; 14 //爆炸发生到哪一个阶段了,对应相应大小的直径 15 int step = 0; 16 17 public Explode(int x, int y, TankClient tc) { 18 this.x = x; 19 this.y = y; 20 this.tc = tc; 21 } 22 23 public void draw(Graphics g) { 24 if(!live) { 25 //爆炸发生,将相应直径的爆炸圆从集合explodes中去除 26 tc.explodes.remove(this); 27 return; 28 } 29 30 if(step == diameter.length) { 31 live = false; 32 step = 0; 33 return; 34 } 35 36 Color c = g.getColor(); 37 g.setColor(Color.ORANGE); 38 39 //把不同的圆画出来 40 g.fillOval(x, y, diameter[step], diameter[step]); 41 g.setColor(c); 42 43 step ++; 44 } 45 }
Tankclient:
1 import java.awt.*; 2 import java.awt.event.*; 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class TankClient extends Frame { 7 // 设置成常量,方便以后的改动 8 public static final int GAME_WIDTH = 800; 9 public static final int GAME_HEIGHT = 600; 10 11 // 将当前的TankClient对象传递给myTank; 12 // 目的是方便我们在Tank这个类中访问m(炮弹对象)这个成员变量 13 // 其实就是在Tank类中持有TankClient类对象的一个引用 14 15 // 我们这里new我们自己的坦克 16 Tank myTank = new Tank(50, 50, true, Tank.Direction.STOP, this); 17 18 /* 19 * //新建敌方坦克,(不再需要这个了) Tank enemyTank=new Tank(100,100,false,this); 20 */ 21 // 定义爆炸 22 Explode e = new Explode(70, 70, this); 23 // 定义一个集合,多个爆炸点 24 List<Explode> explodes = new ArrayList<Explode>(); 25 26 // 使用容器装炮弹 27 List<Missile> missiles = new ArrayList<Missile>(); 28 29 // 用容器来装敌人的Tank 30 List<Tank> tanks = new ArrayList<Tank>(); 31 // 定义虚拟图片,方便后期的一次性显示 32 Image offScreenImage = null; 33 34 public void paint(Graphics g) { 35 // 记录屏幕上的子弹数目 36 g.drawString("missiles count:" + missiles.size(), 10, 50); 37 // 添加上方标记栏,记录爆炸次数 38 g.drawString("explodes count:" + explodes.size(), 10, 70); 39 // 记录现在屏幕上一共有多少敌方坦克 40 g.drawString("tanks count:" + tanks.size(), 10, 90); 41 42 // 遍历结合,发出多发炮弹 43 for (int i = 0; i < missiles.size(); i++) { 44 Missile m = missiles.get(i); 45 // 对于每一发炮弹,都可以将tanks集合中的敌方炮弹干掉 46 m.hitTanks(tanks); 47 m.hitTank(myTank); 48 m.draw(g); 49 } 50 51 for (int i = 0; i < explodes.size(); i++) { 52 Explode e = explodes.get(i); 53 e.draw(g); 54 } 55 56 for (int i = 0; i < tanks.size(); i++) { 57 Tank t = tanks.get(i); 58 t.draw(g); 59 } 60 // 不改变前景色 61 myTank.draw(g); 62 } 63 64 // 刷新操作 65 public void update(Graphics g) { 66 if (offScreenImage == null) { 67 offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT); 68 } 69 Graphics gOffScreen = offScreenImage.getGraphics(); 70 Color c = gOffScreen.getColor(); 71 gOffScreen.setColor(Color.GREEN); 72 gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); 73 gOffScreen.setColor(c); 74 paint(gOffScreen); 75 g.drawImage(offScreenImage, 0, 0, null); 76 } 77 78 public void lauchFrame() { 79 80 // 添加多辆坦克 81 for (int i = 0; i < 10; i++) { 82 tanks.add(new Tank(50 + 40 * (i + 1), 50, false, Tank.Direction.D, 83 this)); 84 } 85 // this.setLocation(400, 300); 86 this.setSize(GAME_WIDTH, GAME_HEIGHT); 87 this.setTitle("TankWar"); 88 this.addWindowListener(new WindowAdapter() { 89 public void windowClosing(WindowEvent e) { 90 System.exit(0); 91 } 92 }); 93 this.setResizable(false); 94 this.setBackground(Color.GREEN); 95 96 this.addKeyListener(new KeyMonitor()); 97 98 setVisible(true); 99 100 new Thread(new PaintThread()).start(); 101 } 102 103 public static void main(String[] args) { 104 TankClient tc = new TankClient(); 105 tc.lauchFrame(); 106 } 107 108 private class PaintThread implements Runnable { 109 110 public void run() { 111 while (true) { 112 repaint(); 113 try { 114 // 为了爆炸效果,改成1000 115 Thread.sleep(50); 116 } catch (InterruptedException e) { 117 e.printStackTrace(); 118 } 119 } 120 } 121 } 122 123 // 创建键盘时间监听 124 private class KeyMonitor extends KeyAdapter { 125 126 // 直接调用myTank自己的方法根据相应的按键信息进行移动 127 public void keyPressed(KeyEvent e) { 128 myTank.KeyPressed(e); 129 // 添加了处理键抬起的事件,可以控制坦克起步以后的状态 130 // 而不是一直按照一个方向走下去 131 } 132 133 public void keyReleased(KeyEvent e) { 134 myTank.keyReleased(e); 135 } 136 137 } 138 }
版本2.0
功能:添加两堵墙
步骤:1)建Wall类、建立Wall对象、画出来
2)让每一颗子弹打击每一堵墙
hitWall()方法
注意:子弹速度不能太快,否则很容易穿过墙
3)让坦克不能穿过墙
记录上一次的位置oldX, oldY
修改构造函数
每次move之前纪录上一次位置
添加stay方法
记录移动前的位置
当撞到时回到移动前的位置
当碰到墙的时候stay
具体代码实现:
Tank:
1 import java.awt.*; 2 import java.awt.event.*; 3 import java.util.Random; 4 5 public class Tank { 6 // 方便后期更改 7 public static final int XSPEED = 5; 8 public static final int YSPEED = 5; 9 // 将坦克的高度和宽度设置为常量 10 public static final int WIDTH = 30; 11 public static final int HEIGHT = 30; 12 TankClient tc; 13 // 区别是我方坦克还是地方坦克,方便据此进行不同的设置 14 private boolean good; 15 16 public boolean isGood() { 17 return good; 18 } 19 20 public void setGood(boolean good) { 21 this.good = good; 22 } 23 24 // 判断坦克生死的变量 25 private boolean live = true; 26 27 public boolean isLive() { 28 return live; 29 } 30 31 public void setLive(boolean live) { 32 this.live = live; 33 } 34 35 private int x; 36 private int y; 37 38 //记录坦克上一步的位置,防止坦克一碰到wall,就会依附在上面 39 private int oldx; 40 private int oldy; 41 42 // 随机数产生器,方便敌方坦克可以任意移动 43 private static Random r = new Random(); 44 // 添加记录按键状态的布尔量 45 private boolean bL = false; 46 private boolean bR = false; 47 private boolean bU = false; 48 private boolean bD = false; 49 50 // 添加代表方向的量(使用枚举) 51 enum Direction { 52 L, R, U, D, LU, LD, RU, RD, STOP 53 }; 54 55 private Direction dir = Direction.STOP; 56 57 // 定义炮筒的方向,我们想办法将炮筒的方法调整成和坦克移动方向一致; 58 // 我们这里会用一条直线来表示炮筒:模拟炮筒 59 // 我们要根据炮筒的方向画直线表示炮筒 60 Direction ptDir = Direction.D; 61 62 // 为了让敌方坦克在一定方向运动移动时间再自动变换方向 63 private int step = r.nextInt(12) + 3; 64 65 // 更改构造函数 66 public Tank(int x, int y, boolean good) { 67 this.x = x; 68 this.y = y; 69 this.oldx=x; 70 this.oldy=y; 71 this.good = good; 72 } 73 74 // 这个位置的构造函数也相应进行了更改 75 public Tank(int x, int y, boolean good, Direction dir, TankClient tc) { 76 // 调用那个有两个参数的构造方法 77 this(x, y, good); 78 this.dir = dir; 79 // 在这个位置初始化tc 80 this.tc = tc; 81 } 82 83 // Tank对象的draw方法 84 public void draw(Graphics g) { 85 if (!live) { 86 // 如果死亡的是敌方坦克,在tanks集合中去除该坦克 87 if (!good) { 88 tc.tanks.remove(this); 89 } 90 // 如果是我方坦克,直接返回 91 return; 92 } 93 Color c = g.getColor(); 94 if (good) { 95 g.setColor(Color.RED); 96 } else { 97 g.setColor(Color.PINK); 98 } 99 g.fillOval(x, y, WIDTH, HEIGHT); 100 g.setColor(c); 101 // 根据炮筒的方向画直线来表示我们坦克的炮筒 102 switch (ptDir) { 103 case L: 104 // 画直线:四个参数分别代表:坦克中心点的坐标 直线的另一头的的坐标 105 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 106 + Tank.HEIGHT / 2); 107 break; 108 case R: 109 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 110 y + Tank.HEIGHT / 2); 111 112 break; 113 case U: 114 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 115 / 2, y); 116 117 break; 118 case D: 119 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 120 / 2, y + Tank.HEIGHT); 121 122 break; 123 case LU: 124 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y); 125 break; 126 case LD: 127 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 128 + Tank.HEIGHT); 129 130 break; 131 case RU: 132 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 133 y); 134 135 break; 136 case RD: 137 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 138 y + Tank.HEIGHT); 139 140 break; 141 /* 142 * case STOP: break; 143 */ 144 } 145 move(); 146 } 147 148 public void move() { 149 //记录坦克上一步的位置 150 this.oldx=x; 151 this.oldy=y; 152 switch (dir) { 153 case L: 154 x -= XSPEED; 155 break; 156 case R: 157 x += XSPEED; 158 break; 159 case U: 160 y -= YSPEED; 161 break; 162 case D: 163 y += YSPEED; 164 break; 165 case LU: 166 x -= XSPEED; 167 y -= YSPEED; 168 break; 169 case LD: 170 x -= XSPEED; 171 y += YSPEED; 172 break; 173 case RU: 174 x += XSPEED; 175 y -= YSPEED; 176 break; 177 case RD: 178 x += XSPEED; 179 y += YSPEED; 180 break; 181 182 case STOP: 183 break; 184 } 185 // 如果坦克不是停着的,则将炮筒调整至和坦克移动的方向相同 186 if (this.dir != Direction.STOP) { 187 this.ptDir = this.dir; 188 } 189 if (x < 0) { 190 x = 0; 191 } 192 // 因为我们的游戏界面有那个missileCount标签,所以在y轴方向不能用y是否<0进行判断 193 // 否则的话我们的坦克可以从上面出去 194 if (y < 50) { 195 y = 50; 196 } 197 if (x + Tank.WIDTH > TankClient.GAME_WIDTH) { 198 x = TankClient.GAME_WIDTH - Tank.WIDTH; 199 } 200 if (y + Tank.HEIGHT > TankClient.GAME_HEIGHT) { 201 y = TankClient.GAME_HEIGHT - Tank.HEIGHT; 202 } 203 // 在move方法中判断如果是敌方坦克 204 if (!good) { 205 Direction[] dirs = Direction.values(); 206 // 定义敌方坦克的移动 207 if (step == 0) { 208 step = r.nextInt(12) + 3; 209 int rn = r.nextInt(dirs.length); 210 dir = dirs[rn]; 211 } 212 // 使得敌方坦克每隔一定时间就变化方向,values:方向枚举转化为数组 213 214 step--; 215 //用随机数,使得敌方坦克可以发炮弹,但是不要太猛烈 216 if(r.nextInt(40)>38){ 217 this.fire(); 218 } 219 } 220 } 221 222 public void locateDirection() { 223 if (bL && !bU && !bR && !bD) 224 dir = Direction.L; 225 else if (bL && bU && !bR && !bD) 226 dir = Direction.LU; 227 else if (!bL && bU && !bR && !bD) 228 dir = Direction.U; 229 else if (!bL && bU && bR && !bD) 230 dir = Direction.RU; 231 else if (!bL && !bU && bR && !bD) 232 dir = Direction.R; 233 else if (!bL && !bU && bR && bD) 234 dir = Direction.RD; 235 else if (!bL && !bU && !bR && bD) 236 dir = Direction.D; 237 else if (bL && !bU && !bR && bD) 238 dir = Direction.LD; 239 else if (!bL && !bU && !bR && !bD) 240 dir = Direction.STOP; 241 242 } 243 244 private void stay(){ 245 x=oldx; 246 y=oldy; 247 } 248 // 坦克自己向哪个方向移动,它自己最清楚; 249 public void KeyPressed(KeyEvent e) { 250 // 获得所按下的键所对应的虚拟码: 251 // Returns the integer keyCode associated with the key in this event 252 int key = e.getKeyCode(); 253 // 判断不同的按键,指挥坦克的运动方向 254 switch (key) { 255 case KeyEvent.VK_LEFT: 256 bL = true; 257 break; 258 case KeyEvent.VK_UP: 259 bU = true; 260 break; 261 case KeyEvent.VK_RIGHT: 262 bR = true; 263 break; 264 case KeyEvent.VK_DOWN: 265 bD = true; 266 break; 267 } 268 locateDirection(); 269 } 270 271 public void keyReleased(KeyEvent e) { 272 int key = e.getKeyCode(); 273 // 判断不同的按键,指挥坦克的运动方向 274 // 哪个键按下了,就把对应方向的布尔类型置为false 275 switch (key) { 276 // 为了防止一直按着Ctrl键的时候,炮弹太过于密集 277 // 因此我们定义在Ctrl键抬起的时候才发炮弹 278 // 这样炮弹不至于太过密集 279 case KeyEvent.VK_CONTROL: 280 fire(); 281 break; 282 case KeyEvent.VK_LEFT: 283 bL = false; 284 break; 285 case KeyEvent.VK_UP: 286 bU = false; 287 break; 288 case KeyEvent.VK_RIGHT: 289 bR = false; 290 break; 291 case KeyEvent.VK_DOWN: 292 bD = false; 293 break; 294 } 295 // 重新定位一下 296 locateDirection(); 297 } 298 299 public Missile fire() { 300 if(!live){ 301 return null; 302 } 303 // 计算子弹的位置,使得子弹从坦克的中间发出来 304 int x = this.x + Tank.WIDTH / 2 - Missile.WIDTH / 2; 305 int y = this.y + Tank.HEIGHT / 2 - Missile.HEIGHT / 2; 306 // 这个时候我们就根据炮筒的方向来发炮弹了,之前是根据坦克的方向来发炮弹 307 Missile m = new Missile(x, y, good,ptDir, tc); 308 // 将新产生的炮弹放置到List容器中 309 tc.missiles.add(m); 310 return m; 311 } 312 313 // 拿到包围坦克的那个方块 314 public Rectangle getRect() { 315 316 return new Rectangle(x, y, WIDTH, HEIGHT); 317 } 318 319 public boolean collidesWithWall(Wall w){ 320 321 if(this.live&&this.getRect().intersects(w.getRect())){ 322 this.dir=Direction.STOP; 323 //当坦克撞到墙上的时候,停一下,再回到上一步的位置 324 this.stay(); 325 return true; 326 } 327 return false; 328 329 } 330 }
Missile:
1 import java.awt.Color; 2 import java.awt.Graphics; 3 import java.awt.Rectangle; 4 import java.util.List; 5 6 public class Missile { 7 // 炮弹的移动速度,不要比坦克的移动速度慢,不然你看到的是满屏的坦克追着炮弹跑 8 public static final int XSPEED = 10; 9 public static final int YSPEED = 10; 10 // 将子弹的高度和宽度设置为常量 11 public static final int WIDTH = 10; 12 public static final int HEIGHT = 10; 13 // 炮弹自己的三个属性 14 int x; 15 int y; 16 Tank.Direction dir; 17 18 //同一阵营的的坦克发出的子弹不能伤害自己人 19 private boolean good; 20 // 定义一个布尔类型的变量来判断炮弹是否已经消亡 21 private boolean live = true; 22 //我们在Missile类中也持有一个TankClient的引用 23 //在炮弹出界的时候就可以从装炮弹的missiles集合中去除该炮弹,不再对其重画 24 private TankClient tc; 25 26 public boolean isLive() { 27 return live; 28 } 29 30 public Missile(int x, int y, Tank.Direction dir) { 31 this.x = x; 32 this.y = y; 33 this.dir = dir; 34 } 35 public Missile(int x,int y,boolean good,Tank.Direction dir,TankClient tc){ 36 this(x, y, dir); 37 this.good=good; 38 this.tc=tc; 39 } 40 41 // 炮弹自己的draw方法 42 public void draw(Graphics g) { 43 //炮弹消亡就不需要再画出来了 44 if(!live){ 45 tc.missiles.remove(this); 46 return; 47 } 48 Color c = g.getColor(); 49 g.setColor(Color.BLACK); 50 // 炮弹形状不要比坦克大,这里设置成10,10; 51 g.fillOval(x, y, WIDTH, HEIGHT); 52 g.setColor(c); 53 move(); 54 } 55 56 public void move() { 57 switch (dir) { 58 case L: 59 x -= XSPEED; 60 break; 61 case R: 62 x += XSPEED; 63 break; 64 case U: 65 y -= YSPEED; 66 break; 67 case D: 68 y += YSPEED; 69 break; 70 case LU: 71 x -= XSPEED; 72 y -= YSPEED; 73 break; 74 case LD: 75 x -= XSPEED; 76 y += YSPEED; 77 break; 78 case RU: 79 x += XSPEED; 80 y -= YSPEED; 81 break; 82 case RD: 83 x += XSPEED; 84 y += YSPEED; 85 break; 86 // 炮弹就没有STOP这个枚举类型的值了 87 /* 88 * case STOP: break; 89 */ 90 } 91 // 判断炮弹出边界则消亡 92 // 注意x,y只有正数值,x向右递增,y向下递增 93 if (x < 0 || y < 0 || x > TankClient.GAME_WIDTH 94 || y > TankClient.GAME_HEIGHT) { 95 live = false; 96 } 97 } 98 public boolean hitTank(Tank t){ 99 //炮弹的方框和坦克的方框碰在一起了并且坦克是存活着的,后面的判断我们是一伙的我就不打你了 100 if(this.live&&this.getRect().intersects(t.getRect())&&t.isLive()&&this.good!=t.isGood()){ 101 t.setLive(false); 102 this.live=false; 103 104 //炮弹击中坦克,发生爆炸 105 Explode e=new Explode(x, y, tc); 106 tc.explodes.add(e); 107 return true; 108 } 109 return false; 110 } 111 //碰撞检测类Rectangle 112 //拿到包围在炮弹周围的小方块 113 public Rectangle getRect(){ 114 return new Rectangle(x,y,WIDTH,HEIGHT); 115 } 116 117 //添加hitTanks方法 118 public boolean hitTanks(List<Tank> tanks){ 119 for(int i=0;i<tanks.size();i++){ 120 if(hitTank(tanks.get(i))){ 121 return true; 122 } 123 } 124 return false; 125 126 } 127 public boolean hitWall(Wall w){ 128 if(this.live&&this.getRect().intersects(w.getRect())){ 129 this.live=false; 130 return true; 131 } 132 return false; 133 } 134 }
Explode:
1 import java.awt.*; 2 3 public class Explode { 4 //爆炸的位置 5 int x, y; 6 //爆炸是否存在 7 private boolean live = true; 8 9 //持有一个Tankclient的引用 10 private TankClient tc ; 11 12 //定义不同直径大小的爆炸 13 int[] diameter = {4, 7, 12, 18, 26, 32, 49, 30, 14, 6}; 14 //爆炸发生到哪一个阶段了,对应相应大小的直径 15 int step = 0; 16 17 public Explode(int x, int y, TankClient tc) { 18 this.x = x; 19 this.y = y; 20 this.tc = tc; 21 } 22 23 public void draw(Graphics g) { 24 if(!live) { 25 //爆炸发生,将相应直径的爆炸圆从集合explodes中去除 26 tc.explodes.remove(this); 27 return; 28 } 29 30 if(step == diameter.length) { 31 live = false; 32 step = 0; 33 return; 34 } 35 36 Color c = g.getColor(); 37 g.setColor(Color.ORANGE); 38 39 //把不同的圆画出来 40 g.fillOval(x, y, diameter[step], diameter[step]); 41 g.setColor(c); 42 43 step ++; 44 } 45 }
Wall:
1 import java.awt.Graphics; 2 import java.awt.Rectangle; 3 4 //墙 5 public class Wall { 6 int x,y,w,h; 7 TankClient tc; 8 public Wall(int x, int y, int w, int h, TankClient tc) { 9 super(); 10 this.x = x; 11 this.y = y; 12 this.w = w; 13 this.h = h; 14 this.tc = tc; 15 } 16 public void draw(Graphics g){ 17 g.fillRect(x, y, w, h); 18 } 19 //碰撞检测 20 public Rectangle getRect(){ 21 return new Rectangle(x,y,w,h); 22 } 23 }
TankClient:
1 import java.awt.*; 2 import java.awt.event.*; 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class TankClient extends Frame { 7 // 设置成常量,方便以后的改动 8 public static final int GAME_WIDTH = 800; 9 public static final int GAME_HEIGHT = 600; 10 11 // 将当前的TankClient对象传递给myTank; 12 // 目的是方便我们在Tank这个类中访问m(炮弹对象)这个成员变量 13 // 其实就是在Tank类中持有TankClient类对象的一个引用 14 15 // 我们这里new我们自己的坦克 16 Tank myTank = new Tank(50, 50, true, Tank.Direction.STOP, this); 17 18 Wall w1=new Wall(100, 200,20, 150,this); 19 Wall w2=new Wall(300, 100,300,20,this); 20 /* 21 * //新建敌方坦克,(不再需要这个了) Tank enemyTank=new Tank(100,100,false,this); 22 */ 23 // 定义爆炸 24 Explode e = new Explode(70, 70, this); 25 // 定义一个集合,多个爆炸点 26 List<Explode> explodes = new ArrayList<Explode>(); 27 28 // 使用容器装炮弹 29 List<Missile> missiles = new ArrayList<Missile>(); 30 31 // 用容器来装敌人的Tank 32 List<Tank> tanks = new ArrayList<Tank>(); 33 // 定义虚拟图片,方便后期的一次性显示 34 Image offScreenImage = null; 35 36 public void paint(Graphics g) { 37 // 记录屏幕上的子弹数目 38 g.drawString("missiles count:" + missiles.size(), 10, 50); 39 // 添加上方标记栏,记录爆炸次数 40 g.drawString("explodes count:" + explodes.size(), 10, 70); 41 // 记录现在屏幕上一共有多少敌方坦克 42 g.drawString("tanks count:" + tanks.size(), 10, 90); 43 44 // 遍历结合,发出多发炮弹 45 for (int i = 0; i < missiles.size(); i++) { 46 Missile m = missiles.get(i); 47 // 对于每一发炮弹,都可以将tanks集合中的敌方炮弹干掉 48 m.hitTanks(tanks); 49 m.hitTank(myTank); 50 m.hitWall(w1); 51 m.hitWall(w2); 52 m.draw(g); 53 } 54 55 for (int i = 0; i < explodes.size(); i++) { 56 Explode e = explodes.get(i); 57 e.draw(g); 58 } 59 60 for (int i = 0; i < tanks.size(); i++) { 61 Tank t = tanks.get(i); 62 t.collidesWithWall(w1); 63 t.collidesWithWall(w2); 64 t.draw(g); 65 } 66 // 不改变前景色 67 myTank.draw(g); 68 w1.draw(g); 69 w2.draw(g); 70 } 71 72 // 刷新操作 73 public void update(Graphics g) { 74 if (offScreenImage == null) { 75 offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT); 76 } 77 Graphics gOffScreen = offScreenImage.getGraphics(); 78 Color c = gOffScreen.getColor(); 79 gOffScreen.setColor(Color.GREEN); 80 gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); 81 gOffScreen.setColor(c); 82 paint(gOffScreen); 83 g.drawImage(offScreenImage, 0, 0, null); 84 } 85 86 public void lauchFrame() { 87 88 // 添加多辆坦克 89 for (int i = 0; i < 10; i++) { 90 tanks.add(new Tank(50 + 40 * (i + 1), 50, false, Tank.Direction.D, 91 this)); 92 } 93 // this.setLocation(400, 300); 94 this.setSize(GAME_WIDTH, GAME_HEIGHT); 95 this.setTitle("TankWar"); 96 this.addWindowListener(new WindowAdapter() { 97 public void windowClosing(WindowEvent e) { 98 System.exit(0); 99 } 100 }); 101 this.setResizable(false); 102 this.setBackground(Color.GREEN); 103 104 this.addKeyListener(new KeyMonitor()); 105 106 setVisible(true); 107 108 new Thread(new PaintThread()).start(); 109 } 110 111 public static void main(String[] args) { 112 TankClient tc = new TankClient(); 113 tc.lauchFrame(); 114 } 115 116 private class PaintThread implements Runnable { 117 118 public void run() { 119 while (true) { 120 repaint(); 121 try { 122 // 为了爆炸效果,改成1000 123 Thread.sleep(50); 124 } catch (InterruptedException e) { 125 e.printStackTrace(); 126 } 127 } 128 } 129 } 130 131 // 创建键盘时间监听 132 private class KeyMonitor extends KeyAdapter { 133 134 // 直接调用myTank自己的方法根据相应的按键信息进行移动 135 public void keyPressed(KeyEvent e) { 136 myTank.KeyPressed(e); 137 // 添加了处理键抬起的事件,可以控制坦克起步以后的状态 138 // 而不是一直按照一个方向走下去 139 } 140 141 public void keyReleased(KeyEvent e) { 142 myTank.keyReleased(e); 143 } 144 145 } 146 }
版本2.1
功能:坦克不能互相穿越
步骤:
当坦克撞到Tank时stay:添加碰撞检测,就添加了一个方法
1 public boolean collidesWithTanks(java.util.List<Tank> tanks) { 2 for (int i = 0; i < tanks.size(); i++) { 3 Tank t = tanks.get(i); 4 if (this != t) { 5 if (this.live && t.isLive() 6 && this.getRect().intersects(t.getRect())) { 7 this.stay(); 8 t.stay(); 9 } 10 } 11 } 12 return bD; 13 14 }
TankClient类中的paint方法中:
1 for (int i = 0; i < tanks.size(); i++) { 2 Tank t = tanks.get(i); 3 t.collidesWithWall(w1); 4 t.collidesWithWall(w2); 5 t.collidesWithTanks(tanks); 6 t.draw(g); 7 }
具体代码实现:
Tank:
1 import java.awt.*; 2 import java.awt.event.*; 3 import java.util.Random; 4 5 public class Tank { 6 // 方便后期更改 7 public static final int XSPEED = 5; 8 public static final int YSPEED = 5; 9 // 将坦克的高度和宽度设置为常量 10 public static final int WIDTH = 30; 11 public static final int HEIGHT = 30; 12 TankClient tc; 13 // 区别是我方坦克还是地方坦克,方便据此进行不同的设置 14 private boolean good; 15 16 public boolean isGood() { 17 return good; 18 } 19 20 public void setGood(boolean good) { 21 this.good = good; 22 } 23 24 // 判断坦克生死的变量 25 private boolean live = true; 26 27 public boolean isLive() { 28 return live; 29 } 30 31 public void setLive(boolean live) { 32 this.live = live; 33 } 34 35 private int x; 36 private int y; 37 38 // 记录坦克上一步的位置,防止坦克一碰到wall,就会依附在上面 39 private int oldx; 40 private int oldy; 41 42 // 随机数产生器,方便敌方坦克可以任意移动 43 private static Random r = new Random(); 44 // 添加记录按键状态的布尔量 45 private boolean bL = false; 46 private boolean bR = false; 47 private boolean bU = false; 48 private boolean bD = false; 49 50 // 添加代表方向的量(使用枚举) 51 enum Direction { 52 L, R, U, D, LU, LD, RU, RD, STOP 53 }; 54 55 private Direction dir = Direction.STOP; 56 57 // 定义炮筒的方向,我们想办法将炮筒的方法调整成和坦克移动方向一致; 58 // 我们这里会用一条直线来表示炮筒:模拟炮筒 59 // 我们要根据炮筒的方向画直线表示炮筒 60 Direction ptDir = Direction.D; 61 62 // 为了让敌方坦克在一定方向运动移动时间再自动变换方向 63 private int step = r.nextInt(12) + 3; 64 65 // 更改构造函数 66 public Tank(int x, int y, boolean good) { 67 this.x = x; 68 this.y = y; 69 this.oldx = x; 70 this.oldy = y; 71 this.good = good; 72 } 73 74 // 这个位置的构造函数也相应进行了更改 75 public Tank(int x, int y, boolean good, Direction dir, TankClient tc) { 76 // 调用那个有两个参数的构造方法 77 this(x, y, good); 78 this.dir = dir; 79 // 在这个位置初始化tc 80 this.tc = tc; 81 } 82 83 // Tank对象的draw方法 84 public void draw(Graphics g) { 85 if (!live) { 86 // 如果死亡的是敌方坦克,在tanks集合中去除该坦克 87 if (!good) { 88 tc.tanks.remove(this); 89 } 90 // 如果是我方坦克,直接返回 91 return; 92 } 93 Color c = g.getColor(); 94 if (good) { 95 g.setColor(Color.RED); 96 } else { 97 g.setColor(Color.PINK); 98 } 99 g.fillOval(x, y, WIDTH, HEIGHT); 100 g.setColor(c); 101 // 根据炮筒的方向画直线来表示我们坦克的炮筒 102 switch (ptDir) { 103 case L: 104 // 画直线:四个参数分别代表:坦克中心点的坐标 直线的另一头的的坐标 105 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 106 + Tank.HEIGHT / 2); 107 break; 108 case R: 109 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 110 y + Tank.HEIGHT / 2); 111 112 break; 113 case U: 114 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 115 / 2, y); 116 117 break; 118 case D: 119 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 120 / 2, y + Tank.HEIGHT); 121 122 break; 123 case LU: 124 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y); 125 break; 126 case LD: 127 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 128 + Tank.HEIGHT); 129 130 break; 131 case RU: 132 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 133 y); 134 135 break; 136 case RD: 137 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 138 y + Tank.HEIGHT); 139 140 break; 141 /* 142 * case STOP: break; 143 */ 144 } 145 move(); 146 } 147 148 public void move() { 149 // 记录坦克上一步的位置 150 this.oldx = x; 151 this.oldy = y; 152 switch (dir) { 153 case L: 154 x -= XSPEED; 155 break; 156 case R: 157 x += XSPEED; 158 break; 159 case U: 160 y -= YSPEED; 161 break; 162 case D: 163 y += YSPEED; 164 break; 165 case LU: 166 x -= XSPEED; 167 y -= YSPEED; 168 break; 169 case LD: 170 x -= XSPEED; 171 y += YSPEED; 172 break; 173 case RU: 174 x += XSPEED; 175 y -= YSPEED; 176 break; 177 case RD: 178 x += XSPEED; 179 y += YSPEED; 180 break; 181 182 case STOP: 183 break; 184 } 185 // 如果坦克不是停着的,则将炮筒调整至和坦克移动的方向相同 186 if (this.dir != Direction.STOP) { 187 this.ptDir = this.dir; 188 } 189 if (x < 0) { 190 x = 0; 191 } 192 // 因为我们的游戏界面有那个missileCount标签,所以在y轴方向不能用y是否<0进行判断 193 // 否则的话我们的坦克可以从上面出去 194 if (y < 50) { 195 y = 50; 196 } 197 if (x + Tank.WIDTH > TankClient.GAME_WIDTH) { 198 x = TankClient.GAME_WIDTH - Tank.WIDTH; 199 } 200 if (y + Tank.HEIGHT > TankClient.GAME_HEIGHT) { 201 y = TankClient.GAME_HEIGHT - Tank.HEIGHT; 202 } 203 // 在move方法中判断如果是敌方坦克 204 if (!good) { 205 Direction[] dirs = Direction.values(); 206 // 定义敌方坦克的移动 207 if (step == 0) { 208 step = r.nextInt(12) + 3; 209 int rn = r.nextInt(dirs.length); 210 dir = dirs[rn]; 211 } 212 // 使得敌方坦克每隔一定时间就变化方向,values:方向枚举转化为数组 213 214 step--; 215 // 用随机数,使得敌方坦克可以发炮弹,但是不要太猛烈 216 if (r.nextInt(40) > 38) { 217 this.fire(); 218 } 219 } 220 } 221 222 public void locateDirection() { 223 if (bL && !bU && !bR && !bD) 224 dir = Direction.L; 225 else if (bL && bU && !bR && !bD) 226 dir = Direction.LU; 227 else if (!bL && bU && !bR && !bD) 228 dir = Direction.U; 229 else if (!bL && bU && bR && !bD) 230 dir = Direction.RU; 231 else if (!bL && !bU && bR && !bD) 232 dir = Direction.R; 233 else if (!bL && !bU && bR && bD) 234 dir = Direction.RD; 235 else if (!bL && !bU && !bR && bD) 236 dir = Direction.D; 237 else if (bL && !bU && !bR && bD) 238 dir = Direction.LD; 239 else if (!bL && !bU && !bR && !bD) 240 dir = Direction.STOP; 241 242 } 243 244 private void stay() { 245 x = oldx; 246 y = oldy; 247 } 248 249 // 坦克自己向哪个方向移动,它自己最清楚; 250 public void KeyPressed(KeyEvent e) { 251 // 获得所按下的键所对应的虚拟码: 252 // Returns the integer keyCode associated with the key in this event 253 int key = e.getKeyCode(); 254 // 判断不同的按键,指挥坦克的运动方向 255 switch (key) { 256 case KeyEvent.VK_LEFT: 257 bL = true; 258 break; 259 case KeyEvent.VK_UP: 260 bU = true; 261 break; 262 case KeyEvent.VK_RIGHT: 263 bR = true; 264 break; 265 case KeyEvent.VK_DOWN: 266 bD = true; 267 break; 268 } 269 locateDirection(); 270 } 271 272 public void keyReleased(KeyEvent e) { 273 int key = e.getKeyCode(); 274 // 判断不同的按键,指挥坦克的运动方向 275 // 哪个键按下了,就把对应方向的布尔类型置为false 276 switch (key) { 277 // 为了防止一直按着Ctrl键的时候,炮弹太过于密集 278 // 因此我们定义在Ctrl键抬起的时候才发炮弹 279 // 这样炮弹不至于太过密集 280 case KeyEvent.VK_CONTROL: 281 fire(); 282 break; 283 case KeyEvent.VK_LEFT: 284 bL = false; 285 break; 286 case KeyEvent.VK_UP: 287 bU = false; 288 break; 289 case KeyEvent.VK_RIGHT: 290 bR = false; 291 break; 292 case KeyEvent.VK_DOWN: 293 bD = false; 294 break; 295 } 296 // 重新定位一下 297 locateDirection(); 298 } 299 300 public Missile fire() { 301 if (!live) { 302 return null; 303 } 304 // 计算子弹的位置,使得子弹从坦克的中间发出来 305 int x = this.x + Tank.WIDTH / 2 - Missile.WIDTH / 2; 306 int y = this.y + Tank.HEIGHT / 2 - Missile.HEIGHT / 2; 307 // 这个时候我们就根据炮筒的方向来发炮弹了,之前是根据坦克的方向来发炮弹 308 Missile m = new Missile(x, y, good, ptDir, tc); 309 // 将新产生的炮弹放置到List容器中 310 tc.missiles.add(m); 311 return m; 312 } 313 314 // 拿到包围坦克的那个方块 315 public Rectangle getRect() { 316 317 return new Rectangle(x, y, WIDTH, HEIGHT); 318 } 319 320 public boolean collidesWithWall(Wall w) { 321 322 if (this.live && this.getRect().intersects(w.getRect())) { 323 this.dir = Direction.STOP; 324 // 当坦克撞到墙上的时候,停一下,再回到上一步的位置 325 this.stay(); 326 return true; 327 } 328 return false; 329 330 } 331 332 // 坦克和坦克之间的碰撞检测 333 public boolean collidesWithTanks(java.util.List<Tank> tanks) { 334 for (int i = 0; i < tanks.size(); i++) { 335 Tank t = tanks.get(i); 336 if (this != t) { 337 if (this.live && t.isLive() 338 && this.getRect().intersects(t.getRect())) { 339 this.stay(); 340 t.stay(); 341 } 342 } 343 } 344 return bD; 345 346 } 347 }
Missile:
1 import java.awt.Color; 2 import java.awt.Graphics; 3 import java.awt.Rectangle; 4 import java.util.List; 5 6 public class Missile { 7 // 炮弹的移动速度,不要比坦克的移动速度慢,不然你看到的是满屏的坦克追着炮弹跑 8 public static final int XSPEED = 10; 9 public static final int YSPEED = 10; 10 // 将子弹的高度和宽度设置为常量 11 public static final int WIDTH = 10; 12 public static final int HEIGHT = 10; 13 // 炮弹自己的三个属性 14 int x; 15 int y; 16 Tank.Direction dir; 17 18 //同一阵营的的坦克发出的子弹不能伤害自己人 19 private boolean good; 20 // 定义一个布尔类型的变量来判断炮弹是否已经消亡 21 private boolean live = true; 22 //我们在Missile类中也持有一个TankClient的引用 23 //在炮弹出界的时候就可以从装炮弹的missiles集合中去除该炮弹,不再对其重画 24 private TankClient tc; 25 26 public boolean isLive() { 27 return live; 28 } 29 30 public Missile(int x, int y, Tank.Direction dir) { 31 this.x = x; 32 this.y = y; 33 this.dir = dir; 34 } 35 public Missile(int x,int y,boolean good,Tank.Direction dir,TankClient tc){ 36 this(x, y, dir); 37 this.good=good; 38 this.tc=tc; 39 } 40 41 // 炮弹自己的draw方法 42 public void draw(Graphics g) { 43 //炮弹消亡就不需要再画出来了 44 if(!live){ 45 tc.missiles.remove(this); 46 return; 47 } 48 Color c = g.getColor(); 49 g.setColor(Color.BLACK); 50 // 炮弹形状不要比坦克大,这里设置成10,10; 51 g.fillOval(x, y, WIDTH, HEIGHT); 52 g.setColor(c); 53 move(); 54 } 55 56 public void move() { 57 switch (dir) { 58 case L: 59 x -= XSPEED; 60 break; 61 case R: 62 x += XSPEED; 63 break; 64 case U: 65 y -= YSPEED; 66 break; 67 case D: 68 y += YSPEED; 69 break; 70 case LU: 71 x -= XSPEED; 72 y -= YSPEED; 73 break; 74 case LD: 75 x -= XSPEED; 76 y += YSPEED; 77 break; 78 case RU: 79 x += XSPEED; 80 y -= YSPEED; 81 break; 82 case RD: 83 x += XSPEED; 84 y += YSPEED; 85 break; 86 // 炮弹就没有STOP这个枚举类型的值了 87 /* 88 * case STOP: break; 89 */ 90 } 91 // 判断炮弹出边界则消亡 92 // 注意x,y只有正数值,x向右递增,y向下递增 93 if (x < 0 || y < 0 || x > TankClient.GAME_WIDTH 94 || y > TankClient.GAME_HEIGHT) { 95 live = false; 96 } 97 } 98 public boolean hitTank(Tank t){ 99 //炮弹的方框和坦克的方框碰在一起了并且坦克是存活着的,后面的判断我们是一伙的我就不打你了 100 if(this.live&&this.getRect().intersects(t.getRect())&&t.isLive()&&this.good!=t.isGood()){ 101 t.setLive(false); 102 this.live=false; 103 104 //炮弹击中坦克,发生爆炸 105 Explode e=new Explode(x, y, tc); 106 tc.explodes.add(e); 107 return true; 108 } 109 return false; 110 } 111 //碰撞检测类Rectangle 112 //拿到包围在炮弹周围的小方块 113 public Rectangle getRect(){ 114 return new Rectangle(x,y,WIDTH,HEIGHT); 115 } 116 117 //添加hitTanks方法 118 public boolean hitTanks(List<Tank> tanks){ 119 for(int i=0;i<tanks.size();i++){ 120 if(hitTank(tanks.get(i))){ 121 return true; 122 } 123 } 124 return false; 125 126 } 127 public boolean hitWall(Wall w){ 128 if(this.live&&this.getRect().intersects(w.getRect())){ 129 this.live=false; 130 return true; 131 } 132 return false; 133 } 134 }
Explode:
1 import java.awt.*; 2 3 public class Explode { 4 //爆炸的位置 5 int x, y; 6 //爆炸是否存在 7 private boolean live = true; 8 9 //持有一个Tankclient的引用 10 private TankClient tc ; 11 12 //定义不同直径大小的爆炸 13 int[] diameter = {4, 7, 12, 18, 26, 32, 49, 30, 14, 6}; 14 //爆炸发生到哪一个阶段了,对应相应大小的直径 15 int step = 0; 16 17 public Explode(int x, int y, TankClient tc) { 18 this.x = x; 19 this.y = y; 20 this.tc = tc; 21 } 22 23 public void draw(Graphics g) { 24 if(!live) { 25 //爆炸发生,将相应直径的爆炸圆从集合explodes中去除 26 tc.explodes.remove(this); 27 return; 28 } 29 30 if(step == diameter.length) { 31 live = false; 32 step = 0; 33 return; 34 } 35 36 Color c = g.getColor(); 37 g.setColor(Color.ORANGE); 38 39 //把不同的圆画出来 40 g.fillOval(x, y, diameter[step], diameter[step]); 41 g.setColor(c); 42 43 step ++; 44 } 45 }
Wall:
1 import java.awt.Graphics; 2 import java.awt.Rectangle; 3 4 //墙 5 public class Wall { 6 int x,y,w,h; 7 TankClient tc; 8 public Wall(int x, int y, int w, int h, TankClient tc) { 9 super(); 10 this.x = x; 11 this.y = y; 12 this.w = w; 13 this.h = h; 14 this.tc = tc; 15 } 16 public void draw(Graphics g){ 17 g.fillRect(x, y, w, h); 18 } 19 //碰撞检测 20 public Rectangle getRect(){ 21 return new Rectangle(x,y,w,h); 22 } 23 }
TankClient:
1 import java.awt.*; 2 import java.awt.event.*; 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class TankClient extends Frame { 7 // 设置成常量,方便以后的改动 8 public static final int GAME_WIDTH = 800; 9 public static final int GAME_HEIGHT = 600; 10 11 // 将当前的TankClient对象传递给myTank; 12 // 目的是方便我们在Tank这个类中访问m(炮弹对象)这个成员变量 13 // 其实就是在Tank类中持有TankClient类对象的一个引用 14 15 // 我们这里new我们自己的坦克 16 Tank myTank = new Tank(50, 50, true, Tank.Direction.STOP, this); 17 18 Wall w1=new Wall(100, 200,20, 150,this); 19 Wall w2=new Wall(300, 100,300,20,this); 20 /* 21 * //新建敌方坦克,(不再需要这个了) Tank enemyTank=new Tank(100,100,false,this); 22 */ 23 // 定义爆炸 24 Explode e = new Explode(70, 70, this); 25 // 定义一个集合,多个爆炸点 26 List<Explode> explodes = new ArrayList<Explode>(); 27 28 // 使用容器装炮弹 29 List<Missile> missiles = new ArrayList<Missile>(); 30 31 // 用容器来装敌人的Tank 32 List<Tank> tanks = new ArrayList<Tank>(); 33 // 定义虚拟图片,方便后期的一次性显示 34 Image offScreenImage = null; 35 36 public void paint(Graphics g) { 37 // 记录屏幕上的子弹数目 38 g.drawString("missiles count:" + missiles.size(), 10, 50); 39 // 添加上方标记栏,记录爆炸次数 40 g.drawString("explodes count:" + explodes.size(), 10, 70); 41 // 记录现在屏幕上一共有多少敌方坦克 42 g.drawString("tanks count:" + tanks.size(), 10, 90); 43 44 // 遍历结合,发出多发炮弹 45 for (int i = 0; i < missiles.size(); i++) { 46 Missile m = missiles.get(i); 47 // 对于每一发炮弹,都可以将tanks集合中的敌方炮弹干掉 48 m.hitTanks(tanks); 49 m.hitTank(myTank); 50 m.hitWall(w1); 51 m.hitWall(w2); 52 m.draw(g); 53 } 54 55 for (int i = 0; i < explodes.size(); i++) { 56 Explode e = explodes.get(i); 57 e.draw(g); 58 } 59 60 for (int i = 0; i < tanks.size(); i++) { 61 Tank t = tanks.get(i); 62 t.collidesWithWall(w1); 63 t.collidesWithWall(w2); 64 t.collidesWithTanks(tanks); 65 t.draw(g); 66 } 67 // 不改变前景色 68 myTank.draw(g); 69 w1.draw(g); 70 w2.draw(g); 71 } 72 73 // 刷新操作 74 public void update(Graphics g) { 75 if (offScreenImage == null) { 76 offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT); 77 } 78 Graphics gOffScreen = offScreenImage.getGraphics(); 79 Color c = gOffScreen.getColor(); 80 gOffScreen.setColor(Color.GREEN); 81 gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); 82 gOffScreen.setColor(c); 83 paint(gOffScreen); 84 g.drawImage(offScreenImage, 0, 0, null); 85 } 86 87 public void lauchFrame() { 88 89 // 添加多辆坦克 90 for (int i = 0; i < 10; i++) { 91 tanks.add(new Tank(50 + 40 * (i + 1), 50, false, Tank.Direction.D, 92 this)); 93 } 94 // this.setLocation(400, 300); 95 this.setSize(GAME_WIDTH, GAME_HEIGHT); 96 this.setTitle("TankWar"); 97 this.addWindowListener(new WindowAdapter() { 98 public void windowClosing(WindowEvent e) { 99 System.exit(0); 100 } 101 }); 102 this.setResizable(false); 103 this.setBackground(Color.GREEN); 104 105 this.addKeyListener(new KeyMonitor()); 106 107 setVisible(true); 108 109 new Thread(new PaintThread()).start(); 110 } 111 112 public static void main(String[] args) { 113 TankClient tc = new TankClient(); 114 tc.lauchFrame(); 115 } 116 117 private class PaintThread implements Runnable { 118 119 public void run() { 120 while (true) { 121 repaint(); 122 try { 123 // 为了爆炸效果,改成1000 124 Thread.sleep(50); 125 } catch (InterruptedException e) { 126 e.printStackTrace(); 127 } 128 } 129 } 130 } 131 132 // 创建键盘时间监听 133 private class KeyMonitor extends KeyAdapter { 134 135 // 直接调用myTank自己的方法根据相应的按键信息进行移动 136 public void keyPressed(KeyEvent e) { 137 myTank.KeyPressed(e); 138 // 添加了处理键抬起的事件,可以控制坦克起步以后的状态 139 // 而不是一直按照一个方向走下去 140 } 141 142 public void keyReleased(KeyEvent e) { 143 myTank.keyReleased(e); 144 } 145 146 } 147 }
版本2.2
功能:超级炮弹
步骤:
处理按键A
具体代码实现:只有Tank类发生了变化
Tank类 :
1 import java.awt.*; 2 import java.awt.event.*; 3 import java.util.Random; 4 5 public class Tank { 6 // 方便后期更改 7 public static final int XSPEED = 5; 8 public static final int YSPEED = 5; 9 // 将坦克的高度和宽度设置为常量 10 public static final int WIDTH = 30; 11 public static final int HEIGHT = 30; 12 TankClient tc; 13 // 区别是我方坦克还是地方坦克,方便据此进行不同的设置 14 private boolean good; 15 16 public boolean isGood() { 17 return good; 18 } 19 20 public void setGood(boolean good) { 21 this.good = good; 22 } 23 24 // 判断坦克生死的变量 25 private boolean live = true; 26 27 public boolean isLive() { 28 return live; 29 } 30 31 public void setLive(boolean live) { 32 this.live = live; 33 } 34 35 private int x; 36 private int y; 37 38 // 记录坦克上一步的位置,防止坦克一碰到wall,就会依附在上面 39 private int oldx; 40 private int oldy; 41 42 // 随机数产生器,方便敌方坦克可以任意移动 43 private static Random r = new Random(); 44 // 添加记录按键状态的布尔量 45 private boolean bL = false; 46 private boolean bR = false; 47 private boolean bU = false; 48 private boolean bD = false; 49 50 // 添加代表方向的量(使用枚举) 51 enum Direction { 52 L, R, U, D, LU, LD, RU, RD, STOP 53 }; 54 55 private Direction dir = Direction.STOP; 56 57 // 定义炮筒的方向,我们想办法将炮筒的方法调整成和坦克移动方向一致; 58 // 我们这里会用一条直线来表示炮筒:模拟炮筒 59 // 我们要根据炮筒的方向画直线表示炮筒 60 Direction ptDir = Direction.D; 61 62 // 为了让敌方坦克在一定方向运动移动时间再自动变换方向 63 private int step = r.nextInt(12) + 3; 64 65 // 更改构造函数 66 public Tank(int x, int y, boolean good) { 67 this.x = x; 68 this.y = y; 69 this.oldx = x; 70 this.oldy = y; 71 this.good = good; 72 } 73 74 // 这个位置的构造函数也相应进行了更改 75 public Tank(int x, int y, boolean good, Direction dir, TankClient tc) { 76 // 调用那个有两个参数的构造方法 77 this(x, y, good); 78 this.dir = dir; 79 // 在这个位置初始化tc 80 this.tc = tc; 81 } 82 83 // Tank对象的draw方法 84 public void draw(Graphics g) { 85 if (!live) { 86 // 如果死亡的是敌方坦克,在tanks集合中去除该坦克 87 if (!good) { 88 tc.tanks.remove(this); 89 } 90 // 如果是我方坦克,直接返回 91 return; 92 } 93 Color c = g.getColor(); 94 if (good) { 95 g.setColor(Color.RED); 96 } else { 97 g.setColor(Color.PINK); 98 } 99 g.fillOval(x, y, WIDTH, HEIGHT); 100 g.setColor(c); 101 // 根据炮筒的方向画直线来表示我们坦克的炮筒 102 switch (ptDir) { 103 case L: 104 // 画直线:四个参数分别代表:坦克中心点的坐标 直线的另一头的的坐标 105 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 106 + Tank.HEIGHT / 2); 107 break; 108 case R: 109 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 110 y + Tank.HEIGHT / 2); 111 112 break; 113 case U: 114 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 115 / 2, y); 116 117 break; 118 case D: 119 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 120 / 2, y + Tank.HEIGHT); 121 122 break; 123 case LU: 124 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y); 125 break; 126 case LD: 127 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 128 + Tank.HEIGHT); 129 130 break; 131 case RU: 132 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 133 y); 134 135 break; 136 case RD: 137 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 138 y + Tank.HEIGHT); 139 140 break; 141 /* 142 * case STOP: break; 143 */ 144 } 145 move(); 146 } 147 148 public void move() { 149 // 记录坦克上一步的位置 150 this.oldx = x; 151 this.oldy = y; 152 switch (dir) { 153 case L: 154 x -= XSPEED; 155 break; 156 case R: 157 x += XSPEED; 158 break; 159 case U: 160 y -= YSPEED; 161 break; 162 case D: 163 y += YSPEED; 164 break; 165 case LU: 166 x -= XSPEED; 167 y -= YSPEED; 168 break; 169 case LD: 170 x -= XSPEED; 171 y += YSPEED; 172 break; 173 case RU: 174 x += XSPEED; 175 y -= YSPEED; 176 break; 177 case RD: 178 x += XSPEED; 179 y += YSPEED; 180 break; 181 182 case STOP: 183 break; 184 } 185 // 如果坦克不是停着的,则将炮筒调整至和坦克移动的方向相同 186 if (this.dir != Direction.STOP) { 187 this.ptDir = this.dir; 188 } 189 if (x < 0) { 190 x = 0; 191 } 192 // 因为我们的游戏界面有那个missileCount标签,所以在y轴方向不能用y是否<0进行判断 193 // 否则的话我们的坦克可以从上面出去 194 if (y < 50) { 195 y = 50; 196 } 197 if (x + Tank.WIDTH > TankClient.GAME_WIDTH) { 198 x = TankClient.GAME_WIDTH - Tank.WIDTH; 199 } 200 if (y + Tank.HEIGHT > TankClient.GAME_HEIGHT) { 201 y = TankClient.GAME_HEIGHT - Tank.HEIGHT; 202 } 203 // 在move方法中判断如果是敌方坦克 204 if (!good) { 205 Direction[] dirs = Direction.values(); 206 // 定义敌方坦克的移动 207 if (step == 0) { 208 step = r.nextInt(12) + 3; 209 int rn = r.nextInt(dirs.length); 210 dir = dirs[rn]; 211 } 212 // 使得敌方坦克每隔一定时间就变化方向,values:方向枚举转化为数组 213 214 step--; 215 // 用随机数,使得敌方坦克可以发炮弹,但是不要太猛烈 216 if (r.nextInt(40) > 38) { 217 this.fire(); 218 } 219 } 220 } 221 222 public void locateDirection() { 223 if (bL && !bU && !bR && !bD) 224 dir = Direction.L; 225 else if (bL && bU && !bR && !bD) 226 dir = Direction.LU; 227 else if (!bL && bU && !bR && !bD) 228 dir = Direction.U; 229 else if (!bL && bU && bR && !bD) 230 dir = Direction.RU; 231 else if (!bL && !bU && bR && !bD) 232 dir = Direction.R; 233 else if (!bL && !bU && bR && bD) 234 dir = Direction.RD; 235 else if (!bL && !bU && !bR && bD) 236 dir = Direction.D; 237 else if (bL && !bU && !bR && bD) 238 dir = Direction.LD; 239 else if (!bL && !bU && !bR && !bD) 240 dir = Direction.STOP; 241 242 } 243 244 private void stay() { 245 x = oldx; 246 y = oldy; 247 } 248 249 // 坦克自己向哪个方向移动,它自己最清楚; 250 public void KeyPressed(KeyEvent e) { 251 // 获得所按下的键所对应的虚拟码: 252 // Returns the integer keyCode associated with the key in this event 253 int key = e.getKeyCode(); 254 // 判断不同的按键,指挥坦克的运动方向 255 switch (key) { 256 case KeyEvent.VK_LEFT: 257 bL = true; 258 break; 259 case KeyEvent.VK_UP: 260 bU = true; 261 break; 262 case KeyEvent.VK_RIGHT: 263 bR = true; 264 break; 265 case KeyEvent.VK_DOWN: 266 bD = true; 267 break; 268 } 269 locateDirection(); 270 } 271 272 public void keyReleased(KeyEvent e) { 273 int key = e.getKeyCode(); 274 // 判断不同的按键,指挥坦克的运动方向 275 // 哪个键按下了,就把对应方向的布尔类型置为false 276 switch (key) { 277 // 为了防止一直按着Ctrl键的时候,炮弹太过于密集 278 // 因此我们定义在Ctrl键抬起的时候才发炮弹 279 // 这样炮弹不至于太过密集 280 case KeyEvent.VK_CONTROL: 281 fire(); 282 break; 283 case KeyEvent.VK_LEFT: 284 bL = false; 285 break; 286 case KeyEvent.VK_UP: 287 bU = false; 288 break; 289 case KeyEvent.VK_RIGHT: 290 bR = false; 291 break; 292 case KeyEvent.VK_DOWN: 293 bD = false; 294 break; 295 296 //当按键A被按下时,会发出超级炮弹superFire() 297 case KeyEvent.VK_A: 298 superFire(); 299 break; 300 } 301 // 重新定位一下 302 locateDirection(); 303 } 304 305 public Missile fire() { 306 if (!live) { 307 return null; 308 } 309 // 计算子弹的位置,使得子弹从坦克的中间发出来 310 int x = this.x + Tank.WIDTH / 2 - Missile.WIDTH / 2; 311 int y = this.y + Tank.HEIGHT / 2 - Missile.HEIGHT / 2; 312 // 这个时候我们就根据炮筒的方向来发炮弹了,之前是根据坦克的方向来发炮弹 313 Missile m = new Missile(x, y, good, ptDir, tc); 314 // 将新产生的炮弹放置到List容器中 315 tc.missiles.add(m); 316 return m; 317 } 318 319 public Missile fire(Direction dir){ 320 if (!live) { 321 return null; 322 } 323 // 计算子弹的位置,使得子弹从坦克的中间发出来 324 int x = this.x + Tank.WIDTH / 2 - Missile.WIDTH / 2; 325 int y = this.y + Tank.HEIGHT / 2 - Missile.HEIGHT / 2; 326 // 这个时候我们就根据炮筒的方向来发炮弹了,之前是根据坦克的方向来发炮弹 327 Missile m = new Missile(x, y, good, ptDir, tc); 328 // 将新产生的炮弹放置到List容器中 329 tc.missiles.add(m); 330 return m; 331 } 332 333 // 拿到包围坦克的那个方块 334 public Rectangle getRect() { 335 336 return new Rectangle(x, y, WIDTH, HEIGHT); 337 } 338 339 public boolean collidesWithWall(Wall w) { 340 341 if (this.live && this.getRect().intersects(w.getRect())) { 342 this.dir = Direction.STOP; 343 // 当坦克撞到墙上的时候,停一下,再回到上一步的位置 344 this.stay(); 345 return true; 346 } 347 return false; 348 349 } 350 351 // 坦克和坦克之间的碰撞检测 352 public boolean collidesWithTanks(java.util.List<Tank> tanks) { 353 for (int i = 0; i < tanks.size(); i++) { 354 Tank t = tanks.get(i); 355 if (this != t) { 356 if (this.live && t.isLive() 357 && this.getRect().intersects(t.getRect())) { 358 this.stay(); 359 t.stay(); 360 } 361 } 362 } 363 return bD; 364 } 365 366 //超级炮弹 367 private void superFire(){ 368 Direction[] dirs=Direction.values(); 369 for(int i=0;i<8;i++){ 370 //朝八个方向各打一发 371 fire(dirs[i]); 372 } 373 } 374 }
版本2.3
功能:主战坦克的生命值(我们的坦克不能一炮就被打死吧,敌方坦克我们可以一炮打死),代码的变化相较于上一个版本Wall类和Explode类没有变化
步骤:
1)加入life变量:在Tank类中private int life=100;并添加get和set方法
2)在窗口显示生命值:TankClient的paint方法中:g.drawString("tanks life:" + myTank.getLife(), 10, 110);
Tank:
1 import java.awt.*; 2 import java.awt.event.*; 3 import java.util.Random; 4 5 public class Tank { 6 // 方便后期更改 7 public static final int XSPEED = 5; 8 public static final int YSPEED = 5; 9 // 将坦克的高度和宽度设置为常量 10 public static final int WIDTH = 30; 11 public static final int HEIGHT = 30; 12 TankClient tc; 13 // 区别是我方坦克还是地方坦克,方便据此进行不同的设置 14 private boolean good; 15 16 //坦克的生命值 17 private int life=100; 18 19 public int getLife() { 20 return life; 21 } 22 23 public void setLife(int life) { 24 this.life = life; 25 } 26 27 public boolean isGood() { 28 return good; 29 } 30 31 public void setGood(boolean good) { 32 this.good = good; 33 } 34 35 // 判断坦克生死的变量 36 private boolean live = true; 37 38 public boolean isLive() { 39 return live; 40 } 41 42 public void setLive(boolean live) { 43 this.live = live; 44 } 45 46 private int x; 47 private int y; 48 49 // 记录坦克上一步的位置,防止坦克一碰到wall,就会依附在上面 50 private int oldx; 51 private int oldy; 52 53 // 随机数产生器,方便敌方坦克可以任意移动 54 private static Random r = new Random(); 55 // 添加记录按键状态的布尔量 56 private boolean bL = false; 57 private boolean bR = false; 58 private boolean bU = false; 59 private boolean bD = false; 60 61 // 添加代表方向的量(使用枚举) 62 enum Direction { 63 L, R, U, D, LU, LD, RU, RD, STOP 64 }; 65 66 private Direction dir = Direction.STOP; 67 68 // 定义炮筒的方向,我们想办法将炮筒的方法调整成和坦克移动方向一致; 69 // 我们这里会用一条直线来表示炮筒:模拟炮筒 70 // 我们要根据炮筒的方向画直线表示炮筒 71 Direction ptDir = Direction.D; 72 73 // 为了让敌方坦克在一定方向运动移动时间再自动变换方向 74 private int step = r.nextInt(12) + 3; 75 76 // 更改构造函数 77 public Tank(int x, int y, boolean good) { 78 this.x = x; 79 this.y = y; 80 this.oldx = x; 81 this.oldy = y; 82 this.good = good; 83 } 84 85 // 这个位置的构造函数也相应进行了更改 86 public Tank(int x, int y, boolean good, Direction dir, TankClient tc) { 87 // 调用那个有两个参数的构造方法 88 this(x, y, good); 89 this.dir = dir; 90 // 在这个位置初始化tc 91 this.tc = tc; 92 } 93 94 // Tank对象的draw方法 95 public void draw(Graphics g) { 96 if (!live) { 97 // 如果死亡的是敌方坦克,在tanks集合中去除该坦克 98 if (!good) { 99 tc.tanks.remove(this); 100 } 101 // 如果是我方坦克,直接返回 102 return; 103 } 104 Color c = g.getColor(); 105 if (good) { 106 g.setColor(Color.RED); 107 } else { 108 g.setColor(Color.PINK); 109 } 110 g.fillOval(x, y, WIDTH, HEIGHT); 111 g.setColor(c); 112 // 根据炮筒的方向画直线来表示我们坦克的炮筒 113 switch (ptDir) { 114 case L: 115 // 画直线:四个参数分别代表:坦克中心点的坐标 直线的另一头的的坐标 116 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 117 + Tank.HEIGHT / 2); 118 break; 119 case R: 120 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 121 y + Tank.HEIGHT / 2); 122 123 break; 124 case U: 125 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 126 / 2, y); 127 128 break; 129 case D: 130 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 131 / 2, y + Tank.HEIGHT); 132 133 break; 134 case LU: 135 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y); 136 break; 137 case LD: 138 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 139 + Tank.HEIGHT); 140 141 break; 142 case RU: 143 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 144 y); 145 146 break; 147 case RD: 148 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 149 y + Tank.HEIGHT); 150 151 break; 152 /* 153 * case STOP: break; 154 */ 155 } 156 move(); 157 } 158 159 public void move() { 160 // 记录坦克上一步的位置 161 this.oldx = x; 162 this.oldy = y; 163 switch (dir) { 164 case L: 165 x -= XSPEED; 166 break; 167 case R: 168 x += XSPEED; 169 break; 170 case U: 171 y -= YSPEED; 172 break; 173 case D: 174 y += YSPEED; 175 break; 176 case LU: 177 x -= XSPEED; 178 y -= YSPEED; 179 break; 180 case LD: 181 x -= XSPEED; 182 y += YSPEED; 183 break; 184 case RU: 185 x += XSPEED; 186 y -= YSPEED; 187 break; 188 case RD: 189 x += XSPEED; 190 y += YSPEED; 191 break; 192 193 case STOP: 194 break; 195 } 196 // 如果坦克不是停着的,则将炮筒调整至和坦克移动的方向相同 197 if (this.dir != Direction.STOP) { 198 this.ptDir = this.dir; 199 } 200 if (x < 0) { 201 x = 0; 202 } 203 // 因为我们的游戏界面有那个missileCount标签,所以在y轴方向不能用y是否<0进行判断 204 // 否则的话我们的坦克可以从上面出去 205 if (y < 50) { 206 y = 50; 207 } 208 if (x + Tank.WIDTH > TankClient.GAME_WIDTH) { 209 x = TankClient.GAME_WIDTH - Tank.WIDTH; 210 } 211 if (y + Tank.HEIGHT > TankClient.GAME_HEIGHT) { 212 y = TankClient.GAME_HEIGHT - Tank.HEIGHT; 213 } 214 // 在move方法中判断如果是敌方坦克 215 if (!good) { 216 Direction[] dirs = Direction.values(); 217 // 定义敌方坦克的移动 218 if (step == 0) { 219 step = r.nextInt(12) + 3; 220 int rn = r.nextInt(dirs.length); 221 dir = dirs[rn]; 222 } 223 // 使得敌方坦克每隔一定时间就变化方向,values:方向枚举转化为数组 224 225 step--; 226 // 用随机数,使得敌方坦克可以发炮弹,但是不要太猛烈 227 if (r.nextInt(40) > 38) { 228 this.fire(); 229 } 230 } 231 } 232 233 public void locateDirection() { 234 if (bL && !bU && !bR && !bD) 235 dir = Direction.L; 236 else if (bL && bU && !bR && !bD) 237 dir = Direction.LU; 238 else if (!bL && bU && !bR && !bD) 239 dir = Direction.U; 240 else if (!bL && bU && bR && !bD) 241 dir = Direction.RU; 242 else if (!bL && !bU && bR && !bD) 243 dir = Direction.R; 244 else if (!bL && !bU && bR && bD) 245 dir = Direction.RD; 246 else if (!bL && !bU && !bR && bD) 247 dir = Direction.D; 248 else if (bL && !bU && !bR && bD) 249 dir = Direction.LD; 250 else if (!bL && !bU && !bR && !bD) 251 dir = Direction.STOP; 252 253 } 254 255 private void stay() { 256 x = oldx; 257 y = oldy; 258 } 259 260 // 坦克自己向哪个方向移动,它自己最清楚; 261 public void KeyPressed(KeyEvent e) { 262 // 获得所按下的键所对应的虚拟码: 263 // Returns the integer keyCode associated with the key in this event 264 int key = e.getKeyCode(); 265 // 判断不同的按键,指挥坦克的运动方向 266 switch (key) { 267 case KeyEvent.VK_LEFT: 268 bL = true; 269 break; 270 case KeyEvent.VK_UP: 271 bU = true; 272 break; 273 case KeyEvent.VK_RIGHT: 274 bR = true; 275 break; 276 case KeyEvent.VK_DOWN: 277 bD = true; 278 break; 279 } 280 locateDirection(); 281 } 282 283 public void keyReleased(KeyEvent e) { 284 int key = e.getKeyCode(); 285 // 判断不同的按键,指挥坦克的运动方向 286 // 哪个键按下了,就把对应方向的布尔类型置为false 287 switch (key) { 288 // 为了防止一直按着Ctrl键的时候,炮弹太过于密集 289 // 因此我们定义在Ctrl键抬起的时候才发炮弹 290 // 这样炮弹不至于太过密集 291 case KeyEvent.VK_CONTROL: 292 fire(); 293 break; 294 case KeyEvent.VK_LEFT: 295 bL = false; 296 break; 297 case KeyEvent.VK_UP: 298 bU = false; 299 break; 300 case KeyEvent.VK_RIGHT: 301 bR = false; 302 break; 303 case KeyEvent.VK_DOWN: 304 bD = false; 305 break; 306 307 //当按键A被按下时,会发出超级炮弹superFire() 308 case KeyEvent.VK_A: 309 superFire(); 310 break; 311 } 312 // 重新定位一下 313 locateDirection(); 314 } 315 316 public Missile fire() { 317 if (!live) { 318 return null; 319 } 320 // 计算子弹的位置,使得子弹从坦克的中间发出来 321 int x = this.x + Tank.WIDTH / 2 - Missile.WIDTH / 2; 322 int y = this.y + Tank.HEIGHT / 2 - Missile.HEIGHT / 2; 323 // 这个时候我们就根据炮筒的方向来发炮弹了,之前是根据坦克的方向来发炮弹 324 Missile m = new Missile(x, y, good, ptDir, tc); 325 // 将新产生的炮弹放置到List容器中 326 tc.missiles.add(m); 327 return m; 328 } 329 330 public Missile fire(Direction dir){ 331 if (!live) { 332 return null; 333 } 334 // 计算子弹的位置,使得子弹从坦克的中间发出来 335 int x = this.x + Tank.WIDTH / 2 - Missile.WIDTH / 2; 336 int y = this.y + Tank.HEIGHT / 2 - Missile.HEIGHT / 2; 337 // 这个时候我们就根据炮筒的方向来发炮弹了,之前是根据坦克的方向来发炮弹 338 Missile m = new Missile(x, y, good, ptDir, tc); 339 // 将新产生的炮弹放置到List容器中 340 tc.missiles.add(m); 341 return m; 342 } 343 344 // 拿到包围坦克的那个方块 345 public Rectangle getRect() { 346 347 return new Rectangle(x, y, WIDTH, HEIGHT); 348 } 349 350 public boolean collidesWithWall(Wall w) { 351 352 if (this.live && this.getRect().intersects(w.getRect())) { 353 this.dir = Direction.STOP; 354 // 当坦克撞到墙上的时候,停一下,再回到上一步的位置 355 this.stay(); 356 return true; 357 } 358 return false; 359 360 } 361 362 // 坦克和坦克之间的碰撞检测 363 public boolean collidesWithTanks(java.util.List<Tank> tanks) { 364 for (int i = 0; i < tanks.size(); i++) { 365 Tank t = tanks.get(i); 366 if (this != t) { 367 if (this.live && t.isLive() 368 && this.getRect().intersects(t.getRect())) { 369 this.stay(); 370 t.stay(); 371 } 372 } 373 } 374 return bD; 375 } 376 377 //超级炮弹 378 private void superFire(){ 379 Direction[] dirs=Direction.values(); 380 for(int i=0;i<8;i++){ 381 //朝八个方向各打一发 382 fire(dirs[i]); 383 } 384 } 385 }
Missile:
1 import java.awt.Color; 2 import java.awt.Graphics; 3 import java.awt.Rectangle; 4 import java.util.List; 5 6 public class Missile { 7 // 炮弹的移动速度,不要比坦克的移动速度慢,不然你看到的是满屏的坦克追着炮弹跑 8 public static final int XSPEED = 10; 9 public static final int YSPEED = 10; 10 // 将子弹的高度和宽度设置为常量 11 public static final int WIDTH = 10; 12 public static final int HEIGHT = 10; 13 // 炮弹自己的三个属性 14 int x; 15 int y; 16 Tank.Direction dir; 17 18 //同一阵营的的坦克发出的子弹不能伤害自己人 19 private boolean good; 20 // 定义一个布尔类型的变量来判断炮弹是否已经消亡 21 private boolean live = true; 22 //我们在Missile类中也持有一个TankClient的引用 23 //在炮弹出界的时候就可以从装炮弹的missiles集合中去除该炮弹,不再对其重画 24 private TankClient tc; 25 26 public boolean isLive() { 27 return live; 28 } 29 30 public Missile(int x, int y, Tank.Direction dir) { 31 this.x = x; 32 this.y = y; 33 this.dir = dir; 34 } 35 public Missile(int x,int y,boolean good,Tank.Direction dir,TankClient tc){ 36 this(x, y, dir); 37 this.good=good; 38 this.tc=tc; 39 } 40 41 // 炮弹自己的draw方法 42 public void draw(Graphics g) { 43 //炮弹消亡就不需要再画出来了 44 if(!live){ 45 tc.missiles.remove(this); 46 return; 47 } 48 Color c = g.getColor(); 49 g.setColor(Color.BLACK); 50 // 炮弹形状不要比坦克大,这里设置成10,10; 51 g.fillOval(x, y, WIDTH, HEIGHT); 52 g.setColor(c); 53 move(); 54 } 55 56 public void move() { 57 switch (dir) { 58 case L: 59 x -= XSPEED; 60 break; 61 case R: 62 x += XSPEED; 63 break; 64 case U: 65 y -= YSPEED; 66 break; 67 case D: 68 y += YSPEED; 69 break; 70 case LU: 71 x -= XSPEED; 72 y -= YSPEED; 73 break; 74 case LD: 75 x -= XSPEED; 76 y += YSPEED; 77 break; 78 case RU: 79 x += XSPEED; 80 y -= YSPEED; 81 break; 82 case RD: 83 x += XSPEED; 84 y += YSPEED; 85 break; 86 // 炮弹就没有STOP这个枚举类型的值了 87 /* 88 * case STOP: break; 89 */ 90 } 91 // 判断炮弹出边界则消亡 92 // 注意x,y只有正数值,x向右递增,y向下递增 93 if (x < 0 || y < 0 || x > TankClient.GAME_WIDTH 94 || y > TankClient.GAME_HEIGHT) { 95 live = false; 96 } 97 } 98 public boolean hitTank(Tank t){ 99 //炮弹的方框和坦克的方框碰在一起了并且坦克是存活着的,后面的判断我们是一伙的我就不打你了 100 if(this.live&&this.getRect().intersects(t.getRect())&&t.isLive()&&this.good!=t.isGood()){ 101 if(t.isGood()){ 102 t.setLife(t.getLife()-20); 103 if(t.getLife()<=0){ 104 t.setLive(false); 105 } 106 }else { 107 t.setLive(false); 108 } 109 this.live=false; 110 111 //炮弹击中坦克,发生爆炸 112 Explode e=new Explode(x, y, tc); 113 tc.explodes.add(e); 114 return true; 115 } 116 return false; 117 } 118 //碰撞检测类Rectangle 119 //拿到包围在炮弹周围的小方块 120 public Rectangle getRect(){ 121 return new Rectangle(x,y,WIDTH,HEIGHT); 122 } 123 124 //添加hitTanks方法 125 public boolean hitTanks(List<Tank> tanks){ 126 for(int i=0;i<tanks.size();i++){ 127 if(hitTank(tanks.get(i))){ 128 return true; 129 } 130 } 131 return false; 132 133 } 134 public boolean hitWall(Wall w){ 135 if(this.live&&this.getRect().intersects(w.getRect())){ 136 this.live=false; 137 return true; 138 } 139 return false; 140 } 141 }
TankClient:
1 import java.awt.*; 2 import java.awt.event.*; 3 import java.util.ArrayList; 4 import java.util.List; 5 6 public class TankClient extends Frame { 7 // 设置成常量,方便以后的改动 8 public static final int GAME_WIDTH = 800; 9 public static final int GAME_HEIGHT = 600; 10 11 // 将当前的TankClient对象传递给myTank; 12 // 目的是方便我们在Tank这个类中访问m(炮弹对象)这个成员变量 13 // 其实就是在Tank类中持有TankClient类对象的一个引用 14 15 // 我们这里new我们自己的坦克 16 Tank myTank = new Tank(50, 50, true, Tank.Direction.STOP, this); 17 18 Wall w1=new Wall(100, 200,20, 150,this); 19 Wall w2=new Wall(300, 100,300,20,this); 20 /* 21 * //新建敌方坦克,(不再需要这个了) Tank enemyTank=new Tank(100,100,false,this); 22 */ 23 // 定义爆炸 24 Explode e = new Explode(70, 70, this); 25 // 定义一个集合,多个爆炸点 26 List<Explode> explodes = new ArrayList<Explode>(); 27 28 // 使用容器装炮弹 29 List<Missile> missiles = new ArrayList<Missile>(); 30 31 // 用容器来装敌人的Tank 32 List<Tank> tanks = new ArrayList<Tank>(); 33 // 定义虚拟图片,方便后期的一次性显示 34 Image offScreenImage = null; 35 36 public void paint(Graphics g) { 37 // 记录屏幕上的子弹数目 38 g.drawString("missiles count:" + missiles.size(), 10, 50); 39 // 添加上方标记栏,记录爆炸次数 40 g.drawString("explodes count:" + explodes.size(), 10, 70); 41 // 记录现在屏幕上一共有多少敌方坦克 42 g.drawString("tanks count:" + tanks.size(), 10, 90); 43 //我们坦克的生命值 44 g.drawString("tanks life:" + myTank.getLife(), 10, 110); 45 46 // 遍历结合,发出多发炮弹 47 for (int i = 0; i < missiles.size(); i++) { 48 Missile m = missiles.get(i); 49 // 对于每一发炮弹,都可以将tanks集合中的敌方炮弹干掉 50 m.hitTanks(tanks); 51 m.hitTank(myTank); 52 m.hitWall(w1); 53 m.hitWall(w2); 54 m.draw(g); 55 } 56 57 for (int i = 0; i < explodes.size(); i++) { 58 Explode e = explodes.get(i); 59 e.draw(g); 60 } 61 62 for (int i = 0; i < tanks.size(); i++) { 63 Tank t = tanks.get(i); 64 t.collidesWithWall(w1); 65 t.collidesWithWall(w2); 66 t.collidesWithTanks(tanks); 67 t.draw(g); 68 } 69 // 不改变前景色 70 myTank.draw(g); 71 w1.draw(g); 72 w2.draw(g); 73 } 74 75 // 刷新操作 76 public void update(Graphics g) { 77 if (offScreenImage == null) { 78 offScreenImage = this.createImage(GAME_WIDTH, GAME_HEIGHT); 79 } 80 Graphics gOffScreen = offScreenImage.getGraphics(); 81 Color c = gOffScreen.getColor(); 82 gOffScreen.setColor(Color.GREEN); 83 gOffScreen.fillRect(0, 0, GAME_WIDTH, GAME_HEIGHT); 84 gOffScreen.setColor(c); 85 paint(gOffScreen); 86 g.drawImage(offScreenImage, 0, 0, null); 87 } 88 89 public void lauchFrame() { 90 91 // 添加多辆坦克 92 for (int i = 0; i < 10; i++) { 93 tanks.add(new Tank(50 + 40 * (i + 1), 50, false, Tank.Direction.D, 94 this)); 95 } 96 // this.setLocation(400, 300); 97 this.setSize(GAME_WIDTH, GAME_HEIGHT); 98 this.setTitle("TankWar"); 99 this.addWindowListener(new WindowAdapter() { 100 public void windowClosing(WindowEvent e) { 101 System.exit(0); 102 } 103 }); 104 this.setResizable(false); 105 this.setBackground(Color.GREEN); 106 107 this.addKeyListener(new KeyMonitor()); 108 109 setVisible(true); 110 111 new Thread(new PaintThread()).start(); 112 } 113 114 public static void main(String[] args) { 115 TankClient tc = new TankClient(); 116 tc.lauchFrame(); 117 } 118 119 private class PaintThread implements Runnable { 120 121 public void run() { 122 while (true) { 123 repaint(); 124 try { 125 // 为了爆炸效果,改成1000 126 Thread.sleep(50); 127 } catch (InterruptedException e) { 128 e.printStackTrace(); 129 } 130 } 131 } 132 } 133 134 // 创建键盘时间监听 135 private class KeyMonitor extends KeyAdapter { 136 137 // 直接调用myTank自己的方法根据相应的按键信息进行移动 138 public void keyPressed(KeyEvent e) { 139 myTank.KeyPressed(e); 140 // 添加了处理键抬起的事件,可以控制坦克起步以后的状态 141 // 而不是一直按照一个方向走下去 142 } 143 144 public void keyReleased(KeyEvent e) { 145 myTank.keyReleased(e); 146 } 147 148 } 149 }
版本2.4
功能:图形化表示主战坦克的生命值
步骤:
根据不同的life值进行的不同的显示:在Tank类中我们添加一个内部类BloodBar(血条),编写一个函数
1 private class BloodBar{ 2 public void draw(Graphics g){ 3 Color c=g.getColor(); 4 g.setColor(Color.RED); 5 //空心方块 6 g.drawRect(x, y-10, WIDTH, 10); 7 8 //根据我方坦克的生命值来画代表血量的实体快的大小 9 int w=WIDTH*life/100; 10 11 g.fillRect(x, y-10, w,10); 12 g.setColor(c); 13 } 14 }
具体代码实现:只有Tank类中的代码发生了变化
1 import java.awt.*; 2 import java.awt.event.*; 3 import java.util.Random; 4 5 public class Tank { 6 // 方便后期更改 7 public static final int XSPEED = 5; 8 public static final int YSPEED = 5; 9 // 将坦克的高度和宽度设置为常量 10 public static final int WIDTH = 30; 11 public static final int HEIGHT = 30; 12 TankClient tc; 13 // 区别是我方坦克还是地方坦克,方便据此进行不同的设置 14 private boolean good; 15 16 private BloodBar bb=new BloodBar(); 17 //坦克的生命值 18 private int life=100; 19 20 public int getLife() { 21 return life; 22 } 23 24 public void setLife(int life) { 25 this.life = life; 26 } 27 28 public boolean isGood() { 29 return good; 30 } 31 32 public void setGood(boolean good) { 33 this.good = good; 34 } 35 36 // 判断坦克生死的变量 37 private boolean live = true; 38 39 public boolean isLive() { 40 return live; 41 } 42 43 public void setLive(boolean live) { 44 this.live = live; 45 } 46 47 private int x; 48 private int y; 49 50 // 记录坦克上一步的位置,防止坦克一碰到wall,就会依附在上面 51 private int oldx; 52 private int oldy; 53 54 // 随机数产生器,方便敌方坦克可以任意移动 55 private static Random r = new Random(); 56 // 添加记录按键状态的布尔量 57 private boolean bL = false; 58 private boolean bR = false; 59 private boolean bU = false; 60 private boolean bD = false; 61 62 // 添加代表方向的量(使用枚举) 63 enum Direction { 64 L, R, U, D, LU, LD, RU, RD, STOP 65 }; 66 67 private Direction dir = Direction.STOP; 68 69 // 定义炮筒的方向,我们想办法将炮筒的方法调整成和坦克移动方向一致; 70 // 我们这里会用一条直线来表示炮筒:模拟炮筒 71 // 我们要根据炮筒的方向画直线表示炮筒 72 Direction ptDir = Direction.D; 73 74 // 为了让敌方坦克在一定方向运动移动时间再自动变换方向 75 private int step = r.nextInt(12) + 3; 76 77 // 更改构造函数 78 public Tank(int x, int y, boolean good) { 79 this.x = x; 80 this.y = y; 81 this.oldx = x; 82 this.oldy = y; 83 this.good = good; 84 } 85 86 // 这个位置的构造函数也相应进行了更改 87 public Tank(int x, int y, boolean good, Direction dir, TankClient tc) { 88 // 调用那个有两个参数的构造方法 89 this(x, y, good); 90 this.dir = dir; 91 // 在这个位置初始化tc 92 this.tc = tc; 93 } 94 95 // Tank对象的draw方法 96 public void draw(Graphics g) { 97 if (!live) { 98 // 如果死亡的是敌方坦克,在tanks集合中去除该坦克 99 if (!good) { 100 tc.tanks.remove(this); 101 } 102 // 如果是我方坦克,直接返回 103 return; 104 } 105 Color c = g.getColor(); 106 if (good) { 107 g.setColor(Color.YELLOW); 108 } else { 109 g.setColor(Color.PINK); 110 } 111 g.fillOval(x, y, WIDTH, HEIGHT); 112 g.setColor(c); 113 //判断一下,我方坦克才有血条显示 114 if(good){ 115 116 bb.draw(g); 117 } 118 // 根据炮筒的方向画直线来表示我们坦克的炮筒 119 switch (ptDir) { 120 case L: 121 // 画直线:四个参数分别代表:坦克中心点的坐标 直线的另一头的的坐标 122 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 123 + Tank.HEIGHT / 2); 124 break; 125 case R: 126 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 127 y + Tank.HEIGHT / 2); 128 129 break; 130 case U: 131 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 132 / 2, y); 133 134 break; 135 case D: 136 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH 137 / 2, y + Tank.HEIGHT); 138 139 break; 140 case LU: 141 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y); 142 break; 143 case LD: 144 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x, y 145 + Tank.HEIGHT); 146 147 break; 148 case RU: 149 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 150 y); 151 152 break; 153 case RD: 154 g.drawLine(x + Tank.WIDTH / 2, y + Tank.HEIGHT / 2, x + Tank.WIDTH, 155 y + Tank.HEIGHT); 156 157 break; 158 /* 159 * case STOP: break; 160 */ 161 } 162 move(); 163 } 164 165 public void move() { 166 // 记录坦克上一步的位置 167 this.oldx = x; 168 this.oldy = y; 169 switch (dir) { 170 case L: 171 x -= XSPEED; 172 break; 173 case R: 174 x += XSPEED; 175 break; 176 case U: 177 y -= YSPEED; 178 break; 179 case D: 180 y += YSPEED; 181 break; 182 case LU: 183 x -= XSPEED; 184 y -= YSPEED; 185 break; 186 case LD: 187 x -= XSPEED; 188 y += YSPEED; 189 break; 190 case RU: 191 x += XSPEED; 192 y -= YSPEED; 193 break; 194 case RD: 195 x += XSPEED; 196 y += YSPEED; 197 break; 198 199 case STOP: 200 break; 201 } 202 // 如果坦克不是停着的,则将炮筒调整至和坦克移动的方向相同 203 if (this.dir != Direction.STOP) { 204 this.ptDir = this.dir; 205 } 206 if (x < 0) { 207 x = 0; 208 } 209 // 因为我们的游戏界面有那个missileCount标签,所以在y轴方向不能用y是否<0进行判断 210 // 否则的话我们的坦克可以从上面出去 211 if (y < 50) { 212 y = 50; 213 } 214 if (x + Tank.WIDTH > TankClient.GAME_WIDTH) { 215 x = TankClient.GAME_WIDTH - Tank.WIDTH; 216 } 217 if (y + Tank.HEIGHT > TankClient.GAME_HEIGHT) { 218 y = TankClient.GAME_HEIGHT - Tank.HEIGHT; 219 } 220 // 在move方法中判断如果是敌方坦克 221 if (!good) { 222 Direction[] dirs = Direction.values(); 223 // 定义敌方坦克的移动 224 if (step == 0) { 225 step = r.nextInt(12) + 3; 226 int rn = r.nextInt(dirs.length); 227 dir = dirs[rn]; 228 } 229 // 使得敌方坦克每隔一定时间就变化方向,values:方向枚举转化为数组 230 231 step--; 232 // 用随机数,使得敌方坦克可以发炮弹,但是不要太猛烈 233 if (r.nextInt(40) > 38) { 234 this.fire(); 235 } 236 } 237 } 238 239 public void locateDirection() { 240 if (bL && !bU && !bR && !bD) 241 dir = Direction.L; 242 else if (bL && bU && !bR && !bD) 243 dir = Direction.LU; 244 else if (!bL && bU && !bR && !bD) 245 dir = Direction.U; 246 else if (!bL && bU && bR && !bD) 247 dir = Direction.RU; 248 else if (!bL && !bU && bR && !bD) 249 dir = Direction.R; 250 else if (!bL && !bU && bR && bD) 251 dir = Direction.RD; 252 else if (!bL && !bU && !bR && bD) 253 dir = Direction.D; 254 else if (bL && !bU && !bR && bD) 255 dir = Direction.LD; 256 else if (!bL && !bU && !bR && !bD) 257 dir = Direction.STOP; 258 259 } 260 261 private void stay() { 262 x = oldx; 263 y = oldy; 264 } 265 266 // 坦克自己向哪个方向移动,它自己最清楚; 267 public void KeyPressed(KeyEvent e) { 268 // 获得所按下的键所对应的虚拟码: 269 // Returns the integer keyCode associated with the key in this event 270 int key = e.getKeyCode(); 271 // 判断不同的按键,指挥坦克的运动方向 272 switch (key) { 273 case KeyEvent.VK_LEFT: 274 bL = true; 275 break; 276 case KeyEvent.VK_UP: 277 bU = true; 278 break; 279 case KeyEvent.VK_RIGHT: 280 bR = true; 281 break; 282 case KeyEvent.VK_DOWN: 283 bD = true; 284 break; 285 } 286 locateDirection(); 287 } 288 289 public void keyReleased(KeyEvent e) { 290 int key = e.getKeyCode(); 291 // 判断不同的按键,指挥坦克的运动方向 292 // 哪个键按下了,就把对应方向的布尔类型置为false 293 switch (key) { 294 // 为了防止一直按着Ctrl键的时候,炮弹太过于密集 295 // 因此我们定义在Ctrl键抬起的时候才发炮弹 296 // 这样炮弹不至于太过密集 297 case KeyEvent.VK_CONTROL: 298 fire(); 299 break; 300 case KeyEvent.VK_LEFT: 301 bL = false; 302 break; 303 case KeyEvent.VK_UP: 304 bU = false; 305 break; 306 case KeyEvent.VK_RIGHT: 307 bR = false; 308 break; 309 case KeyEvent.VK_DOWN: 310 bD = false; 311 break; 312 313 //当按键A被按下时,会发出超级炮弹superFire() 314 case KeyEvent.VK_A: 315 superFire(); 316 break; 317 } 318 // 重新定位一下 319 locateDirection(); 320 } 321 322 public Missile fire() { 323 if (!live) { 324 return null; 325 } 326 // 计算子弹的位置,使得子弹从坦克的中间发出来 327 int x = this.x + Tank.WIDTH / 2 - Missile.WIDTH / 2; 328 int y = this.y + Tank.HEIGHT / 2 - Missile.HEIGHT / 2; 329 // 这个时候我们就根据炮筒的方向来发炮弹了,之前是根据坦克的方向来发炮弹 330 Missile m = new Missile(x, y, good, ptDir, tc); 331 // 将新产生的炮弹放置到List容器中 332 tc.missiles.add(m); 333 return m; 334 } 335 336 public Missile fire(Direction dir){ 337 if (!live) { 338 return null; 339 } 340 // 计算子弹的位置,使得子弹从坦克的中间发出来 341 int x = this.x + Tank.WIDTH / 2 - Missile.WIDTH / 2; 342 int y = this.y + Tank.HEIGHT / 2 - Missile.HEIGHT / 2; 343 // 这个时候我们就根据炮筒的方向来发炮弹了,之前是根据坦克的方向来发炮弹 344 Missile m = new Missile(x, y, good, ptDir, tc); 345 // 将新产生的炮弹放置到List容器中 346 tc.missiles.add(m); 347 return m; 348 } 349 350 // 拿到包围坦克的那个方块 351 public Rectangle getRect() { 352 353 return new Rectangle(x, y, WIDTH, HEIGHT); 354 } 355 356 public boolean collidesWithWall(Wall w) { 357 358 if (this.live && this.getRect().intersects(w.getRect())) { 359 this.dir = Direction.STOP; 360 // 当坦克撞到墙上的时候,停一下,再回到上一步的位置 361 this.stay(); 362 return true; 363 } 364 return false; 365 366 } 367 368 // 坦克和坦克之间的碰撞检测 369 public boolean collidesWithTanks(java.util.List<Tank> tanks) { 370 for (int i = 0; i < tanks.size(); i++) { 371 Tank t = tanks.get(i); 372 if (this != t) { 373 if (this.live && t.isLive() 374 && this.getRect().intersects(t.getRect())) { 375 this.stay(); 376 t.stay(); 377 } 378 } 379 } 380 return bD; 381 } 382 383 //超级炮弹 384 private void superFire(){ 385 Direction[] dirs=Direction.values(); 386 for(int i=0;i<8;i++){ 387 //朝八个方向各打一发 388 fire(dirs[i]); 389 } 390 } 391 //内部类定义坦克的图形化血量显示 392 private class BloodBar{ 393 public void draw(Graphics g){ 394 Color c=g.getColor(); 395 g.setColor(Color.RED); 396 //空心方块 397 g.drawRect(x, y-10, WIDTH, 10); 398 399 //根据我方坦克的生命值来画代表血量的实体快的大小 400 int w=WIDTH*life/100; 401 402 g.fillRect(x, y-10, w,10); 403 g.setColor(c); 404 } 405 } 406 }
未完待续。。。。。。