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