坦克大战进阶第三版:防止重叠、击杀记录、存盘退出、背景音乐等

1. 坦克大战0.5版

1.1 功能进阶:

增加功能[HspTankGame05java]

  1. 防止敌人坦克重叠运动[思路->走代码]
  2. 记录玩家的总成绩(累积击毁敌方坦克数),存盘退出[io流]
  3. 记录退出游戏时敌人坦克坐标/方向,存盘退出[io流]
  4. 玩游戏时,可以选择是开新游戏还是继续上局游戏

1.2 进阶思路一:

1.2.1 第一步:防止敌人坦克重叠运动

  1. 这里我们先以一辆敌人坦克跟其他敌人坦克进行对比:

    1. 当敌人坦克一方向向上的时候:其他敌人坦克有两种情况
      1. 其他敌人坦克方向为上/下的时候:
        1. 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 40]
        2. 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 60]
      2. 其他敌人坦克方向为左/右的时候:
        1. 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 60]
        2. 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 40]
      3. 这时候我们只要判断敌人坦克一的左上角和右上角不在其他坦克的范围内就为真;
  2. 当敌人坦克一方向向右的时候:其他敌人坦克有两种情况

    1. 其他敌人坦克方向为上/下的时候:
      1. 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 40]
      2. 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 60]
    2. 其他敌人坦克方向为左/右的时候:
      1. 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 60]
      2. 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 40]
    3. 这时候我们只要判断敌人坦克一的右上角和右下角不在其他坦克的范围内就为真;
  3. 当敌人坦克一方向向下的时候:其他敌人坦克有两种情况

    1. 其他敌人坦克方向为上/下的时候:
      1. 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 40]
      2. 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 60]
    2. 其他敌人坦克方向为左/右的时候:
      1. 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 60]
      2. 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 40]
    3. 这时候我们只要判断敌人坦克一的左下角和右下角不在其他坦克的范围内就为真;
  4. 当敌人坦克一方向向左的时候:其他敌人坦克有两种情况

    1. 其他敌人坦克方向为上/下的时候:
      1. 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 40]
      2. 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 60]
    2. 其他敌人坦克方向为左/右的时候:
      1. 敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 60]
      2. 敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 40]
    3. 这时候我们只要判断敌人坦克一的左上角和左下角不在其他坦克的范围内就为真;
  5. 至关重要的一步:在MyPanel中将enemyTanks 设置给 enemyTank,要不然不能实现防止敌人坦克重叠的优化

    enemyTank.setEnemyTanks(enemyTanks);
    

1.3 进阶思路二

1.3.1 第二步:记录玩家的总成绩(累积击毁敌方坦克数),存盘退出[io流]

一、新建一个Recorder类,用于记录相关的信息:

  1. 在类里面定义变量,记录我方击毁敌人坦克数量

    private static int allEnemyTankNum = 0;
    
  2. 定义IO对象

    private static FileWriter fw = null;
    private static BufferedWriter bw = null;
    private static String recordFile = "E:\\myRecord.txt";
    
  3. 添加allEnemyTankNum的构造器和set方法

    public static int getAllEnemyTankNum() {
            return allEnemyTankNum;
        }
    
        public static void setAllEnemyTankNum(int allEnemyTankNum) {
            Recoder.allEnemyTankNum = allEnemyTankNum;
        }
    
  4. 当我方坦克击毁一个敌人,就应当 allEnemyTankNum++;

    public static void addAllEnemyTankNum() {
            Recoder.allEnemyTankNum++;
    }
    
  5. 增加一个方法,当游戏退出时,我们将 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文件中记录相应数据

  1. 在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);
        }
    
  2. 在击中敌人坦克的hitTank()方法中,当敌人击毁的时候,增加Recorder的addAllEnemyTankNum方法,记录击毁的数量

    //当我方击毁敌人坦克时,就对数据allEnemyTankNum++
    //因为 tank 可以是 MyTank,也可以是 EnemyTank
    //所以我们这里要进行一个判断
    if (tank instanceof EnemyTank) {
       Recoder.addAllEnemyTankNum();
      }
    
  3. 最后在 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流]

  1. 对 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();
       }
    }
    
  2. 接下来将 MyPanel 对象的 enemyTanks 设置给 Recorder 的 enemyTanks

    //将 MyPanel 对象的 enemyTanks 设置给 Recorder 的 enemyTanks
    Recoder.setEnemyTanks(enemyTanks);
    

1.5 进阶思路四

1.5.1 第四步:玩游戏时,可以选择是开新游戏还是继续上局游戏

一、将Recorder文件中每个敌人信息恢复成一个Node对象,再将其放入Vector

  1. 先在Recorder中定义一个Node的Vector,用于保存敌人坦克的信息

    private static Vector<Node> nodes = new Vector<>();
    
  2. 在Recorder中定义一个输入流,用于读取recordFile文件

    private static BufferedReader br = null;
    
  3. 然后增加一个方法,用于读取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去恢复敌人坦克的位置和方向

  1. 找到我们游戏启动的地方MyPanel,在游戏一启动就进行数据恢复;然后把相应的数据给下面定义的Node对象nodes接收

    nodes = Recoder.getNodesAndEnemyTankRec();
    
  2. 然后在MyPanel中定义一个存放Node对象的Vector,用于恢复敌人坦克的坐标和方向;

    Vector<Node> nodes = new Vector<>();
    
  3. 然后在MyPanel中增加一个参数,用于选择开始新游戏或者继续上局

    public MyPanel(String key) 
    
  4. 在主界面MyTankGame05中增加一个用户输入界面Scanner

    static Scanner scanner = new Scanner(System.in);//添加static,可直接调用
    
  5. 在主界面MyTankGame05的构造器中添加供用户选择的代码:开始新游戏还是继续上局,再将key放入初始化中

    //供用户选择:开始新游戏还是继续上局
    System.out.println("请输入选择:1:新游戏 2:继续上局");
    String key = scanner.next();
    //初始化
    mp = new MyPanel(key);
    
  6. 最后再在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]

  1. 游戏开始时,播放经典的坦克大战音乐 ,[思路,使用一个播放音乐的类,即可]
  2. 修正下文件存储位置
  3. 处理文件相关异常

2.2 进阶思路一:

2.2.1 第一步:游戏开始时,播放经典的坦克大战音乐 ,

  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();
            }
    
        }
    }
    
  2. 找到一个自己想要的音乐,例如111.wav

  3. 在MyPanel的里面播放指定的音乐即可

    //这里播放指定的音乐
    new AePlayWave("src\\111.wav").start();
    

2.3 进阶思路二:

2.3.1 第二步:修正下文件存储位置

思路:

  1. 我们储存文件的位置一般跟着我们的项目走

  2. 但是我们之前储存IO流的文件位置定义在E盘,到时候不一定能够跟着项目一起走

  3. 会导致文件缺失,我们可以把记录文件保存到src下

    //把记录文件的位置改为src下
    //private static String recordFile = "E:\\myRecord.txt";
    private static String recordFile = "src\\myRecord.txt";
    

2.4 进阶思路三:

2.4.1 第三步:处理文件相关异常

一、我们前面在MyPanel中设置了在游戏一启动就进行数据恢复;

二、但是当我们还没有数据储存的时候运行继续上局时,会抛出异常

三、因此我们需要进行优化 => 提升代码的健壮性

  1. 先在Recorder类中添加一个getRecordFile()方法,用于返回记录文件的目录

    //返回记录文件的目录
    public static String getRecordFile() {
        return recordFile;
    }
    
  2. 在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版的最终代码如下:

  1. 父类坦克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;
        }
    
    }
    
  2. 敌人坦克 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() <= enemyTank.getY() + 60) {
                                    return true;
                                }
                            }
                            //当敌人坦克是左/右
                            // 1.如果敌人的坦克是左/右
                            //  敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 60]
                            //  敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 40]
                            if (enemyTank.getDirection() == 1 || enemyTank.getDirection() == 3) {
                                //2. 当前的坦克左上角坐标[this.getX(),this.getY()]
                                if (this.getX() >= enemyTank.getX() && this.getX() <= enemyTank.getX() + 60
                                        && this.getY() >= enemyTank.getY() && this.getY() <= enemyTank.getY() + 40) {
                                    return true;
                                }
                                //3. 当前的坦克右上角坐标[this.getX()+40,this.getY()]
                                if (this.getX() + 40 >= enemyTank.getX() && this.getX() + 40 <= enemyTank.getX() + 60
                                        && this.getY() >= enemyTank.getY() && this.getY() <= enemyTank.getY() + 40) {
                                    return true;
                                }
                            }
                        }
                    }
                    break;
                case 1://向右
                    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()+60,this.getY()]
                                if (this.getX() + 60 >= enemyTank.getX() && this.getX() + 60 <= enemyTank.getX() + 40
                                        && this.getY() >= enemyTank.getY() && this.getY() <= enemyTank.getY() + 60) {
                                    return true;
                                }
                                //3. 当前的坦克右下角坐标[this.getX()+60,this.getY()+40]
                                if (this.getX() + 60 >= enemyTank.getX() && this.getX() + 60 <= enemyTank.getX() + 40
                                        && this.getY() + 40 >= enemyTank.getY() && this.getY() + 40 <= enemyTank.getY() + 60) {
                                    return true;
                                }
                            }
                            //当敌人坦克是左/右
                            // 1.如果敌人的坦克是左/右
                            //  敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 60]
                            //  敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 40]
                            if (enemyTank.getDirection() == 1 || enemyTank.getDirection() == 3) {
                                //2. 当前的坦克右上角坐标[this.getX()+60,this.getY()]
                                if (this.getX() + 60 >= enemyTank.getX() && this.getX() + 60 <= enemyTank.getX() + 60
                                        && this.getY() >= enemyTank.getY() && this.getY() <= enemyTank.getY() + 40) {
                                    return true;
                                }
                                //3. 当前的坦克右下角坐标[this.getX()+60,this.getY()+40]
                                if (this.getX() + 60 >= enemyTank.getX() && this.getX() + 60 <= enemyTank.getX() + 60
                                        && this.getY() + 40 >= enemyTank.getY() && this.getY() + 40 <= enemyTank.getY() + 40) {
                                    return true;
                                }
                            }
                        }
                    }
                    break;
                case 2://向下
                    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()+60]
                                if (this.getX() >= enemyTank.getX() && this.getX() <= enemyTank.getX() + 40
                                        && this.getY() + 60 >= enemyTank.getY() && this.getY() + 60 <= enemyTank.getY() + 60) {
                                    return true;
                                }
                                //3. 当前的坦克右下角坐标[this.getX()+40,this.getY()+60]
                                if (this.getX() + 40 >= enemyTank.getX() && this.getX() + 40 <= enemyTank.getX() + 40
                                        && this.getY() + 60 >= enemyTank.getY() && this.getY() + 60 <= enemyTank.getY() + 60) {
                                    return true;
                                }
                            }
                            //当敌人坦克是左/右
                            // 1.如果敌人的坦克是左/右
                            //  敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 60]
                            //  敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 40]
                            if (enemyTank.getDirection() == 1 || enemyTank.getDirection() == 3) {
                                //2. 当前的坦克左下角坐标[this.getX(),this.getY()+60]
                                if (this.getX() >= enemyTank.getX() && this.getX() <= enemyTank.getX() + 60
                                        && this.getY() + 60 >= enemyTank.getY() && this.getY() + 60 <= enemyTank.getY() + 40) {
                                    return true;
                                }
                                //3. 当前的坦克右下角坐标[this.getX()+40,this.getY()+60]
                                if (this.getX() + 40 >= enemyTank.getX() && this.getX() + 40 <= enemyTank.getX() + 60
                                        && this.getY() + 60 >= enemyTank.getY() && this.getY() + 60 <= enemyTank.getY() + 40) {
                                    return true;
                                }
                            }
                        }
                    }
                    break;
                case 3://向左
                    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(),this.getY()+40]
                                if (this.getX() >= enemyTank.getX() && this.getX() <= enemyTank.getX() + 40
                                        && this.getY() + 40 >= enemyTank.getY() && this.getY() + 40 <= enemyTank.getY() + 60) {
                                    return true;
                                }
                            }
                            //当敌人坦克是左/右
                            // 1.如果敌人的坦克是左/右
                            //  敌人的 x 范围 [enemyTank.getX(),enemyTank.getX() + 60]
                            //  敌人的 y 范围 [enemyTank.getY(),enemyTank.getY() + 40]
                            if (enemyTank.getDirection() == 1 || enemyTank.getDirection() == 3) {
                                //2. 当前的坦克左上角坐标[this.getX(),this.getY()]
                                if (this.getX() >= enemyTank.getX() && this.getX() <= enemyTank.getX() + 60
                                        && this.getY() >= enemyTank.getY() && this.getY() <= enemyTank.getY() + 40) {
                                    return true;
                                }
                                //3. 当前的坦克左下角坐标[this.getX(),this.getY()+40]
                                if (this.getX() >= enemyTank.getX() && this.getX() <= enemyTank.getX() + 60
                                        && this.getY() + 40 >= enemyTank.getY() && this.getY() + 40 <= enemyTank.getY() + 40) {
                                    return true;
                                }
                            }
                        }
                    }
                    break;
            }
            return false;
        }
    
        public EnemyTank(int x, int y) {
            super(x, y);
        }
    
        @Override
        public void run() {
            while (true) {
    
                //从这里我们判断如果shots.size()==0,
                // 说明子弹已经销毁,再创建一颗子弹放入shots
                //并启动
                if (isLive && shots.size() < 3) {
                    //创建一个临时变量
                    Shot s = null;
                    //判断坦克的方向
                    //创建对应的子弹
                    switch (getDirection()) {
                        case 0://向上
                            s = new Shot(getX() + 20, getY(), 0);
                            break;
                        case 1://向右
                            s = new Shot(getX() + 60, getY() + 20, 1);
                            break;
                        case 2://向下
                            s = new Shot(getX() + 20, getY() + 60, 2);
                            break;
                        case 3://向左
                            s = new Shot(getX(), getY() + 20, 3);
                            break;
                    }
                    //添加一颗子弹
                    shots.add(s);
                    //启动
                    new Thread(s).start();
                }
    
                //设置坦克移动
                switch (getDirection()) {
                    case 0:
                        //让坦克保持一个方向走100步
                        for (int i = 0; i < 50; i++) {
                            if (getY() > 0 && !isTouchEnemyTank()) {
                                moveUp();
                            }
                            //休眠100毫秒
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        break;
                    case 1:
                        //让坦克保持一个方向走100步
                        for (int i = 0; i < 50; i++) {
                            if (getX() + 60 < 1000 && !isTouchEnemyTank()) {
                                moveRight();
                            }
                            //休眠100毫秒
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        break;
                    case 2:
                        //让坦克保持一个方向走100步
                        for (int i = 0; i < 50; i++) {
                            if (getY() + 60 < 750 && !isTouchEnemyTank()) {
                                moveDown();
                            }
                            //休眠100毫秒
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        break;
                    case 3:
                        //让坦克保持一个方向走100步
                        for (int i = 0; i < 50; i++) {
                            if (getX() > 0 && !isTouchEnemyTank()) {
                                moveLeft();
                            }
                            //休眠100毫秒
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                throw new RuntimeException(e);
                            }
                        }
                        break;
                }
    
                //随机改变方向
                setDirection((int) (Math.random() * 4));
    
                //考虑线程什么时候退出
                if (!isLive) {
                    break;
                }
            }
        }
    }
    
  3. 我们的坦克 MyTank

    public class MyTank extends Tank {
        //定义一个shot对象,表示一个射击行为(线程)
        Shot shot = null;
        //创建多个子弹
        Vector<Shot> shots = new Vector<>();
    
        //创建一个属性判断我们是否存活
    
    
        public MyTank(int x, int y) {
            super(x, y);
        }
    
        public void shotEnemyTank() {
            if (shots.size() == 5) {
                return;
            }
            //创建shot对象
            switch (getDirection()) {
                case 0://向上
                    shot = new Shot(getX() + 20, getY(), 0);
                    break;
                case 1://向右
                    shot = new Shot(getX() + 60, getY() + 20, 1);
                    break;
                case 2://向下
                    shot = new Shot(getX() + 20, getY() + 60, 2);
                    break;
                case 3://向左
                    shot = new Shot(getX(), getY() + 20, 3);
                    break;
            }
            //把新创建的shot放入到shots集合中
            shots.add(shot);
            //启动我们的shot线程
            new Thread(shot).start();
        }
    }
    
  4. 子弹 Shot

    public class Shot implements Runnable {//射击子弹
        int x;//子弹x坐标
        int y;//子弹y坐标
        int direction;//子弹方向
        int speed = 2;//子弹速度
        boolean isLive = true;//子弹是否还存活
    
        public Shot(int x, int y, int direction) {
            this.x = x;
            this.y = y;
            this.direction = direction;
    
        }
    
        @Override
        public void run() {//射击行为
            while (true) {
                try {//让子弹休眠一下
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //根据方向来改变x,y坐标
                switch (direction) {
                    case 0://向上
                        y -= speed;
                        break;
                    case 1://向右
                        x += speed;
                        break;
                    case 2://向下
                        y += speed;
                        break;
                    case 3://向左
                        x -= speed;
                        break;
                }
                //这里用于调试,输入子弹坐标
                //System.out.println("子弹x =" + x + " y =" + y);
                //当子弹超出边界就销毁子弹
                //当子弹碰到敌人坦克时,也应该结束线程
                if (!(x > 0 && x < 1000 && y > 0 && y < 750 && isLive)) {
                    System.out.println("子弹消失");
                    isLive = false;
                    break;
                }
            }
        }
    }
    
  5. 爆炸效果 Boom

    public class Boom {
        int x, y;//炸弹的坐标
        int life = 9;//炸弹的生命周期
        boolean isLive = true;
    
        public Boom(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        //减少生命值
        public void lifeDown() {//配合图片爆炸效果
            if (life > 0) {
                life--;
            } else {
                isLive = false;
            }
        }
    }
    
  6. 面板显示 MyPanel

    //为了监听键盘事件,实现 KeyListener
    //为了让panel不停的重绘,实现 Runnable,当做一个线程使用
    public class MyPanel extends JPanel implements KeyListener, Runnable {
        //定义我的坦克
        MyTank myTank = null;
        //定义敌人坦克,放入到 Vector 
        Vector<EnemyTank> enemyTanks = new Vector<>();
    
        //定义一个存放Node对象的Vector,用于恢复敌人坦克的坐标和方向
        Vector<Node> nodes = new Vector<>();
    
        //定义一个Vector,用于存放炸弹
        //当子弹击中坦克时,就加入一个Boom对象booms
        Vector<Boom> booms = new Vector<>();
    
        int enemyTanksize = 3;
    
        //定义三张图片,用于显示爆炸效果
        Image image1 = null;
        Image image2 = null;
        Image image3 = null;
    
        public MyPanel(String key) {
            //1. 我们先判断记录文件是否存在
            //2. 如果存在,游戏正常执行,
            //    如果文件不存在,提示只能开启新游戏, key = "1"
            File file = new File(Recoder.getRecordFile());
            if (file.exists()) {
                //当我们游戏启动的时候进行数据恢复
                //然后用nodes接收相应数据
                nodes = Recoder.getNodesAndEnemyTankRec();
            } else {
                System.out.println("文件不存在,只能开启新的游戏");
                key = "1";
            }
    
            //将 MyPanel 对象的 enemyTanks 设置给 Recorder 的 enemyTanks
            Recoder.setEnemyTanks(enemyTanks);
    
            //初始化自己的坦克
            myTank = new MyTank(300, 600);
    
            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("选择有误");
            }
    
            //初始化图片
            image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_1.gif"));
            image2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_2.gif"));
            image3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_3.gif"));
    
            //这里播放指定的音乐
            new AePlayWave("src\\111.wav").start();
        }
    
        //编写方法,显示我方击毁敌方坦克的信息
        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);
        }
    
    
        @Override
        public void paint(Graphics g) {
            super.paint(g);
            //设置填充矩形,默认黑色
            g.fillRect(0, 0, 1000, 750);
    
            //调用showInfo()方法
            showInfo(g);
    
            myTank.setSpeed(5);
            //画出坦克-封装方法
            //自己的坦克
            //增加一个if判断,当自己不为空并且还存活的情况下才画坦克
            if (myTank != null && myTank.isLive) {
                drawTank(myTank.getX(), myTank.getY(), g, myTank.getDirection(), 1);
            }
            //画出自己的子弹(一颗)
    //        if (myTank.shot != null && myTank.shot.isLive == true) {
    //            g.draw3DRect(myTank.shot.x, myTank.shot.y, 5, 5, true);
    //        }
            //现在我们要画多颗子弹
            //将myTank的子弹shots遍历取出
            for (int i = 0; i < myTank.shots.size(); i++) {
                //取出子弹
                Shot shot = myTank.shots.get(i);
                if (shot != null && shot.isLive) {
                    g.draw3DRect(shot.x, shot.y, 5, 5, true);
                } else {//否则该shot对象无效,就从shots集合中拿掉
                    myTank.shots.remove(shot);
                    break;
                }
            }
    
            //如果booms集合中有对象,就画出
            for (int i = 0; i < booms.size(); i++) {
                //取出炸弹
                Boom boom = booms.get(i);
                //根据当前这个boom对象的life值画出对应的图片
                if (boom.life > 6) {
                    g.drawImage(image1, boom.x, boom.y, 80, 80, this);
                } else if (boom.life > 30) {
                    g.drawImage(image2, boom.x, boom.y, 80, 80, this);
                } else {
                    g.drawImage(image3, boom.x, boom.y, 80, 80, this);
                }
                //让炸弹的生命值减少
                boom.lifeDown();
                //如果boom.life为0,就从booms的集合中删除
                if (boom.life == 0) {
                    booms.remove(boom);
                }
            }
    
            //画出敌人的坦克,遍历Vector
            for (int i = 0; i < enemyTanks.size(); i++) {
                //从Vector取出坦克
                EnemyTank enemyTank = enemyTanks.get(i);
                //判断当前坦克是否还存活
                if (enemyTank.isLive) {//如果敌人的坦克是存活的,才画出该坦克
                    drawTank(enemyTank.getX(), enemyTank.getY(), g, enemyTank.getDirection(), 0);
    
                    //画出 enemyTank所有子弹
                    for (int j = 0; j < enemyTank.shots.size(); j++) {
                        //取出子弹
                        Shot shot = enemyTank.shots.get(j);
                        //绘制子弹
                        if (shot.isLive) {
                            g.draw3DRect(shot.x, shot.y, 5, 5, true);
                        } else {
                            //从 Vector 移除
                            enemyTank.shots.remove(shot);
                        }
                    }
                }
            }
        }
        //编写方法,画出坦克
    
        /**
         * @param x         坦克的左上角x坐标
         * @param y         坦克的左上角y坐标
         * @param g         画笔
         * @param direction 坦克的方向
         * @param type      坦克的类型
         */
        public void drawTank(int x, int y, Graphics g, int direction, int type) {
            switch (type) {
                case 0://敌人的坦克
                    g.setColor(Color.cyan);
                    break;
                case 1://我们的坦克
                    g.setColor(Color.yellow);
                    break;
            }
            //根据坦克的方向,来绘制坦克
            switch (direction) {//0向上 1向右 2向下 3向左
                case 0://默认方向向上
                    // 先画第一个矩形 大小 10*60
                    //坦克左边轮子
                    // 定点(x,y)
                    g.fill3DRect(x, y, 10, 60, false);
                    // 第二个矩形 大小 20*40
                    //坦克身体
                    // 定点(x+10,y+10)
                    g.fill3DRect(x + 10, y + 10, 20, 40, false);
                    // 第三个矩形 大小 10*60
                    //坦克右边轮子
                    // 定点(x+30,y)
                    g.fill3DRect(x + 30, y, 10, 60, false);
                    // 上面的圆盖子 大小 (20,20)
                    // 定点(x+10,y+20)
                    g.fillOval(x + 10, y + 20, 20, 20);
                    // 最后的炮管
                    // 定点1 (x+20,y)
                    // 定点2 (x+20,y+30)
                    g.drawLine(x + 20, y, x + 20, y + 30);
                    // 画出子弹
                    g.drawLine(x + 20, y, x + 20, y + 5);
                    break;
                case 1://默认方向向右
                    // 先画第一个矩形 大小 60*10
                    //坦克上边轮子
                    g.fill3DRect(x, y, 60, 10, false);
                    // 第二个矩形 大小 40*20
                    //坦克身体
                    g.fill3DRect(x + 10, y + 10, 40, 20, false);
                    // 第三个矩形 大小 10*60
                    //坦克下边轮子
                    g.fill3DRect(x, y + 30, 60, 10, false);
                    // 上面的圆盖子 大小 (20,20)
                    g.fillOval(x + 20, y + 10, 20, 20);
                    // 最后的炮管
                    g.drawLine(x + 60, y + 20, x + 30, y + 20);
                    // 画出子弹
                    g.drawLine(x + 60, y + 20, x + 55, y + 20);
                    break;
                case 2://默认方向向下
                    // 先画第一个矩形 大小 10*60
                    //坦克左边轮子
                    g.fill3DRect(x, y, 10, 60, false);
                    // 第二个矩形 大小 20*40
                    //坦克身体
                    g.fill3DRect(x + 10, y + 10, 20, 40, false);
                    // 第三个矩形 大小 10*60
                    //坦克右边轮子
                    g.fill3DRect(x + 30, y, 10, 60, false);
                    // 上面的圆盖子 大小 (20,20)
                    g.fillOval(x + 10, y + 20, 20, 20);
                    // 最后的炮管
                    g.drawLine(x + 20, y + 60, x + 20, y + 30);
                    // 画出子弹
                    g.drawLine(x + 20, y + 60, x + 20, y + 55);
                    break;
                case 3://默认方向向左
                    // 先画第一个矩形 大小 60*10
                    //坦克上边轮子
                    g.fill3DRect(x, y, 60, 10, false);
                    // 第二个矩形 大小 40*20
                    //坦克身体
                    g.fill3DRect(x + 10, y + 10, 40, 20, false);
                    // 第三个矩形 大小 10*60
                    //坦克下边轮子
                    g.fill3DRect(x, y + 30, 60, 10, false);
                    // 上面的圆盖子 大小 (20,20)
                    g.fillOval(x + 20, y + 10, 20, 20);
                    // 最后的炮管
                    g.drawLine(x, y + 20, x + 30, y + 20);
                    // 画出子弹
                    g.drawLine(x, y + 20, x + 5, y + 20);
                    break;
                default:
                    System.out.println("暂时不作处理");
            }
        }
    
        // 创建一个方法 hitMyTank
        // 判断敌人的子弹是否打中我们
        public void hitMyTank() {
            //遍历所有敌人坦克
            for (int i = 0; i < enemyTanks.size(); i++) {
                //取出敌人坦克
                EnemyTank enemyTank = enemyTanks.get(i);
                for (int j = 0; j < enemyTank.shots.size(); j++) {
                    //取出子弹
                    Shot shot = enemyTank.shots.get(j);
                    //判断shot是否击中我们的坦克
                    if (myTank.isLive && shot.isLive) {
                        hitTank(shot, myTank);
                    }
                }
            }
        }
    
        // 现我们的坦克可以发射多个子弹
        // 在判断我方子弹是否击中敌人坦克时,
        // 就需要把我们的子弹集合中所有的子弹都取出,
        // 和敌人的所有坦克进行判断
        public void hitEnemyTank() {
            //遍历我们的子弹
            for (int j = 0; j < myTank.shots.size(); j++) {
                Shot shot = myTank.shots.get(j);
                if (shot != null && shot.isLive) {//当前我的子弹还存活
                    for (int i = 0; i < enemyTanks.size(); i++) {
                        EnemyTank enemyTank = enemyTanks.get(i);
                        hitTank(shot, enemyTank);
                    }
                }
            }
        }
    
        // 编写方法,判断我方子弹是否击中敌人的坦克
        // 什么时候判断我方子弹是否击中敌人?
        // 在run方法里判断比较适合
    
        // 因为现在这方法用于判断我们的坦克跟敌人的坦克了
        // 所以这里把 EnemyTank enemyTank 改为 Tank tank
        //改用两者的父类 Tank 对象
        public void hitTank(Shot s, Tank tank) {
            //判断击中坦克
            switch (tank.getDirection()) {
                case 0://向上
                case 2://向下
                    //子弹的x坐标大于坦克的最左边x坐标
                    // 或者小于了坦克最右边x坐标(坦克宽40)
                    //子弹的y坐标大于坦克的最上边y坐标
                    // 或者小于了坦克最下边y坐标(坦克高60)
                    if (s.x > tank.getX() && s.x < tank.getX() + 40
                            && s.y > tank.getY() && s.y < tank.getY() + 60) {
                        //子弹消失
                        s.isLive = false;
                        //敌人的坦克消失
                        tank.isLive = false;
                        //当我们的子弹击中敌人坦克时,将enemyTank从Vector拿掉
                        enemyTanks.remove(tank);
    
                        //当我方击毁敌人坦克时,就对数据allEnemyTankNum++
                        //因为 tank 可以是 MyTank,也可以是 EnemyTank
                        //所以我们这里要进行一个判断
                        if (tank instanceof EnemyTank) {
                            Recoder.addAllEnemyTankNum();
                        }
    
                        //创建Boom对象,加入到booms集合
                        Boom boom = new Boom(tank.getX(), tank.getY());
                        booms.add(boom);
                        break;
                    }
    
                case 1://向右
                case 3://向左
                    //子弹的x坐标大于坦克的最左边x坐标
                    // 或者小于了坦克最右边x坐标(坦克宽60)
                    //子弹的y坐标大于坦克的最上边y坐标
                    // 或者小于了坦克最下边y坐标(坦克高40)
                    if (s.x > tank.getX() && s.x < tank.getX() + 60
                            && s.y > tank.getY() && s.y < tank.getY() + 40) {
                        //子弹消失
                        s.isLive = false;
                        //敌人的坦克消失
                        tank.isLive = false;
                        //当我们的子弹击中敌人坦克时,将enemyTank从Vector拿掉
                        enemyTanks.remove(tank);
    
                        //当我方击毁敌人坦克时,就对数据allEnemyTankNum++
                        //因为 tank 可以是 MyTank,也可以是 EnemyTank
                        //所以我们这里要进行一个判断
                        if (tank instanceof EnemyTank) {
                            Recoder.addAllEnemyTankNum();
                        }
    
                        //创建Boom对象,加入到booms集合
                        Boom boom = new Boom(tank.getX(), tank.getY());
                        booms.add(boom);
                        break;
                    }
            }
        }
    
    
        @Override
        public void keyTyped(KeyEvent e) {
    
        }
    
        //处理 wsad 按下的情况
        @Override
        public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() == KeyEvent.VK_W) {
                //改变坦克的方向
                myTank.setDirection(0);
                if (myTank.getY() > 0) {
                    myTank.moveUp();
                }
            } else if (e.getKeyCode() == KeyEvent.VK_S) {
                myTank.setDirection(2);
                if (myTank.getY() + 60 < 750) {
                    myTank.moveDown();
                }
            } else if (e.getKeyCode() == KeyEvent.VK_A) {
                myTank.setDirection(3);
                if (myTank.getX() > 0) {
                    myTank.moveLeft();
                }
            } else if (e.getKeyCode() == KeyEvent.VK_D) {
                myTank.setDirection(1);
                if (myTank.getX() + 60 < 1000) {
                    myTank.moveRight();
                }
            }
    
            //如果用户按下J键,就是发射子弹
            if (e.getKeyCode() == KeyEvent.VK_J) {
                //判断当前myTank子弹是否已经销毁 发射一颗子弹
    //            if (myTank.shot == null || !myTank.shot.isLive) {
    //                myTank.shotEnemyTank();//发射子弹
    //            }
                //判断当前myTank子弹是否已经销毁 发射duo颗子弹
                myTank.shotEnemyTank();
            }
            this.repaint();
        }
    
        @Override
        public void keyReleased(KeyEvent e) {
    
        }
    
        //让子弹不停的重绘
        @Override
        public void run() {
            while (true) {
                try {//每隔200毫秒,重绘
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //把下面注释的这段代码封装到上面的方法里面
                //判断是否击中了敌人的坦克
    //            if (myTank.shot != null && myTank.shot.isLive) {//当前我的子弹还存活
    //                for (int i = 0; i < enemyTanks.size(); i++) {
    //                    EnemyTank enemyTank = enemyTanks.get(i);
    //                    hitTank(myTank.shot, enemyTank);
    //                }
    //            }
                //判断敌人坦克是否击中我们
                hitMyTank();
                hitEnemyTank();
                this.repaint();
            }
        }
    }
    
  7. 用于记录相关的信息Recorder

    //为了监听键盘事件,实现 KeyListener
    //为了让panel不停的重绘,实现 Runnable,当做一个线程使用
    public class MyPanel extends JPanel implements KeyListener, Runnable {
        //定义我的坦克
        MyTank myTank = null;
        //定义敌人坦克,放入到 Vector 
        Vector<EnemyTank> enemyTanks = new Vector<>();
    
        //定义一个存放Node对象的Vector,用于恢复敌人坦克的坐标和方向
        Vector<Node> nodes = new Vector<>();
    
        //定义一个Vector,用于存放炸弹
        //当子弹击中坦克时,就加入一个Boom对象booms
        Vector<Boom> booms = new Vector<>();
    
        int enemyTanksize = 3;
    
        //定义三张图片,用于显示爆炸效果
        Image image1 = null;
        Image image2 = null;
        Image image3 = null;
    
        public MyPanel(String key) {
            //1. 我们先判断记录文件是否存在
            //2. 如果存在,游戏正常执行,
            //    如果文件不存在,提示只能开启新游戏, key = "1"
            File file = new File(Recoder.getRecordFile());
            if (file.exists()) {
                //当我们游戏启动的时候进行数据恢复
                //然后用nodes接收相应数据
                nodes = Recoder.getNodesAndEnemyTankRec();
            } else {
                System.out.println("文件不存在,只能开启新的游戏");
                key = "1";
            }
    
            //将 MyPanel 对象的 enemyTanks 设置给 Recorder 的 enemyTanks
            Recoder.setEnemyTanks(enemyTanks);
    
            //初始化自己的坦克
            myTank = new MyTank(300, 600);
    
            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("选择有误");
            }
    
            //初始化图片
            image1 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_1.gif"));
            image2 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_2.gif"));
            image3 = Toolkit.getDefaultToolkit().getImage(Panel.class.getResource("/bomb_3.gif"));
    
            //这里播放指定的音乐
            new AePlayWave("src\\111.wav").start();
        }
    
        //编写方法,显示我方击毁敌方坦克的信息
        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);
        }
    
    
        @Override
        public void paint(Graphics g) {
            super.paint(g);
            //设置填充矩形,默认黑色
            g.fillRect(0, 0, 1000, 750);
    
            //调用showInfo()方法
            showInfo(g);
    
            myTank.setSpeed(5);
            //画出坦克-封装方法
            //自己的坦克
            //增加一个if判断,当自己不为空并且还存活的情况下才画坦克
            if (myTank != null && myTank.isLive) {
                drawTank(myTank.getX(), myTank.getY(), g, myTank.getDirection(), 1);
            }
            //画出自己的子弹(一颗)
    //        if (myTank.shot != null && myTank.shot.isLive == true) {
    //            g.draw3DRect(myTank.shot.x, myTank.shot.y, 5, 5, true);
    //        }
            //现在我们要画多颗子弹
            //将myTank的子弹shots遍历取出
            for (int i = 0; i < myTank.shots.size(); i++) {
                //取出子弹
                Shot shot = myTank.shots.get(i);
                if (shot != null && shot.isLive) {
                    g.draw3DRect(shot.x, shot.y, 5, 5, true);
                } else {//否则该shot对象无效,就从shots集合中拿掉
                    myTank.shots.remove(shot);
                    break;
                }
            }
    
            //如果booms集合中有对象,就画出
            for (int i = 0; i < booms.size(); i++) {
                //取出炸弹
                Boom boom = booms.get(i);
                //根据当前这个boom对象的life值画出对应的图片
                if (boom.life > 6) {
                    g.drawImage(image1, boom.x, boom.y, 80, 80, this);
                } else if (boom.life > 30) {
                    g.drawImage(image2, boom.x, boom.y, 80, 80, this);
                } else {
                    g.drawImage(image3, boom.x, boom.y, 80, 80, this);
                }
                //让炸弹的生命值减少
                boom.lifeDown();
                //如果boom.life为0,就从booms的集合中删除
                if (boom.life == 0) {
                    booms.remove(boom);
                }
            }
    
            //画出敌人的坦克,遍历Vector
            for (int i = 0; i < enemyTanks.size(); i++) {
                //从Vector取出坦克
                EnemyTank enemyTank = enemyTanks.get(i);
                //判断当前坦克是否还存活
                if (enemyTank.isLive) {//如果敌人的坦克是存活的,才画出该坦克
                    drawTank(enemyTank.getX(), enemyTank.getY(), g, enemyTank.getDirection(), 0);
    
                    //画出 enemyTank所有子弹
                    for (int j = 0; j < enemyTank.shots.size(); j++) {
                        //取出子弹
                        Shot shot = enemyTank.shots.get(j);
                        //绘制子弹
                        if (shot.isLive) {
                            g.draw3DRect(shot.x, shot.y, 5, 5, true);
                        } else {
                            //从 Vector 移除
                            enemyTank.shots.remove(shot);
                        }
                    }
                }
            }
        }
        //编写方法,画出坦克
    
        /**
         * @param x         坦克的左上角x坐标
         * @param y         坦克的左上角y坐标
         * @param g         画笔
         * @param direction 坦克的方向
         * @param type      坦克的类型
         */
        public void drawTank(int x, int y, Graphics g, int direction, int type) {
            switch (type) {
                case 0://敌人的坦克
                    g.setColor(Color.cyan);
                    break;
                case 1://我们的坦克
                    g.setColor(Color.yellow);
                    break;
            }
            //根据坦克的方向,来绘制坦克
            switch (direction) {//0向上 1向右 2向下 3向左
                case 0://默认方向向上
                    // 先画第一个矩形 大小 10*60
                    //坦克左边轮子
                    // 定点(x,y)
                    g.fill3DRect(x, y, 10, 60, false);
                    // 第二个矩形 大小 20*40
                    //坦克身体
                    // 定点(x+10,y+10)
                    g.fill3DRect(x + 10, y + 10, 20, 40, false);
                    // 第三个矩形 大小 10*60
                    //坦克右边轮子
                    // 定点(x+30,y)
                    g.fill3DRect(x + 30, y, 10, 60, false);
                    // 上面的圆盖子 大小 (20,20)
                    // 定点(x+10,y+20)
                    g.fillOval(x + 10, y + 20, 20, 20);
                    // 最后的炮管
                    // 定点1 (x+20,y)
                    // 定点2 (x+20,y+30)
                    g.drawLine(x + 20, y, x + 20, y + 30);
                    // 画出子弹
                    g.drawLine(x + 20, y, x + 20, y + 5);
                    break;
                case 1://默认方向向右
                    // 先画第一个矩形 大小 60*10
                    //坦克上边轮子
                    g.fill3DRect(x, y, 60, 10, false);
                    // 第二个矩形 大小 40*20
                    //坦克身体
                    g.fill3DRect(x + 10, y + 10, 40, 20, false);
                    // 第三个矩形 大小 10*60
                    //坦克下边轮子
                    g.fill3DRect(x, y + 30, 60, 10, false);
                    // 上面的圆盖子 大小 (20,20)
                    g.fillOval(x + 20, y + 10, 20, 20);
                    // 最后的炮管
                    g.drawLine(x + 60, y + 20, x + 30, y + 20);
                    // 画出子弹
                    g.drawLine(x + 60, y + 20, x + 55, y + 20);
                    break;
                case 2://默认方向向下
                    // 先画第一个矩形 大小 10*60
                    //坦克左边轮子
                    g.fill3DRect(x, y, 10, 60, false);
                    // 第二个矩形 大小 20*40
                    //坦克身体
                    g.fill3DRect(x + 10, y + 10, 20, 40, false);
                    // 第三个矩形 大小 10*60
                    //坦克右边轮子
                    g.fill3DRect(x + 30, y, 10, 60, false);
                    // 上面的圆盖子 大小 (20,20)
                    g.fillOval(x + 10, y + 20, 20, 20);
                    // 最后的炮管
                    g.drawLine(x + 20, y + 60, x + 20, y + 30);
                    // 画出子弹
                    g.drawLine(x + 20, y + 60, x + 20, y + 55);
                    break;
                case 3://默认方向向左
                    // 先画第一个矩形 大小 60*10
                    //坦克上边轮子
                    g.fill3DRect(x, y, 60, 10, false);
                    // 第二个矩形 大小 40*20
                    //坦克身体
                    g.fill3DRect(x + 10, y + 10, 40, 20, false);
                    // 第三个矩形 大小 10*60
                    //坦克下边轮子
                    g.fill3DRect(x, y + 30, 60, 10, false);
                    // 上面的圆盖子 大小 (20,20)
                    g.fillOval(x + 20, y + 10, 20, 20);
                    // 最后的炮管
                    g.drawLine(x, y + 20, x + 30, y + 20);
                    // 画出子弹
                    g.drawLine(x, y + 20, x + 5, y + 20);
                    break;
                default:
                    System.out.println("暂时不作处理");
            }
        }
    
        // 创建一个方法 hitMyTank
        // 判断敌人的子弹是否打中我们
        public void hitMyTank() {
            //遍历所有敌人坦克
            for (int i = 0; i < enemyTanks.size(); i++) {
                //取出敌人坦克
                EnemyTank enemyTank = enemyTanks.get(i);
                for (int j = 0; j < enemyTank.shots.size(); j++) {
                    //取出子弹
                    Shot shot = enemyTank.shots.get(j);
                    //判断shot是否击中我们的坦克
                    if (myTank.isLive && shot.isLive) {
                        hitTank(shot, myTank);
                    }
                }
            }
        }
    
        // 现我们的坦克可以发射多个子弹
        // 在判断我方子弹是否击中敌人坦克时,
        // 就需要把我们的子弹集合中所有的子弹都取出,
        // 和敌人的所有坦克进行判断
        public void hitEnemyTank() {
            //遍历我们的子弹
            for (int j = 0; j < myTank.shots.size(); j++) {
                Shot shot = myTank.shots.get(j);
                if (shot != null && shot.isLive) {//当前我的子弹还存活
                    for (int i = 0; i < enemyTanks.size(); i++) {
                        EnemyTank enemyTank = enemyTanks.get(i);
                        hitTank(shot, enemyTank);
                    }
                }
            }
        }
    
        // 编写方法,判断我方子弹是否击中敌人的坦克
        // 什么时候判断我方子弹是否击中敌人?
        // 在run方法里判断比较适合
    
        // 因为现在这方法用于判断我们的坦克跟敌人的坦克了
        // 所以这里把 EnemyTank enemyTank 改为 Tank tank
        //改用两者的父类 Tank 对象
        public void hitTank(Shot s, Tank tank) {
            //判断击中坦克
            switch (tank.getDirection()) {
                case 0://向上
                case 2://向下
                    //子弹的x坐标大于坦克的最左边x坐标
                    // 或者小于了坦克最右边x坐标(坦克宽40)
                    //子弹的y坐标大于坦克的最上边y坐标
                    // 或者小于了坦克最下边y坐标(坦克高60)
                    if (s.x > tank.getX() && s.x < tank.getX() + 40
                            && s.y > tank.getY() && s.y < tank.getY() + 60) {
                        //子弹消失
                        s.isLive = false;
                        //敌人的坦克消失
                        tank.isLive = false;
                        //当我们的子弹击中敌人坦克时,将enemyTank从Vector拿掉
                        enemyTanks.remove(tank);
    
                        //当我方击毁敌人坦克时,就对数据allEnemyTankNum++
                        //因为 tank 可以是 MyTank,也可以是 EnemyTank
                        //所以我们这里要进行一个判断
                        if (tank instanceof EnemyTank) {
                            Recoder.addAllEnemyTankNum();
                        }
    
                        //创建Boom对象,加入到booms集合
                        Boom boom = new Boom(tank.getX(), tank.getY());
                        booms.add(boom);
                        break;
                    }
    
                case 1://向右
                case 3://向左
                    //子弹的x坐标大于坦克的最左边x坐标
                    // 或者小于了坦克最右边x坐标(坦克宽60)
                    //子弹的y坐标大于坦克的最上边y坐标
                    // 或者小于了坦克最下边y坐标(坦克高40)
                    if (s.x > tank.getX() && s.x < tank.getX() + 60
                            && s.y > tank.getY() && s.y < tank.getY() + 40) {
                        //子弹消失
                        s.isLive = false;
                        //敌人的坦克消失
                        tank.isLive = false;
                        //当我们的子弹击中敌人坦克时,将enemyTank从Vector拿掉
                        enemyTanks.remove(tank);
    
                        //当我方击毁敌人坦克时,就对数据allEnemyTankNum++
                        //因为 tank 可以是 MyTank,也可以是 EnemyTank
                        //所以我们这里要进行一个判断
                        if (tank instanceof EnemyTank) {
                            Recoder.addAllEnemyTankNum();
                        }
    
                        //创建Boom对象,加入到booms集合
                        Boom boom = new Boom(tank.getX(), tank.getY());
                        booms.add(boom);
                        break;
                    }
            }
        }
    
    
        @Override
        public void keyTyped(KeyEvent e) {
    
        }
    
        //处理 wsad 按下的情况
        @Override
        public void keyPressed(KeyEvent e) {
            if (e.getKeyCode() == KeyEvent.VK_W) {
                //改变坦克的方向
                myTank.setDirection(0);
                if (myTank.getY() > 0) {
                    myTank.moveUp();
                }
            } else if (e.getKeyCode() == KeyEvent.VK_S) {
                myTank.setDirection(2);
                if (myTank.getY() + 60 < 750) {
                    myTank.moveDown();
                }
            } else if (e.getKeyCode() == KeyEvent.VK_A) {
                myTank.setDirection(3);
                if (myTank.getX() > 0) {
                    myTank.moveLeft();
                }
            } else if (e.getKeyCode() == KeyEvent.VK_D) {
                myTank.setDirection(1);
                if (myTank.getX() + 60 < 1000) {
                    myTank.moveRight();
                }
            }
    
            //如果用户按下J键,就是发射子弹
            if (e.getKeyCode() == KeyEvent.VK_J) {
                //判断当前myTank子弹是否已经销毁 发射一颗子弹
    //            if (myTank.shot == null || !myTank.shot.isLive) {
    //                myTank.shotEnemyTank();//发射子弹
    //            }
                //判断当前myTank子弹是否已经销毁 发射duo颗子弹
                myTank.shotEnemyTank();
            }
            this.repaint();
        }
    
        @Override
        public void keyReleased(KeyEvent e) {
    
        }
    
        //让子弹不停的重绘
        @Override
        public void run() {
            while (true) {
                try {//每隔200毫秒,重绘
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                //把下面注释的这段代码封装到上面的方法里面
                //判断是否击中了敌人的坦克
    //            if (myTank.shot != null && myTank.shot.isLive) {//当前我的子弹还存活
    //                for (int i = 0; i < enemyTanks.size(); i++) {
    //                    EnemyTank enemyTank = enemyTanks.get(i);
    //                    hitTank(myTank.shot, enemyTank);
    //                }
    //            }
                //判断敌人坦克是否击中我们
                hitMyTank();
                hitEnemyTank();
                this.repaint();
            }
        }
    }
    
  8. 保存敌人存活坦克的信息Node

    //一个Node对象,表示一个敌人坦克的信息
    public class Node {
        private int x;
        private int y;
        private int direction;
    
        public Node(int x, int y, int direction) {
            this.x = x;
            this.y = y;
            this.direction = direction;
        }
    
        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;
        }
    
        public int getDirection() {
            return direction;
        }
    
        public void setDirection(int direction) {
            this.direction = direction;
        }
    }
    
  9. 播放音乐的类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();
            }
    
        }
    }
    
  10. 主界面MyTankGame06

    public class MyTankGame06 extends JFrame {
        //定义 MyPanel
        MyPanel mp = null;
        //用户输入界面
        static Scanner scanner = new Scanner(System.in);
    
        public static void main(String[] args) {
    
            MyTankGame06 myTankGame01 = new MyTankGame06();
        }
    
        public MyTankGame06() {
            //供用户选择:开始新游戏还是继续上局
            System.out.println("请输入选择:1:新游戏 2:继续上局");
            String key = scanner.next();
            //初始化
            mp = new MyPanel(key);
            //将mp放入到Thread并启动
            Thread thread = new Thread(mp);
            thread.start();
            //面板(游戏的绘图区域)
            this.add(mp);
            //面板大小
            this.setSize(1300, 750);
            //添加键盘监听
            this.addKeyListener(mp);
            //当点击窗口的 × , 程序完全退出
            this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            //设置显示
            this.setVisible(true);
    
            //在 Jframe 中增加响应关闭窗口的处理
            this.addWindowListener(new WindowAdapter() {
                @Override
                public void windowClosing(WindowEvent e) {
                    Recoder.keepRecord();
                    System.exit(0);
                }
            });
        }
    }
    
  11. 这样一个简单版本的坦克大战就生成了。

05-22 19:17