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

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() {@Overridepublic 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保存多个shotVector<Shot> shots = new Vector<>();//1. Vector<EnemyTank> 在 MyPanelVector<EnemyTank> enemyTanks = new Vector<>();//这里提供一个方法,可以将 MyPanel 的成员 Vector<EnemyTank> enemyTanks = new Vector<>();//设置到 EnemyTank 的成员 enemyTankspublic 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);}@Overridepublic 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;}@Overridepublic 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对象boomsVector<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 的 enemyTanksRecoder.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);}@Overridepublic 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);}}//画出敌人的坦克,遍历Vectorfor (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;}}}@Overridepublic void keyTyped(KeyEvent e) {}//处理 wsad 按下的情况@Overridepublic 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();}@Overridepublic void keyReleased(KeyEvent e) {}//让子弹不停的重绘@Overridepublic 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对象boomsVector<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 的 enemyTanksRecoder.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);}@Overridepublic 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);}}//画出敌人的坦克,遍历Vectorfor (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;}}}@Overridepublic void keyTyped(KeyEvent e) {}//处理 wsad 按下的情况@Overridepublic 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();}@Overridepublic void keyReleased(KeyEvent e) {}//让子弹不停的重绘@Overridepublic 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 {//定义 MyPanelMyPanel 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() {@Overridepublic void windowClosing(WindowEvent e) {Recoder.keepRecord();System.exit(0);}});}
    }
    
  11. 这样一个简单版本的坦克大战就生成了。

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

  1. java怎么给坦克上图片_Java坦克大战 (七) 之图片版

    在此小易将坦克大战这个项目分为几个版本,以此对J2SE的知识进行回顾和总结,希望这样也能给刚学完J2SE的小伙伴们一点启示! 坦克大战效果图: 坦克大战V0.7图片版实现功能: 1.将方向定义为一个E ...

  2. Java坦克大战 (七) 之图片版

    本文来自:小易博客专栏.转载请注明出处:http://blog.csdn.net/oldinaction 在此小易将坦克大战这个项目分为几个版本,以此对J2SE的知识进行回顾和总结,希望这样也能给刚学 ...

  3. 经典坦克大战再现(三)

    今日效果展示 不知不觉就到第三(四)天了今天的进程还算顺利主要功能没怎么增加,主要是美化了一下界面,下面是效果图 由于这次GIF图片比较大上传不了所以我就放一张截图大家感受一下. 下面是有爆炸效果和音 ...

  4. Java学习总结之坦克大战项目(完结版)

    接上篇 V1.0:在这个版本中呢,我们要让敌方坦克动起来,不仅要移动,还要打出炮弹,怎么实现呢?其实很简单,在RobotTank类中添加一个随机数产生器就ok了.用nextInt ( n ) 方法可以 ...

  5. Java基础 - 坦克大战(第三章,线程基础与线程使用)

    文章目录 本章内容 - 多线程处理 绘制敌方坦克 线程基础 线程相关概念 程序(program) 进程 什么是线程 其他相关概念 单线程 多线程 并发 并行 获取当前电脑处理器(cpu)个数 Java ...

  6. java用drawline画血条,Java小项目之坦克大战单机1.0版

    单机1.0版包括6个class文件: TankClient.java : 主要执行部分,项目的大管家 Tank.java : 实现Tank类,模拟坦克的运动 Missile.java : 实现Miss ...

  7. 坦克大战-C语言-详注版

    代码地址如下: http://www.demodashi.com/demo/14259.html 坦克大战-C语言-详注版 概述 本文详述了C语言版坦克大战游戏的原理以及实现方法,对游戏代码进行了详细 ...

  8. Java坦克大战(四)

    这是我的坦克游戏大战的最后一版,里面添加很多新的功能.这个坦克大战的有很多不足之处,但是对于初学者来说依然是一个很好的练习项目,从中我们可以学习Java基础知识,将知识与项目结合,学习面向对象编程思想 ...

  9. 坦克大战2.0,3.0,4.0版本

    1.坦克大战 0.3 在坦克大战游戏(0.2版)基础上添加如下功能:当玩家按一下j键,就发射一颗子弹. 编写Shot类 package com.yt.tankgame03;/*** 射击子弹*/ pu ...

最新文章

  1. SpringBoot 用RestTemplate 优雅的发送HTTP请求,注意需要@Autowired注入后才能用
  2. 在 Linux 下忘记 mysql root 密码的解决方法
  3. SpringOne 2017第二日:Juergen Hoeller的演讲
  4. php 添加工信部链接,常见程序(wordpress,dede,discuz)网页下方添加备案号,添加链接跳转到工信部...
  5. 抽象工厂模式:实现ASP.NET访问不同数据库
  6. 掌握XML系列(四)---创建格式良好的XML文档
  7. js字符串与数组的处理
  8. oracle 12c 多线程,Oracle 12c(12.1)中性能优化功能增强之通过参数THREADED_EXECTION使用多线程模型...
  9. tcp http socket
  10. 软件公司管理基本原则
  11. Windows 新漏洞可被用于强制服务器以攻击者身份认证,官方缓解措施已发布
  12. 2017-2018-2 1723《程序设计与数据结构》第八周作业 实验二 第一周结对编程 总结...
  13. iOS多线程:『GCD』详尽总结
  14. 《图解设计模式》读书笔记
  15. 一步步破解app协议第二步(审计代码)
  16. 内存测试(ubuntu)
  17. P2135 方块消除
  18. 【JAVA】8.5.1内部购物券
  19. scanf库函数的返回值
  20. 【VBA研究】如何将单元格数据赋给数组

热门文章

  1. 周末python培训机构
  2. 未能注册模块 HSIDCandRead.ocx HRESULT -2147010895
  3. SpringMVC是如何消灭web.xml后还能加载到DispatchServlet的流程
  4. CryptoHack-wp(一)
  5. 高效便捷的团队协作的软件
  6. mediainfo使用
  7. 相似性度量方法(欧式距离等各种距离)
  8. Flash图片的内存计算公式
  9. 993 二叉树的堂兄弟节点
  10. JavaSE-java.io.File类