1.坦克大战总体功能设计
1.1 TankClient
TankClient类是客户端的主类,它承担整个系统的所有功能的调度工作,是游戏运行的界面。它有三个线程,主线程负责对窗口和键盘的监听;PaintThread线程负责对界面的重画;ControlThread负责对游戏中的事件响应。主要方法和属性有:
public static final int GAME_WINDOW_WIDTH= ; 窗口宽度
public static final int GAME_WINDOW_HEIGHT = ; 窗口高度
public static final int GAME_WIDTH = ; 游戏界面宽度
public static final int GAME_HEIGHT = ; 游戏界面高度
public static final int ENEMYSUM = ; 每关总敌人数
public static final int ENEMYCONTROL = ; 场上存活的最多地敌人数
public static int enemyLeave = ENEMYSUM ; 每关剩下的地人数
public static int enemyLive = ; 场上存活的敌人数
public static boolean enemy =f/t ; 添加敌人的标志
public static boolean win = f/t ; 胜利的标志
public static boolean loadMap = f/t ; 加载地图的标志
public static boolean choose = f/t ; 单机是否开始的标志
public static boolean single = f/t ; 单机单人的标志
public static boolean gameOver = f/t ; 失败的标志
public static boolean paint = f/t ; 重新绘画的标志
public static boolean restart =f/t ; 重开的标志
public static boolean controlThread = f/t ; ControlThread线程的标志
public static boolean paintThread = f/t ; PaintThread 线程的标志
public static boolean bomb = f/t ; 爆炸道具的标志
public static boolean timer = f/t ; 时间冻结的标志
public static boolean pass = f/t ; 通过全关的标志
public static boolean net = f/t ; 联网模式的标志
List<Missile> missiles = new ArrayList<Missile>(); 保存子弹的集合
List<Tank> tanks = new ArrayList<Tank>(); 保存坦克的集合
List<Explode> explodes = new ArrayList<Explode>(); 保存爆炸的集合
List<MapElement> maps= new ArrayList<MapElement>();保存地图元素的集合
public void luanchFrame();初始化窗口
public void initMenu();添加菜单栏
public void paint(Graphics g);绘画
public void update(Graphics g);缓冲
void addEnemy();添加敌人坦克
void addProp();刷出道具
public void init();初始化游戏参数
Class PaintThread extends Runnable 绘画线程
Class ControlThread extends Runnable 游戏控制线程
Class KeyMonit extends KeyAdapter 键盘监听类
Class ConnectDialog extends Dialog 网络连接的窗口
Class LeafletDialog extends Dialog 帮助说明窗口
1.2tank类
能够控制坦克的移动和射击,坦克能和别的物体碰撞。因为游戏中是以图片来区分敌我的,单机模式下坦克只有四个方向,而网络模式下坦克有八个方向,所以需要多个坦克类来实现,Tank类为所有坦克类的父接口,其中定义了一些基本的属性和方法,所有坦克类都需实现它。主要方法和属性有:
public static final int XSPEED = ; X方向的速度
public static final int YSPEED = ; Y方向的速度
public static final int WIDTH = ;坦克宽度
public static final int HEIGHT = ;坦克高度
void draw(Graphics g);绘画自己
void move();移动
void back();回退到上一步
void keyPressed(KeyEvent e);对键盘按下事件响应
void keyReleased(KeyEvent e);对键盘弹起事件响应
void fire();开火
void load();加载自己的图片
boolean collidesWithMapElement(MapElement me);检测自己是否与地图上某个元素碰撞
boolean collidesWithMapElements(List<MapElement> list); 检测自己是否与地图上一些元素碰撞
boolean collidesWithTank(Tank t); 检测自己是否与坦克碰撞
boolean collidesWithTanks(List<Tank> list); 检测自己是否与一群坦克碰撞
Rectangle getRect();得到自己的外切矩形
Map<String,Image> imgs 保存坦克的图片。
1.3子弹类
坦克射击后能产生一个飞行的子弹,在和其他物体碰撞后,能做出不同的效果,如:与坦克相撞,则消失;与草地碰撞,穿过去等。由于单机模式下坦克只有四个方向,而网络模式下坦克有八个方向,相应的子弹也是不同的,所以有两个子弹类,还有个接口Missile,他其中定义了一些属性和方法,主要方法和属性有:
public static final int XSPEED = ; X方向的速度
public static final int YSPEED = ; Y方向的速度
public static final int WIDTH = ; 子弹宽度
public static final int HEIGHT = ; 子弹高度
void draw(Graphics g); 绘画自己
void move(); 移动
void load();加载自己的图片
Rectangle getRect();的到自己的外切矩形
boolean hitTank(Tank t); 检测自己是否与坦克碰撞
boolean hitTanks(List<Tank> list); 检测自己是否与一群坦克碰撞
Rectangle getRect();得到自己的外切矩形
boolean hitMapElement(MapElement me); 检测自己是否与地图上某个元素碰撞
boolean hitMapElements(List<MapElement> list); 检测自己是否与地图上一些元素碰撞
Map<String,Image> imgs 保存子弹的图片。
1.4爆炸类
在子弹击中敌方坦克后,能产生一个爆炸,爆炸的效果是通过在一个地方按顺序绘画出一组不同打下的图片得到。Explode类的主要属性和方法有:
private int x , y; 爆炸发生的像素坐标
private int step = 0; 爆炸发生到第几步
public void draw(Graphics g)绘画自己
Image[] imgs 保存爆炸的图片。
1.5加载声音和图片类
加载指定的图片和声音,并封装方便调用。加载声音和图片的类分别是Sound和LoadImages,其中Sound类是通过sun.audio包完成的,Sound的主要属性有:
private static AudioDataStream ads; 音频数据
private static AudioStream as; 音频流
private static FileInputStream fis; 文件输入流
public static final int ADD = 1; 坦克出生
public static final int FIRE = 2; 开火
public static final int HIT = 3; 打到坦克
public static final int START = 4; 游戏开始
图片的加载,通过Toolkit类来现实加载。
2实现
2.1窗口创建
主类TankClient继承Frame类,产生一个固定大小的窗口,并为其添加了windowClosing事件,在退出时有个确认对话框进行确认。还使用了Toolkit类得到屏幕分辨率,来使窗口居中[7]。游戏模式及帮助,是在菜单栏中选择。


1 Toolkit tk = Toolkit.getDefaultToolkit(); 2 3 int windowX = tk.getScreenSize().width; 4 5 int windowY = tk.getScreenSize().height; 6 7 if (GAME_WINDOW_WIDTH > windowX || GAME_WINDOW_HEIGHT > windowY){ 8 9 this.setLocation(0, 0); 10 11 } else { 12 13 this.setLocation((windowX - GAME_WINDOW_WIDTH) / 2, 14 15 (windowY - GAME_WINDOW_HEIGHT) / 2); 16 17 } 18 19 this.setSize(GAME_WINDOW_WIDTH, GAME_WINDOW_HEIGHT); 20 21 this.setTitle("坦克大战"); 22 23 this.addWindowListener(new WindowAdapter() { 24 25 public void windowClosing(WindowEvent e) { 26 27 if (javax.swing.JOptionPane.showConfirmDialog(null,"退出","",javax.swing.JOptionPane.YES_NO_OPTION) <= 0) { 28 29 paintThread = false; 30 31 controlThread = false; 32 33 System.exit(0); 34 35 } else { 36 37 return; 38 39 } 40 41 } 42 43 }); 44 45 this.setResizable(false); 46 47 菜单栏的创建,并为每个MenuItem添加了ActionListener,来控制程序的流程。 48 49 MenuBar mb = new MenuBar(); 50 51 Menu m1 = new Menu("File"); 52 53 Menu m3 = new Menu("Help"); 54 55 MenuItem mi1Single = new MenuItem("单机"); 56 57 MenuItem mi3Leaflet = new MenuItem("说明"); 58 59 m1.add(mi1Single); 60 61 m3.add(mi3Leaflet); 62 63 mb.add(m1); 64 65 mb.add(m3); 66 67 mi1Single.addActionListener(new ActionListener() { 68 69 public void actionPerformed(ActionEvent e) { 70 71 init(); 72 73 level = 1; 74 75 pass = false; 76 77 paint = true; 78 79 enemy = true; 80 81 if (!gameOver) { 82 83 if (!choose) { 84 85 if (paintT == null) { 86 87 paintT = new PaintThread(); 88 89 new Thread(paintT).start(); 90 91 } 92 93 if (controlT == null) { 94 95 controlT = new ControlThread(); 96 97 new Thread(controlT).start(); 98 99 } 100 101 } else { 102 103 tank1p = null; 104 105 if (!single) 106 107 tank2p = null; 108 109 choose = false; 110 111 init(); 112 113 } 114 115 } else { 116 117 tank1p = null; 118 119 if (!single) 120 121 tank2p = null; 122 123 single = true; 124 125 choose = false; 126 127 gameOver = false; 128 129 init(); 130 131 } 132 133 } 134 135 }); 136 137 mi1Net.addActionListener(new ActionListener() { 138 139 public void actionPerformed(ActionEvent e) { 140 141 conn.setVisible(true); 142 143 } 144 145 }); 146 147 mi3Leaflet.addActionListener(new ActionListener() { 148 149 public void actionPerformed(ActionEvent e) { 150 151 leaf.setVisible(true); 152 153 } 154 155 }); 156 157 this.setMenuBar(mb);
2.2坦克的创建与移动
整个程序的动态效果是通过不停的重画来实现的,在TankClient类中有个线程PaintThread调用update方法,再自动调用paint方法,每100毫秒重画一次,paint方法中实现所有物体的绘画。玩家坦克的创建在TankClient类中。


1 public void run() { 2 3 while (paintThread) { 4 5 repaint(); 6 7 if(choose && enemy){ 8 9 if(++enemyTimes > 60){ 10 11 enemyTimes = 0; 12 13 addEnemy(); 14 15 } 16 17 } 18 19 try { 20 21 Thread.sleep(100); 22 23 } catch (InterruptedException e) { 24 25 e.printStackTrace(); 26 27 } 28 29 } 30 31 }
坦克的绘画,就是在制定位置上画上一张图片。图片存放在Map<String, Image> imgs = new HashMap<String, Image>() 的Map对象中,获取方法稍后介绍。坦克的属性中有两个DirectionAlong的成员,他们来控制是画那张图片。


1 switch (ptDir) { 2 3 case L: 4 5 g.drawImage(imgs.get("L"), x, y, null); 6 7 break; 8 9 case U: 10 11 g.drawImage(imgs.get("U"), x, y, null); 12 13 break; 14 15 case R: 16 17 g.drawImage(imgs.get("R"), x, y, null); 18 19 break; 20 21 case D: 22 23 g.drawImage(imgs.get("D"), x, y, null); 24 25 break; 26 27 }
坦克的移动是响应了键盘相应的按键,在move()方法中根据方向来改变坦克的X,Y属性来实现的,move()方法中还有防止坦克出界的控制语句。键盘的监听是再TankClient类中添加的,调用坦克各自的具体的实现。


1 public void keyPressed(KeyEvent e) { 2 3 int key = e.getKeyCode(); 4 5 switch (key) { 6 7 case KeyEvent.VK_A: 8 9 dir = DirectionAlong.L; 10 11 break; 12 13 case KeyEvent.VK_W: 14 15 dir = DirectionAlong.U; 16 17 break; 18 19 case KeyEvent.VK_D: 20 21 dir = DirectionAlong.R; 22 23 break; 24 25 case KeyEvent.VK_S: 26 27 dir = DirectionAlong.D; 28 29 break; 30 31 } 32 33 } 34 35 public void keyReleased(KeyEvent e) { 36 37 int key = e.getKeyCode(); 38 39 switch (key) { 40 41 case KeyEvent.VK_J: 42 43 fire(); 44 45 break; 46 47 case KeyEvent.VK_A: 48 49 case KeyEvent.VK_W: 50 51 case KeyEvent.VK_D: 52 53 case KeyEvent.VK_S: 54 55 dir = DirectionAlong.STOP; 56 57 break; 58 59 } 60 61 } 62 63 public void move() { 64 65 this.oldX = x; 66 67 this.oldY = y; 68 69 switch (dir) { 70 71 case L: 72 73 x -= XSPEED; 74 75 break; 76 77 case U: 78 79 y -= YSPEED; 80 81 break; 82 83 case R: 84 85 x += XSPEED; 86 87 break; 88 89 case D: 90 91 y += YSPEED; 92 93 break; 94 95 case STOP: 96 97 break; 98 99 } 100 101 if (dir != DirectionAlong.STOP) { 102 103 ptDir = dir; 104 105 } 106 107 if (x < 0) 108 109 x = 0; 110 111 if (y < 50) 112 113 y = 50; 114 115 if (x + TANK_ALONG_WIDTH > TankClient.GAME_WIDTH) 116 117 x = TankClient.GAME_WIDTH - TANK_ALONG_WIDTH; 118 119 if (y + TANK_ALONG_HEIGHT > TankClient.GAME_HEIGHT) 120 121 y = TankClient.GAME_HEIGHT - TANK_ALONG_HEIGHT; 122 123 }
为了消除画面的闪烁现象,使用了缓冲技术,就是创建一个虚拟的图片,在内存中把所有需要画的画完,再一次性现实到屏幕上。


1 if (offScreenImage == null) { 2 3 offScreenImage = this.createImage(GAME_WINDOW_WIDTH, 4 5 GAME_WINDOW_HEIGHT); 6 7 } 8 9 Graphics gOffScreen = offScreenImage.getGraphics(); 10 11 Color c = gOffScreen.getColor(); 12 13 gOffScreen.setColor(Color.GREEN); 14 15 gOffScreen.fillRect(0, 0, GAME_WINDOW_WIDTH, GAME_WINDOW_HEIGHT); 16 17 gOffScreen.setColor(c); 18 19 paint(gOffScreen); 20 21 g.drawImage(offScreenImage, 0, 0, null);
2.3子弹的创建与飞行
子弹的实现与坦克相类似,只是子弹发出后不会改变方向,只需要不段前进就可以了,相对坦克容易些。子弹类中有x,y,dir的成员属性,draw方法等封装了起来。实现许多子弹在屏幕上飞行的效果,就是为每个子弹创建一个实例,在TankClient类中有个数组来管理,子弹类中也有出界的处理,出界后会把子弹的live属性设为false;从而这在missiles数组中除去。


1 List<Missile> missiles = new ArrayList<Missile>(); 2 3 for (int i = 0; i < missiles.size(); i++) { 4 5 Missile m = missiles.get(i); 6 7 m.hitTanks(tanks); 8 9 m.hitMapElements(maps); 10 11 m.hitTank(tank1p); 12 13 if (!single) 14 15 m.hitTank(tank2p); 16 17 if (!m.isLive()) { 18 19 missiles.remove(m); 20 21 } else 22 23 m.draw(g); 24 25 }
子弹的创建是在坦克类fire()方法中。


1 if (!live) 2 3 return; 4 5 int x = this.x + Tank.TANK_ALONG_WIDTH / 2 - Missile.WIDTH / 2; 6 7 int y = this.y + Tank.TANK_ALONG_HEIGHT / 2 - Missile.HEIGHT / 2; 8 9 MissileAlong m = new MissileAlong(x, y, ptDir, good, tc, id); 10 11 tc.missiles.add(m);
2.4敌人坦克的创建与AI
敌人的坦克与玩家的坦克一样有draw()方法,move()方法,fire()方法等,只是他们不是通过响应键盘事件来控制,而是简单的AI控制,AI的控制是通过随机数来实现的,给它一个随机的方法和随机开火。区分敌我的标志是good属性,good为true是己方,false为敌人。


1 DirectionAlong[] dires = DirectionAlong.values(); 2 3 if (step == 0) { 4 5 step = r.nextInt(13) + 3; 6 7 int in = r.nextInt(dires.length); 8 9 dir = dires[in]; 10 11 } 12 13 step--; 14 15 if (r.nextInt(100) > 95) 16 17 18 19 this.fire();
敌人坦克创建在PaintThread线程中,在能创建新的敌人坦克的情况下,每隔秒创建一个,具体的创建方法是addEnemy();
敌人坦克的数量是很多的,所以在Tankclient中有个数组来管理。


1 List<Tank> tanks = new ArrayList<Tank>(); 2 3 for (int i = 0; i < tanks.size(); i++) { 4 5 Tank t = tanks.get(i); 6 7 t.collidesWithMapElements(maps); 8 9 t.collidesWithTanks(tanks); 10 11 t.collidesWithTank(tank1p); 12 13 if (!single) 14 15 t.collidesWithTank(tank2p); 16 17 if (!t.isLive()) { 18 19 tanks.remove(t); 20 21 enemyLive--; 22 23 } else 24 25 t.draw(g); 26 27 }
2.5将敌人坦克击毙
子弹类中加入hitTank(Tank t)的方法来击毙敌人坦克,碰撞检测的辅助类Rectangle,在坦克类和子弹类中都加入getRect()方法,得到其外切矩形。当击中敌人坦克时,敌人坦克死亡,子弹也死亡。由于有很多坦克所以还加了方法 hitTanks(List<Tank> tanks) 来检测某子弹与一群坦克的碰撞。


1 public Rectangle getRect() { 2 3 return new Rectangle(x, y, WIDTH, HEIGHT); 4 5 } 6 7 public boolean hitTank(Tank t) { 8 9 if (this.live && t.isLive() && this.good != t.isGood() 10 11 && this.getRect().intersects(t.getRect())) { 12 13 this.live = false; 14 15 if(!t.isPower()){ 16 17 t.setLive(false);} 18 19 Sound.sounds(Sound.HIT); 20 21 return true; 22 23 } 24 25 return false; 26 27 } 28 29 public boolean hitTanks(List<Tank> tanks) { 30 31 for(int i = 0; i<tanks.size(); i++){ 32 33 if(hitTank(tanks.get(i))){ 34 35 return true; 36 37 } 38 39 } 40 41 return false; 42 43 }
2.6击毙坦克时产生爆炸
爆炸的效果是在一个点上按顺序画一组图片产生的,主要方法为draw()方法。有个成员属性step来记录爆炸发生到第几步,画完后会把live属性设为false。应为会有许多爆炸在同事出现在屏幕上,所以在TankClient中有个数组管理。


1 public void draw(Graphics g){ 2 3 if(!live) return; 4 5 if(!init){ 6 7 for (int i = 0; i < imgs.length; i++) { 8 9 g.drawImage(imgs[i], -10, -10, null); 10 11 } 12 13 init = true; 14 15 } 16 17 if(step >= imgs.length) { 18 19 step = 0; 20 21 live = false; 22 23 return; 24 25 } 26 27 g.drawImage(imgs[step],x,y,null); 28 29 step++; 30 31 }