坦克大战进阶第三版:防止重叠、击杀记录、存盘退出、背景音乐等
1. 坦克大战0.5版
1.1 功能进阶:
增加功能[HspTankGame05java]
- 防止敌人坦克重叠运动[思路->走代码]
- 记录玩家的总成绩(累积击毁敌方坦克数),存盘退出[io流]
- 记录退出游戏时敌人坦克坐标/方向,存盘退出[io流]
- 玩游戏时,可以选择是开新游戏还是继续上局游戏
1.2 进阶思路一:
1.2.1 第一步:防止敌人坦克重叠运动
-
这里我们先以一辆敌人坦克跟其他敌人坦克进行对比:
- 当敌人坦克一方向向上的时候:其他敌人坦克有两种情况
- 其他敌人坦克方向为上/下的时候:
- 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 40]
- 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 60]
- 其他敌人坦克方向为左/右的时候:
- 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 60]
- 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 40]
- 这时候我们只要判断敌人坦克一的左上角和右上角不在其他坦克的范围内就为真;
- 其他敌人坦克方向为上/下的时候:
- 当敌人坦克一方向向上的时候:其他敌人坦克有两种情况
-
当敌人坦克一方向向右的时候:其他敌人坦克有两种情况
- 其他敌人坦克方向为上/下的时候:
- 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 40]
- 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 60]
- 其他敌人坦克方向为左/右的时候:
- 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 60]
- 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 40]
- 这时候我们只要判断敌人坦克一的右上角和右下角不在其他坦克的范围内就为真;
- 其他敌人坦克方向为上/下的时候:
-
当敌人坦克一方向向下的时候:其他敌人坦克有两种情况
- 其他敌人坦克方向为上/下的时候:
- 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 40]
- 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 60]
- 其他敌人坦克方向为左/右的时候:
- 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 60]
- 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 40]
- 这时候我们只要判断敌人坦克一的左下角和右下角不在其他坦克的范围内就为真;
- 其他敌人坦克方向为上/下的时候:
-
当敌人坦克一方向向左的时候:其他敌人坦克有两种情况
- 其他敌人坦克方向为上/下的时候:
- 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 40]
- 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 60]
- 其他敌人坦克方向为左/右的时候:
- 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 60]
- 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 40]
- 这时候我们只要判断敌人坦克一的左上角和左下角不在其他坦克的范围内就为真;
- 其他敌人坦克方向为上/下的时候:
-
至关重要的一步:在MyPanel中将enemyTanks 设置给 enemyTank,要不然不能实现防止敌人坦克重叠的优化
enemyTank.setEnemyTanks(enemyTanks);
1.3 进阶思路二
1.3.1 第二步:记录玩家的总成绩(累积击毁敌方坦克数),存盘退出[io流]
一、新建一个Recorder类,用于记录相关的信息:
-
在类里面定义变量,记录我方击毁敌人坦克数量
private static int allEnemyTankNum = 0;
-
定义IO对象
private static FileWriter fw = null; private static BufferedWriter bw = null; private static String recordFile = "E:\\myRecord.txt";
-
添加allEnemyTankNum的构造器和set方法
public static int getAllEnemyTankNum() { return allEnemyTankNum; } public static void setAllEnemyTankNum(int allEnemyTankNum) { Recoder.allEnemyTankNum = allEnemyTankNum; }
-
当我方坦克击毁一个敌人,就应当 allEnemyTankNum++;
public static void addAllEnemyTankNum() { Recoder.allEnemyTankNum++; }
-
增加一个方法,当游戏退出时,我们将 allEnemyTankNum 保存到 recordFile
public static void keepRecord() { try { bw = new BufferedWriter(new FileWriter(recordFile)); bw.write(allEnemyTankNum + "\r\n");//"\r\n" 换行 //或者用 bw.newLine();换行 } catch (IOException e) { throw new RuntimeException(e); } finally { try { if (bw!=null){ bw.close(); } } catch (IOException e) { throw new RuntimeException(e); } } }
二、在坦克显示的右边界面画出机会敌方坦克的情况,并且在IO文件中记录相应数据
-
在MyPanel中编写方法,显示我方击毁敌方坦克的信息
public void showInfo(Graphics g) { //画出玩家的总成绩 g.setColor(Color.black); Font font = new Font("宋体", Font.BOLD, 25); g.setFont(font); g.drawString("您累计击毁敌方坦克", 1020, 30); drawTank(1020, 60, g, 0, 0);//画出一个敌方坦克 g.setColor(Color.BLACK);//这里需要重置设置成黑色 //记录击毁敌方坦克的数量 g.drawString(Recoder.getAllEnemyTankNum() + "", 1080, 100); }
-
在击中敌人坦克的hitTank()方法中,当敌人击毁的时候,增加Recorder的addAllEnemyTankNum方法,记录击毁的数量
//当我方击毁敌人坦克时,就对数据allEnemyTankNum++ //因为 tank 可以是 MyTank,也可以是 EnemyTank //所以我们这里要进行一个判断 if (tank instanceof EnemyTank) { Recoder.addAllEnemyTankNum(); }
-
最后在 Jframe 中增加响应关闭窗口的处理,当我们关闭窗口的时候,记录Recorder的keepRecord()方法
//在 Jframe 中增加响应关闭窗口的处理 this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { Recoder.keepRecord(); System.exit(0); } });
1.4 进阶思路三
1.4.1 第三步:记录退出游戏时敌人坦克坐标/方向,存盘退出[io流]
-
对 KeepRecord() 进行升级,保存敌人坦克的坐标和方向
//遍历敌人坦克的 Vector ,然后根据情况保存 //OOP ,定义一个属性,然后通过setXx 得到敌人的坦克 Vector for (int i = 0; i < enemyTanks.size(); i++) { //取出敌人坦克 EnemyTank enemyTank = enemyTanks.get(i); if (enemyTank.isLive){ //为了保险,建议判断 //保存坦克信息 String record = enemyTank.getX()+" "+enemyTank.getY()+" "+enemyTank.getDirection(); //写入到文件 bw.write(record+"\r\n"); //或者加一个 //bw.newLine(); } }
-
接下来将 MyPanel 对象的 enemyTanks 设置给 Recorder 的 enemyTanks
//将 MyPanel 对象的 enemyTanks 设置给 Recorder 的 enemyTanks Recoder.setEnemyTanks(enemyTanks);
1.5 进阶思路四
1.5.1 第四步:玩游戏时,可以选择是开新游戏还是继续上局游戏
一、将Recorder文件中每个敌人信息恢复成一个Node对象,再将其放入Vector
-
先在Recorder中定义一个Node的Vector,用于保存敌人坦克的信息
private static Vector<Node> nodes = new Vector<>();
-
在Recorder中定义一个输入流,用于读取recordFile文件
private static BufferedReader br = null;
-
然后增加一个方法,用于读取recordFile,恢复相关信息;该方法在继续上局游戏的时候调用
public static Vector<Node> getNodesAndEnemyTankRec() { try { br = new BufferedReader(new FileReader(recordFile)); //先恢复allEnemyTankNum的值 //1. 先读取第一行allEnemyTankNum // 即读取击毁敌人坦克的数量 //因为 br.readLine 是一个字符串,所以我们用Integer.parseInt()进行一个转换 allEnemyTankNum = Integer.parseInt(br.readLine()); //2. 接下来循环读取文件,生成nodes集合 String Line = ""; //后面读取到的数据为 100 170 2类型 while ((Line = br.readLine()) != null) { //所以这里用split分割String生成一个数组 String[] xyd = Line.split(" "); //将读取到的数据生成Node对象 //因为这里的数据为String,所以用Integer.parseInt()进行一个转换 Node node = new Node(Integer.parseInt(xyd[0]), Integer.parseInt(xyd[1]), Integer.parseInt(xyd[2])); //将生成的Node对象node放入到Vector对象的nodes中 nodes.add(node); } } catch (IOException e) { throw new RuntimeException(e); } finally { try { if (br != null) { br.close(); } } catch (IOException e) { throw new RuntimeException(e); } } return nodes; }
二、通过Node的Vector去恢复敌人坦克的位置和方向
-
找到我们游戏启动的地方MyPanel,在游戏一启动就进行数据恢复;然后把相应的数据给下面定义的Node对象nodes接收
nodes = Recoder.getNodesAndEnemyTankRec();
-
然后在MyPanel中定义一个存放Node对象的Vector,用于恢复敌人坦克的坐标和方向;
Vector<Node> nodes = new Vector<>();
-
然后在MyPanel中增加一个参数,用于选择开始新游戏或者继续上局
public MyPanel(String key)
-
在主界面MyTankGame05中增加一个用户输入界面Scanner
static Scanner scanner = new Scanner(System.in);//添加static,可直接调用
-
在主界面MyTankGame05的构造器中添加供用户选择的代码:开始新游戏还是继续上局,再将key放入初始化中
//供用户选择:开始新游戏还是继续上局 System.out.println("请输入选择:1:新游戏 2:继续上局"); String key = scanner.next(); //初始化 mp = new MyPanel(key);
-
最后再在MyPanel中进行一个switch判断,进行新游戏和继续上局
switch (key) { case "1": //初始化敌人的坦克 for (int i = 0; i < enemyTanksize; i++) { //创建一个敌人坦克 EnemyTank enemyTank = new EnemyTank((100 * (i + 1)), 0); //将enemyTanks 设置给 enemyTank !!! //要不然不能完成重叠优化 enemyTank.setEnemyTanks(enemyTanks); //设置方向 enemyTank.setDirection(2); //启动敌人坦克,让他动起来 new Thread(enemyTank).start(); //给该enemyTank对象加入一颗子弹 Shot shot = new Shot(enemyTank.getX() + 20, enemyTank.getY() + 60, enemyTank.getDirection()); //加入到enemyTank的Vector成员 enemyTank.shots.add(shot); //立即启动 new Thread(shot).start(); enemyTanks.add(enemyTank); } break; case "2": for (int i = 0; i < nodes.size(); i++) { //取出存放上局敌人数据的Node对象 Node node = nodes.get(i); //恢复上局每个敌人坦克的坐标 EnemyTank enemyTank = new EnemyTank(node.getX(), node.getY()); //将enemyTanks 设置给 enemyTank !!! //要不然不能完成重叠优化 enemyTank.setEnemyTanks(enemyTanks); //恢复上局每个敌人坦克的方向 enemyTank.setDirection(node.getDirection()); //启动敌人坦克,让他动起来 new Thread(enemyTank).start(); //给该enemyTank对象加入一颗子弹 Shot shot = new Shot(enemyTank.getX() + 20, enemyTank.getY() + 60, enemyTank.getDirection()); //加入到enemyTank的Vector成员 enemyTank.shots.add(shot); //立即启动 new Thread(shot).start(); enemyTanks.add(enemyTank); } break; default: System.out.println("选择有误"); }
2. 坦克大战0.6版
2.1 功能进阶
增加功能[HspTankGame05java]
- 游戏开始时,播放经典的坦克大战音乐 ,[思路,使用一个播放音乐的类,即可]
- 修正下文件存储位置
- 处理文件相关异常
2.2 进阶思路一:
2.2.1 第一步:游戏开始时,播放经典的坦克大战音乐 ,
-
使用一个播放音乐的类AePlayWave
public class AePlayWave extends Thread { private String filename; public AePlayWave(String wavfile) { //构造器 filename = wavfile; } public void run() { File soundFile = new File(filename); AudioInputStream audioInputStream = null; try { audioInputStream = AudioSystem.getAudioInputStream(soundFile); } catch (Exception e1) { e1.printStackTrace(); return; } AudioFormat format = audioInputStream.getFormat(); SourceDataLine auline = null; DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); try { auline = (SourceDataLine) AudioSystem.getLine(info); auline.open(format); } catch (Exception e) { e.printStackTrace(); return; } auline.start(); int nBytesRead = 0; //这是缓冲 byte[] abData = new byte[512]; try { while (nBytesRead != -1) { nBytesRead = audioInputStream.read(abData, 0, abData.length); if (nBytesRead >= 0) auline.write(abData, 0, nBytesRead); } } catch (IOException e) { e.printStackTrace(); return; } finally { auline.drain(); auline.close(); } } }
-
找到一个自己想要的音乐,例如111.wav
-
在MyPanel的里面播放指定的音乐即可
//这里播放指定的音乐 new AePlayWave("src\\111.wav").start();
2.3 进阶思路二:
2.3.1 第二步:修正下文件存储位置
思路:
-
我们储存文件的位置一般跟着我们的项目走
-
但是我们之前储存IO流的文件位置定义在E盘,到时候不一定能够跟着项目一起走
-
会导致文件缺失,我们可以把记录文件保存到src下
//把记录文件的位置改为src下 //private static String recordFile = "E:\\myRecord.txt"; private static String recordFile = "src\\myRecord.txt";
2.4 进阶思路三:
2.4.1 第三步:处理文件相关异常
一、我们前面在MyPanel中设置了在游戏一启动就进行数据恢复;
二、但是当我们还没有数据储存的时候运行继续上局时,会抛出异常
三、因此我们需要进行优化 => 提升代码的健壮性
-
先在Recorder类中添加一个getRecordFile()方法,用于返回记录文件的目录
//返回记录文件的目录 public static String getRecordFile() { return recordFile; }
-
在MyPanel中判断目录是否存在,如果存在,还是按照原来的执行,如果不存在只能开启新的游戏
//1. 我们先判断记录文件是否存在 //2. 如果存在,游戏正常执行, // 如果文件不存在,提示只能开启新游戏, key = "1" File file = new File(Recoder.getRecordFile()); if (file.exists()){ //当我们游戏启动的时候进行数据恢复 //然后用nodes接收相应数据 nodes = Recoder.getNodesAndEnemyTankRec(); }else { System.out.println("文件不存在,只能开启新的游戏"); key = "1"; }
3. 坦克大战进阶0.6完整版
3.1 汇总
我们目前为止坦克大战0.6版的最终代码如下:
-
父类坦克Tank
public class Tank { private int x;//坦克的横坐标 private int y;//坦克的纵坐标 boolean isLive = true; //坦克的方向 0向上 1向右 2向下 3向左 private int direction; //坦克的速度 private int speed = 2; public int getSpeed() { return speed; } public void setSpeed(int speed) { this.speed = speed; } //添加上下左右移动方法 //向上 public void moveUp() { y -= speed; } //向下 public void moveDown() { y += speed; } //向左 public void moveLeft() { x -= speed; } //向右 public void moveRight() { x += speed; } public int getDirection() { return direction; } public void setDirection(int direction) { this.direction = direction; } public Tank(int x, int y) { this.x = x; this.y = y; } public int getX() { return x; } public void setX(int x) { this.x = x; } public int getY() { return y; } public void setY(int y) { this.y = y; } }
-
敌人坦克 EnemyTank
public class EnemyTank extends Tank implements Runnable { //在敌人坦克类,使用Vector保存多个shot Vector<Shot> shots = new Vector<>(); //1. Vector<EnemyTank> 在 MyPanel Vector<EnemyTank> enemyTanks = new Vector<>(); //这里提供一个方法,可以将 MyPanel 的成员 Vector<EnemyTank> enemyTanks = new Vector<>(); //设置到 EnemyTank 的成员 enemyTanks public void setEnemyTanks(Vector<EnemyTank> enemyTanks) { this.enemyTanks = enemyTanks; } //编写方法,判断当前的这个敌人坦克,是否和enemyTanks 中的其他坦克发生重叠或者碰撞 public boolean isTouchEnemyTank() { //判断当前敌人坦克方向(this) switch (this.getDirection()) { case 0://向上 for (int i = 0; i < enemyTanks.size(); i++) { EnemyTank enemyTank = enemyTanks.get(i); if (enemyTank != this) { //当敌人坦克是上/下 //1.如果敌人的坦克是上/下 // 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 40] // 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 60] if (enemyTank.getDirection() == 0 || enemyTank.getDirection() == 2) { //2. 当前的坦克左上角坐标[this.getX(),this.getY()] if (this.getX() >= enemyTank.getX() && this.getX() <= enemyTank.getX() + 40 && this.getY() >= enemyTank.getY() && this.getY() <= enemyTank.getY() + 60) { return true; } //3. 当前的坦克右上角坐标[this.getX()+40,this.getY()] if (this.getX() + 40 >= enemyTank.getX() && this.getX() + 40 <= enemyTank.getX() + 40 && this.getY() >= enemyTank.getY() && this.getY()