java坦克大战(2.0)

坦克大战(2.0)

在前面的坦克大战1.0已经完成了移动 绘制我方/敌方坦克。
接下来会运用到前面学习的多线程的基础,来实现发射子弹的效果

发射子弹

想要坦克发射子弹,可以用以下思路

按J发射子弹
将子弹变成一个线程,让子弹的x,y值不停变换,画板重绘,当子弹到达边界时就将线程销毁

步骤如下:

子弹类

  1. 创建子弹类shoot,定义对应的x,y,发射的方向,移动的速度,实现Runnable接口
  2. 在run方法中根据坦克的朝向,改变子弹的x或者y,且当子弹达到边界时就销毁

子弹类(线程)定义好了自然要启动,而子弹是从坦克发送出来的,所以启动线程就在坦克类中定义

坦克类

  1. 定义一个子弹类的属性,但先不赋值,定义一个发射子弹的方法fire(),用于确定子弹发射的初始位置,根据坦克的位置创建不同的子弹类的实例
  2. 创建完子弹后,启动子弹线程

监听器

  1. 在按下键盘做出操作的方法,新建一个当按下J的时候就调用fire方法

画板类绘制子弹
1.因为子弹类是只有在按J之后才创建的,所以不能直接在paint绘制,而是需要加if判断子弹类不是空,且子弹线程是正在运行的,才绘制
2.又因为paint现在是只有在移动的时候才会重新绘制,但是子弹要求是一直在动的,所以需要将MyPanel画板类变成一个线程,run方法中一直不断重绘,才能让子弹动起来,启动放在画框类

//shoot类
public class shoot implements Runnable{
    int x;//子弹的x坐标
    int y;//子弹的y坐标
    int direct;//子弹的方向
    int speed = 2;//子弹的速度
    boolean lief = true;//子弹线程的状态
    public shoot(int x, int y, int direct) {
        this.x = x;
        this.y = y;
        this.direct = direct;
    }

    @Override
    public void run() {
        while (lief){
            //因为要让子弹移动的别太快,所以休眠一会
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //根据方向,开始移动子弹
            switch (direct){
                case 0://当坦克朝上时
                    y-=speed;
                    break;
                case 1://当坦克朝右时
                    x+=speed;
                    break;
                case 2://当坦克朝下时
                    y+=speed;
                    break;
                case 3://当坦克朝左边时
                    x-=speed;
                    break;
            }
            //当子弹到达边界时
            if (!(x<500&&x>0 && y<600&& y>0)){
                lief = false;
                break;
            }
        }
    }
}
//Tank类
public class Tank {
    int x;
    int y;
    int direct;
    public Tank(int x, int y, int direct) {
        this.x = x;
        this.y = y;
        this.direct = direct;
    }
    shoot shoot;//定义一个子弹属性
    public void fire(){
        switch (direct){//根据此时坦克的朝向创建对应的子弹发送初始位置
            case 0://当坦克朝上时
                System.out.println("上");
                shoot = new shoot(x+20,y-6,direct);
                break;
            case 1://当坦克朝右时
                shoot = new shoot(x+60,y+24,direct);
                break;
            case 2://当坦克朝下时
                System.out.println("下");
                shoot = new shoot(x+21,y+60,direct);
                break;
            case 3://当坦克朝左边时
                System.out.println("左");
                shoot = new shoot(x-8,y+23,direct);
                break;
        }
        //创建完子弹,启动子弹
        new Thread(shoot).start();
    }
}
//MyPanel类
public class MyPanel extends Panel implements KeyListener,Runnable{
    int speek = 1;
    Tank tank;
    Vector<EnemyTanks> enemyTanks = new Vector<>();
    public MyPanel(){
        tank = new Tank(20,460,0);
        for (int i = 0; i < 3; i++) {
            EnemyTanks enemyTank = new EnemyTanks(100*(i+1),0,2);
            enemyTanks.add(enemyTank);
        }
    }
   @Override
    public void paint(Graphics g) {
        super.paint(g);
       System.out.println("调用paint");
       g.fillRect(0,0,500,600);
       //画我方坦克
       drawTank(tank.x,tank.y,g,tank.direct,0);
       for (int i = 0; i < enemyTanks.size(); i++) {
           EnemyTanks e = enemyTanks.get(i);
           //画敌方坦克
           drawTank(e.x,e.y,g,e.direct,1);
       }
       //画子弹
       if (tank.shoot!= null && tank.shoot.lief!=false){
           g.setColor(Color.PINK);
           g.drawRect(tank.shoot.x,tank.shoot.y,3,3);
       }
    }
    /**
    @param x 坦克的起始横坐标
    @param y 坦克的起始纵坐标
    @param g 画笔
    @param direct 根据方向绘制不同朝向的坦克
    @param type  根据0或者1 绘制不同颜色的坦克
    */
    public void drawTank(int x,int y,Graphics g,int direct,int type){
        switch (type){
            case 0:
                g.setColor(Color.PINK);//自己的坦克就粉色
                break;
            case 1:
                g.setColor(Color.RED);//敌人的坦克就红色
                break;
        }
            switch (direct){//0上,1右,2下,3左
                case 0://0表示绘制方向朝上的坦克
                    g.fill3DRect(x,y,10,50,false);
                    g.fill3DRect(x+10,y+5,25,40,false);
                    g.fill3DRect(x+35,y,10,50,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+22,y-5,x+22,y+25);
                    break;
                case 1://1表示绘制方向朝右的坦克
                    g.fill3DRect(x,y+7,50,10,false);
                    g.fill3DRect(x+5,y+14,40,25,false);
                    g.fill3DRect(x,y+37,50,10,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+25,y+25,x+58,y+25);
                    break;
                case 2://2表示绘制方向朝下的坦克
                    g.fill3DRect(x,y,10,50,false);
                    g.fill3DRect(x+10,y+5,25,40,false);
                    g.fill3DRect(x+35,y,10,50,false);
                    g.fillOval(x+10,y+12,25,25);
                    g.drawLine(x+22,y+55,x+22,y+25);
                    break;
                case 3://3表示绘制方向朝左的坦克
                    g.fill3DRect(x,y+7,50,10,false);
                    g.fill3DRect(x+5,y+14,40,25,false);
                    g.fill3DRect(x,y+37,50,10,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+25,y+25,x-8,y+25);
                    break;
                default://其他情况暂不考虑
                    break;
            }
    }

    @Override //字符输出时,该方法触发
    public void keyTyped(KeyEvent e) {

    }

    @Override//有按键按下时,该方法触发
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_W){//前进
            moveUp();
            tank.direct = 0;
        }else if(e.getKeyCode() == KeyEvent.VK_S){
            moveDown();
            tank.direct = 2;
        }else if(e.getKeyCode() == KeyEvent.VK_A){
            moveLeft();
            tank.direct = 3;
        }
        else if(e.getKeyCode() == KeyEvent.VK_D){
            moveRight();
            tank.direct = 1;
        }else if(e.getKeyCode() == 32){
            System.out.println("氮气加速~");
            speek +=5;
        }else{
            System.out.println(e.getKeyCode());
        }
        if (e.getKeyCode() == KeyEvent.VK_J){
            tank.fire();
        }
        repaint();
    }

    @Override//有按键松开时,该方法触发
    public void keyReleased(KeyEvent e) {

    }
    public void moveUp(){
        tank.y-=speek;
    }
    public void moveLeft(){
        tank.x-=speek;
    }
    public void moveRight(){
        tank.x+=speek;
    }
    public void moveDown(){
        tank.y+=speek;
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.repaint();
        }
    }
}
//Window类
public class Window extends JFrame {
    MyPanel m = null;
    public Window(){
        m  = new MyPanel();
        this.add(m);
        new Thread(m).start();
        this.setSize(500,600);
        this.addKeyListener(m);//让JFram监听m的事件
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }

    public static void main(String[] args) {
        new Window();
    }
}

让敌方坦克发射子弹

思路在EnemyTanks类使用Vector类创建一个集合,用于存放子弹
然后在MyPanel类初始化敌方坦克的敌方,每创建一个敌方坦克就初始化一个子弹对象,同时启动这个子弹
接着到绘制敌方坦克的敌方,每绘制一个敌人坦克,就绘制一个敌方坦克的子弹

//MyPanel
public class MyPanel extends Panel implements KeyListener,Runnable{
    int speek = 1;
    Tank tank;
    Vector<EnemyTanks> enemyTanks = new Vector<>();
    public MyPanel(){
        //初始化我方坦克
        tank = new Tank(20,460,0);
        //初始化敌方坦克
        for (int i = 0; i < 3; i++) {
            EnemyTanks enemyTank = new EnemyTanks(100*(i+1),0,2);
            //初始化敌方坦克子弹
            shoot shoot = new shoot(enemyTank.x,enemyTank.y,enemyTank.direct);
            //将子弹放入敌方坦克的shoots集合进行管理
            enemyTank.shoots.add(shoot);
            //启动敌方坦克的子弹
            new Thread(shoot).start();
            enemyTanks.add(enemyTank);
        }
    }
   @Override
    public void paint(Graphics g) {
        super.paint(g);
       System.out.println("调用paint");
       g.fillRect(0,0,500,600);
       //画我方坦克
       drawTank(tank.x,tank.y,g,tank.direct,0);
       //画敌方坦克
       for (int i = 0; i < enemyTanks.size(); i++) {
           EnemyTanks e = enemyTanks.get(i);
           drawTank(e.x,e.y,g,e.direct,1);
           //画出敌方坦克子弹
           for (int j = 0; j < e.shoots.size(); j++) {
               shoot s = e.shoots.get(j);
               if (s.lief){
                   g.setColor(Color.RED);
                   g.drawRect(s.x+20,s.y+60,3,3);
               }else {
                   e.shoots.remove(s);
               }
           }
       }
       //画子弹
       if (tank.shoot!= null && tank.shoot.lief!=false){
           g.setColor(Color.PINK);
           g.drawRect(tank.shoot.x,tank.shoot.y,3,3);
       }
    }
    /**
    @param x 坦克的起始横坐标
    @param y 坦克的起始纵坐标
    @param g 画笔
    @param direct 根据方向绘制不同朝向的坦克
    @param type  根据0或者1 绘制不同颜色的坦克
    */
    public void drawTank(int x,int y,Graphics g,int direct,int type){
        switch (type){
            case 0:
                g.setColor(Color.PINK);//自己的坦克就粉色
                break;
            case 1:
                g.setColor(Color.RED);//敌人的坦克就红色
                break;
        }
            switch (direct){//0上,1右,2下,3左
                case 0://0表示绘制方向朝上的坦克
                    g.fill3DRect(x,y,10,50,false);
                    g.fill3DRect(x+10,y+5,25,40,false);
                    g.fill3DRect(x+35,y,10,50,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+22,y-5,x+22,y+25);
                    break;
                case 1://1表示绘制方向朝右的坦克
                    g.fill3DRect(x,y+7,50,10,false);
                    g.fill3DRect(x+5,y+14,40,25,false);
                    g.fill3DRect(x,y+37,50,10,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+25,y+25,x+58,y+25);
                    break;
                case 2://2表示绘制方向朝下的坦克
                    g.fill3DRect(x,y,10,50,false);
                    g.fill3DRect(x+10,y+5,25,40,false);
                    g.fill3DRect(x+35,y,10,50,false);
                    g.fillOval(x+10,y+12,25,25);
                    g.drawLine(x+22,y+55,x+22,y+25);
                    break;
                case 3://3表示绘制方向朝左的坦克
                    g.fill3DRect(x,y+7,50,10,false);
                    g.fill3DRect(x+5,y+14,40,25,false);
                    g.fill3DRect(x,y+37,50,10,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+25,y+25,x-8,y+25);
                    break;
                default://其他情况暂不考虑
                    break;
            }
    }

    @Override //字符输出时,该方法触发
    public void keyTyped(KeyEvent e) {

    }

    @Override//有按键按下时,该方法触发
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_W){//前进
            moveUp();
            tank.direct = 0;
        }else if(e.getKeyCode() == KeyEvent.VK_S){
            moveDown();
            tank.direct = 2;
        }else if(e.getKeyCode() == KeyEvent.VK_A){
            moveLeft();
            tank.direct = 3;
        }
        else if(e.getKeyCode() == KeyEvent.VK_D){
            moveRight();
            tank.direct = 1;
        }else if(e.getKeyCode() == 32){
            System.out.println("氮气加速~");
            speek +=5;
        }else{
            System.out.println(e.getKeyCode());
        }
        if (e.getKeyCode() == KeyEvent.VK_J){
            tank.fire();
        }
        repaint();
    }

    @Override//有按键松开时,该方法触发
    public void keyReleased(KeyEvent e) {

    }
    public void moveUp(){
        tank.y-=speek;
    }
    public void moveLeft(){
        tank.x-=speek;
    }
    public void moveRight(){
        tank.x+=speek;
    }
    public void moveDown(){
        tank.y+=speek;
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.repaint();
        }
    }
}

当子弹击中敌方坦克,敌人就消失

判断子弹是否击中坦克,肯定是看子弹有没有进入到坦克的x和y的范围内。
根据这个思路可以想到,坦克几乎是有两种形态,上下 和 左右,这两类坦克范围的x和y是有不同的,所以根据这个特性,写出两个x和y的范围,加以判断即可。是否击中可以判断了,让被被击中坦克消失直接将该坦克从集合中删除即可。

根据上面的思路,可以将这个思路封装到一个方法中,再加上一个条件,先在敌人坦克类加一个boolean属性Live。如果子弹击中敌人坦克,就把敌人坦克的live改成false,同时也把打出的子弹改成false。

那么由于我们不知道什么时候子弹会击中敌人的坦克,所以需要在MyPanel类的run方法中调用此方法,且我们不知道到底会击中哪个坦克,所以需要便利敌人坦克的集合。

根据上面的判断,当我方子弹击中敌人坦克时,敌人的坦克的live会变成false。如果这时在MyPanel类绘制敌人坦克的时候,加一个判断,只有当敌人坦克的live是true的时候才进行绘制。那么一旦子弹击中敌人坦克,在下一次的重绘中就不会绘制被击中的坦克,以此就可以实现敌人坦克消失的效果。


//MyPanel类
public class MyPanel extends Panel implements KeyListener,Runnable{
    int speek = 1;
    Tank tank;
    Vector<EnemyTanks> enemyTanks = new Vector<>();
    public MyPanel(){
        //初始化我方坦克
        tank = new Tank(20,460,0);
        //初始化敌方坦克
        for (int i = 0; i < 3; i++) {
            EnemyTanks enemyTank = new EnemyTanks(100*(i+1),0,2);
            //初始化敌方坦克子弹
            shoot shoot = new shoot(enemyTank.x,enemyTank.y,enemyTank.direct);
            //将子弹放入敌方坦克的shoots集合进行管理
            enemyTank.shoots.add(shoot);
            //启动敌方坦克的子弹
            new Thread(shoot).start();
            enemyTanks.add(enemyTank);
        }
    }
   @Override
    public void paint(Graphics g) {
        super.paint(g);
       System.out.println("调用paint");
       g.fillRect(0,0,500,600);
       //画我方坦克
       drawTank(tank.x,tank.y,g,tank.direct,0);
       //画敌方坦克
       for (int i = 0; i < enemyTanks.size(); i++) {
           EnemyTanks e = enemyTanks.get(i);
           if (e.live) {//如果敌方坦克的live不是false才进行绘制
               drawTank(e.x, e.y, g, e.direct, 1);
               //画出敌方坦克子弹
               for (int j = 0; j < e.shoots.size(); j++) {
                   shoot s = e.shoots.get(j);
                   if (s.lief) {
                       g.setColor(Color.RED);
                       g.drawRect(s.x + 20, s.y + 60, 3, 3);
                   } else {
                       e.shoots.remove(s);
                   }
               }
           }
       }
       //画子弹
       if (tank.shoot!= null && tank.shoot.lief!=false){
           g.setColor(Color.PINK);
           g.drawRect(tank.shoot.x,tank.shoot.y,3,3);
       }
    }
    /**
    @param x 坦克的起始横坐标
    @param y 坦克的起始纵坐标
    @param g 画笔
    @param direct 根据方向绘制不同朝向的坦克
    @param type  根据0或者1 绘制不同颜色的坦克
    */
    public void drawTank(int x,int y,Graphics g,int direct,int type){
        switch (type){
            case 0:
                g.setColor(Color.PINK);//自己的坦克就粉色
                break;
            case 1:
                g.setColor(Color.RED);//敌人的坦克就红色
                break;
        }
            switch (direct){//0上,1右,2下,3左
                case 0://0表示绘制方向朝上的坦克
                    g.fill3DRect(x,y,10,50,false);
                    g.fill3DRect(x+10,y+5,25,40,false);
                    g.fill3DRect(x+35,y,10,50,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+22,y-5,x+22,y+25);
                    break;
                case 1://1表示绘制方向朝右的坦克
                    g.fill3DRect(x,y+7,50,10,false);
                    g.fill3DRect(x+5,y+14,40,25,false);
                    g.fill3DRect(x,y+37,50,10,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+25,y+25,x+58,y+25);
                    break;
                case 2://2表示绘制方向朝下的坦克
                    g.fill3DRect(x,y,10,50,false);
                    g.fill3DRect(x+10,y+5,25,40,false);
                    g.fill3DRect(x+35,y,10,50,false);
                    g.fillOval(x+10,y+12,25,25);
                    g.drawLine(x+22,y+55,x+22,y+25);
                    break;
                case 3://3表示绘制方向朝左的坦克
                    g.fill3DRect(x,y+7,50,10,false);
                    g.fill3DRect(x+5,y+14,40,25,false);
                    g.fill3DRect(x,y+37,50,10,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+25,y+25,x-8,y+25);
                    break;
                default://其他情况暂不考虑
                    break;
            }
    }

    @Override //字符输出时,该方法触发
    public void keyTyped(KeyEvent e) {

    }

    @Override//有按键按下时,该方法触发
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_W){//前进
            moveUp();
            tank.direct = 0;
        }else if(e.getKeyCode() == KeyEvent.VK_S){
            moveDown();
            tank.direct = 2;
        }else if(e.getKeyCode() == KeyEvent.VK_A){
            moveLeft();
            tank.direct = 3;
        }
        else if(e.getKeyCode() == KeyEvent.VK_D){
            moveRight();
            tank.direct = 1;
        }else if(e.getKeyCode() == 32){
            System.out.println("氮气加速~");
            speek +=5;
        }else{
            System.out.println(e.getKeyCode());
        }
        if (e.getKeyCode() == KeyEvent.VK_J){
            tank.fire();
        }
        repaint();
    }

    @Override//有按键松开时,该方法触发
    public void keyReleased(KeyEvent e) {

    }
    public void moveUp(){
        tank.y-=speek;
    }
    public void moveLeft(){
        tank.x-=speek;
    }
    public void moveRight(){
        tank.x+=speek;
    }
    public void moveDown(){
        tank.y+=speek;
    }
    //此方法判断我方坦克子弹是否击中敌人坦克
    public static void himEnemyTank(shoot s , EnemyTanks e ){
        //上下和左右的x,y是相同的,所以写两个判断即可

        switch (e.direct){
            case 0:
            case 2:
                if (s.x>e.x&&s.x< e.x+40
                && s.y>e.y&&s.y<e.y+60){//如果此条件成立就代表,地方在朝上或者朝下的方向被击中了
                    e.live = false;
                    s.lief = false;
                }
                break;
            case 1:
            case 3:
                if (s.x>e.x&&s.x< e.x+60
                        && s.y>e.y&&s.y<e.y+40){//如果此条件成立就代表,地方在朝左或者朝右的方向被击中了
                    e.live = false;
                    s.lief = false;
                }
        }
    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (tank.shoot!=null && tank.shoot.lief != false){//如果我方坦克的子弹创建了,且线程未结束就对每个敌人坦克进行判断
                for (int i = 0; i < enemyTanks.size(); i++) {
                    EnemyTanks e = this.enemyTanks.get(i);
                    himEnemyTank(tank.shoot,e);
                }
            }
            this.repaint();
        }
    }
}

除此之外还可以在MyPanel类的run方法中再加一个判断,如果敌人坦克的live不是true的时候,就将此坦克从集合中删除,以防消失后还在吃子弹,因为上面只是不绘制被击中的坦克,实际上他还是存在的。所以再加一个判断,从集合中删除掉,就不会吃子弹了。以此实现真正的死亡

for (int i = 0; i < enemyTanks.size(); i++) {//如果敌人坦克被击中,那么就把此坦克从集合中删除
                    if (!(enemyTanks.get(i).live)){
                        enemyTanks.remove(i);
                    }
                }

坦克死亡爆炸效果

首先可以将爆炸效果写成一个类 boom
再分析
爆炸效果其实就是几张图片快速切换重绘,要实现这个效果需分析几点。
爆炸效果肯定是坦克的当前位置,所以也得有x和y。
再因为爆炸效果是几张图片不断重绘,所以可以添加一个int值作为生命周期,不同的生命周期绘制不同的图片
再写一个生命周期不断–的方法,当生命周期为0时爆炸效果结束。

boom类定义好了后,在MyPanel类定义一个爆炸的集合,用于保存爆炸效果。
再定义三张图片,分别是爆炸开始到结束的效果。
然后在判断是否击中坦克的方法中,每当判断击中坦克时,就new 一个boom类,将被击中的坦克x和y传入。
然后在paint方法中,判断如果booms集合不为null就代表有坦克被击中了,需要执行爆炸效果。
接着在boom的生命周期的不同节点,绘制不同的图片,以此爆炸效果就完成了。

//boom类
public class boom {
    int x;
    int y;
    int duration = 9;//爆炸效果生命周期
    boolean live = true;

    public boom(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public void DownDur(){
        if (duration>0){
            duration--;
        }else{
            live = false;
        }
    }
}
//MyPanel类
public class MyPanel extends Panel implements KeyListener,Runnable{
    int speek = 1;
    Tank tank;
    Vector<EnemyTanks> enemyTanks = new Vector<>();//用于保存敌人坦克
    Vector<boom>booms = new Vector<>();//用于保存要爆炸的地方
    Image image1 = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/b1.png"));//用于保存爆炸效果的图片
    Image image2 = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/b2.png"));
    Image image3 = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/b3.png"));
    public MyPanel(){
        //初始化我方坦克
        tank = new Tank(20,460,0);
        //初始化敌方坦克
        for (int i = 0; i < 3; i++) {
            EnemyTanks enemyTank = new EnemyTanks(100*(i+1),0,2);
            //初始化敌方坦克子弹
            shoot shoot = new shoot(enemyTank.x,enemyTank.y,enemyTank.direct);
            //将子弹放入敌方坦克的shoots集合进行管理
            enemyTank.shoots.add(shoot);
            //启动敌方坦克的子弹
            new Thread(shoot).start();
            enemyTanks.add(enemyTank);
        }
    }
   @Override
    public void paint(Graphics g) {
        super.paint(g);
       System.out.println("调用paint");
       g.fillRect(0,0,500,600);
       //画我方坦克
       drawTank(tank.x,tank.y,g,tank.direct,0);
       //画敌方坦克
       for (int i = 0; i < enemyTanks.size(); i++) {
           EnemyTanks e = enemyTanks.get(i);
           if (e.live) {//如果敌方坦克的live不是false才进行绘制
               drawTank(e.x, e.y, g, e.direct, 1);
               //画出敌方坦克子弹
               for (int j = 0; j < e.shoots.size(); j++) {
                   shoot s = e.shoots.get(j);
                   if (s.lief) {
                       g.setColor(Color.RED);
                       g.drawRect(s.x + 20, s.y + 60, 3, 3);
                   } else {
                       e.shoots.remove(s);
                   }
               }
           }
       }
       //画子弹
       if (tank.shoot!= null && tank.shoot.lief!=false){
           g.setColor(Color.PINK);
           g.drawRect(tank.shoot.x,tank.shoot.y,3,3);
       }
       //当爆炸效果集合不是null时,说明有坦克被击中了,得开始绘制爆炸效果了
       if (booms!=null){
           try {
               Thread.sleep(50);//因为第一次会额外调用一次paint,所以最好休眠一下,不然第一次爆炸不显示
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           for (int i = 0; i < booms.size(); i++) {
               boom boom = booms.get(i);
               if (boom.duration>=7){
                   g.drawImage(image1,boom.x,boom.y,60,60,this);
               }else if(boom.duration>=5){
                   g.drawImage(image2,boom.x,boom.y,60,60,this);
               }
               else if(boom.duration>=3){
                   g.drawImage(image3,boom.x,boom.y,60,60,this);
               }
               boom.DownDur();
               if (boom.duration == 0){
                   booms.remove(boom);
               }
           }
       }
    }
    /**
    @param x 坦克的起始横坐标
    @param y 坦克的起始纵坐标
    @param g 画笔
    @param direct 根据方向绘制不同朝向的坦克
    @param type  根据0或者1 绘制不同颜色的坦克
    */
    public void drawTank(int x,int y,Graphics g,int direct,int type){
        switch (type){
            case 0:
                g.setColor(Color.PINK);//自己的坦克就粉色
                break;
            case 1:
                g.setColor(Color.RED);//敌人的坦克就红色
                break;
        }
            switch (direct){//0上,1右,2下,3左
                case 0://0表示绘制方向朝上的坦克
                    g.fill3DRect(x,y,10,50,false);
                    g.fill3DRect(x+10,y+5,25,40,false);
                    g.fill3DRect(x+35,y,10,50,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+22,y-5,x+22,y+25);
                    break;
                case 1://1表示绘制方向朝右的坦克
                    g.fill3DRect(x,y+7,50,10,false);
                    g.fill3DRect(x+5,y+14,40,25,false);
                    g.fill3DRect(x,y+37,50,10,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+25,y+25,x+58,y+25);
                    break;
                case 2://2表示绘制方向朝下的坦克
                    g.fill3DRect(x,y,10,50,false);
                    g.fill3DRect(x+10,y+5,25,40,false);
                    g.fill3DRect(x+35,y,10,50,false);
                    g.fillOval(x+10,y+12,25,25);
                    g.drawLine(x+22,y+55,x+22,y+25);
                    break;
                case 3://3表示绘制方向朝左的坦克
                    g.fill3DRect(x,y+7,50,10,false);
                    g.fill3DRect(x+5,y+14,40,25,false);
                    g.fill3DRect(x,y+37,50,10,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+25,y+25,x-8,y+25);
                    break;
                default://其他情况暂不考虑
                    break;
            }
    }

    @Override //字符输出时,该方法触发
    public void keyTyped(KeyEvent e) {

    }

    @Override//有按键按下时,该方法触发
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_W){//前进
            moveUp();
            tank.direct = 0;
        }else if(e.getKeyCode() == KeyEvent.VK_S){
            moveDown();
            tank.direct = 2;
        }else if(e.getKeyCode() == KeyEvent.VK_A){
            moveLeft();
            tank.direct = 3;
        }
        else if(e.getKeyCode() == KeyEvent.VK_D){
            moveRight();
            tank.direct = 1;
        }else if(e.getKeyCode() == 32){
            System.out.println("氮气加速~");
            speek +=5;
        }else{
            System.out.println(e.getKeyCode());
        }
        if (e.getKeyCode() == KeyEvent.VK_J){
            tank.fire();
        }
        repaint();
    }

    @Override//有按键松开时,该方法触发
    public void keyReleased(KeyEvent e) {

    }
    public void moveUp(){
        tank.y-=speek;
    }

让敌方坦克动起来

首先分析这个需求:
坦克动起来得改变x和y的值,且需要不断的改变
根据上面的分析,可以使用以下方法:
将敌人坦克类EnemyTanks做成一个线程,然后再run方法中使用switch语句根据坦克的朝向改变x和y的值,再使用 Math. random方法随机反馈0~3之间的数字。不断的循环就可以实现让坦克动起来.
可以在初始化创建敌人坦克后,马上就启动线程

//EnemyTanks类
public class EnemyTanks extends Tank implements Runnable{
    boolean live = true;
    Vector<shoot> shoots = new Vector<>();
    public EnemyTanks(int x, int y, int direct) {
        super(x, y, direct);
    }

    @Override
    public void run() {
        while (live){
            for (int i = 0; i < 30; i++) {
                try {
                    Thread.sleep(50);//睡眠一会防止,还没动几步就换方向了
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                switch (direct){
                    case 0://上
                        y-=2;
                        break;
                    case 1://右
                        x+=2;
                        break;
                    case 2://下
                        y+=2;
                        break;
                    case 3://左
                        x-=2;
                        break;
                }
            }
            direct = (int)(Math.random()*4);//随机调整方向
        }
    }
}

控制坦克的移动范围

在上面的制作中,由于没有限制坦克的x和y最高和最低值是多少,所以坦克是可以出界的,而子弹又是不可以出界的,所以就会有漏洞,下面将限制坦克的x和y最高和最低值。

代码提现也很简单,只需要在改变x和y的时候加一个if判断,确定没有超出才改变,否则不改变。这个思路也可以拿来做障碍物

public class EnemyTanks extends Tank implements Runnable{
    boolean live = true;
    Vector<shoot> shoots = new Vector<>();
    public EnemyTanks(int x, int y, int direct) {
        super(x, y, direct);
    }

    @Override
    public void run() {
        while (live){
            for (int i = 0; i < 30; i++) {
                try {
                    Thread.sleep(50);//睡眠一会防止,还没动几步就换方向了
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                switch (direct){
                    case 0://if (y >= 0) {
                            y -= 2;
                        }
                        break;
                    case 1://if ( x+60 < 500) {
                            x += 2;
                        }
                        break;
                    case 2://if (y+50 < 600) {
                            y += 2;
                        }
                        break;
                    case 3://if (x >= 0 ) {
                            x -= 2;
                        }
                        break;
                }
            }
            direct = (int)(Math.random()*4);//随机调整方向
        }
    }
}

发射多颗子弹

在前面的制作中,只实现了,发射一颗子弹,下面要实现,可以发射多颗子弹。
要实现发射多颗子弹,先将前面的发射子弹的问题解决一下。
前面坦克发射子弹时,当发射子弹后,子弹还未达到边界时,再按J发射,那么之前的子弹对象就会被滞空,从而去绘制新的子弹。
那么我们先调整成,当,前一颗子弹还未到达边界时,或到达边界了且线程live是false时,才可以创建下一个子弹线程。
实现这个功能,只需要在创建子弹对象的时候加一个判断,tank.shoot == null || tank.shoot ==false

if (e.getKeyCode() == KeyEvent.VK_J){
            if(tank.shoot == null || !tank.shoot.lief) {
                tank.fire();
            }
        }

解决了上面的问题,再来思考怎么发射多颗子弹。
发射子弹其实就是创建一个子弹对象,不断的重绘它的位置,然后判断是否进入了敌人坦克的范围
要发射多颗子弹,可以创建一个子弹集合,每当按下J时,就创建一个子弹对象,且加入到集合中
再绘制的时候改成遍历子弹集合,挨个绘制。判断是否击中敌人时也遍历集合,挨个判断。

//Tank类 我方坦克类
public class Tank {
    int x;
    int y;
    int direct;
    //创建一个子弹集合,用于保存多个子弹
    Vector<shoot> shoots = new Vector<>();
    public Tank(int x, int y, int direct) {
        this.x = x;
        this.y = y;
        this.direct = direct;
    }
    shoot shoot;//定义一个子弹属性
    public void fire(){
        switch (direct){//根据此时坦克的朝向创建对应的子弹发送初始位置
            case 0://当坦克朝上时
                System.out.println("上");
                shoot = new shoot(x+20,y-6,direct);
                break;
            case 1://当坦克朝右时
                shoot = new shoot(x+60,y+24,direct);
                break;
            case 2://当坦克朝下时
                System.out.println("下");
                shoot = new shoot(x+21,y+60,direct);
                break;
            case 3://当坦克朝左边时
                System.out.println("左");
                shoot = new shoot(x-8,y+23,direct);
                break;
        }
        //将刚创建的子弹加入到子弹集合中
        shoots.add(shoot);
        //创建完子弹,启动子弹
        new Thread(shoot).start();
    }
}
//MyPanel类
public class MyPanel extends Panel implements KeyListener,Runnable{
    int speek = 1;
    Tank tank;
    Vector<EnemyTanks> enemyTanks = new Vector<>();//用于保存敌人坦克
    Vector<boom>booms = new Vector<>();//用于保存要爆炸的地方
    Image image1 = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/b1.png"));//用于保存爆炸效果的图片
    Image image2 = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/b2.png"));
    Image image3 = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/b3.png"));
    public MyPanel(){
        //初始化我方坦克
        tank = new Tank(20,460,0);
        //初始化敌方坦克
        for (int i = 0; i < 3; i++) {
            EnemyTanks enemyTank = new EnemyTanks(100*(i+1),0,2);
            //启动敌人坦克线程
            new Thread(enemyTank).start();
            //初始化敌方坦克子弹
            shoot shoot = new shoot(enemyTank.x,enemyTank.y,enemyTank.direct);
            //将子弹放入敌方坦克的shoots集合进行管理
            enemyTank.shoots.add(shoot);
            //启动敌方坦克的子弹
            new Thread(shoot).start();
            enemyTanks.add(enemyTank);
        }
    }
   @Override
    public void paint(Graphics g) {
        super.paint(g);
       System.out.println("调用paint");
       g.fillRect(0,0,500,600);
       //画我方坦克
       drawTank(tank.x,tank.y,g,tank.direct,0);
       //画敌方坦克
       for (int i = 0; i < enemyTanks.size(); i++) {
           EnemyTanks e = enemyTanks.get(i);
           if (e.live) {//如果敌方坦克的live不是false才进行绘制
               drawTank(e.x, e.y, g, e.direct, 1);
               //画出敌方坦克子弹
               for (int j = 0; j < e.shoots.size(); j++) {
                   shoot s = e.shoots.get(j);
                   if (s.lief) {
                       g.setColor(Color.RED);
                       g.drawRect(s.x + 20, s.y + 60, 3, 3);
                   } else {
                       e.shoots.remove(s);
                   }
               }
           }
       }
       //画我方坦克的子弹
       for (int i = 0; i < tank.shoots.size(); i++) {
           shoot s = tank.shoots.get(i);
           if (s!= null && s.lief){
               g.setColor(Color.PINK);
               g.drawRect(s.x,s.y,3,3);
           }else if(!s.lief){
               tank.shoots.remove(s);//当该子弹线程消亡后就把此对象从集合中删除
           }
       }
       //当爆炸效果集合不是null时,说明有坦克被击中了,得开始绘制爆炸效果了
       if (booms!=null){
           try {
               Thread.sleep(50);//因为第一次会额外调用一次paint,所以最好休眠一下,不然第一次爆炸不显示
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           for (int i = 0; i < booms.size(); i++) {
               boom boom = booms.get(i);
               if (boom.duration>=7){
                   g.drawImage(image1,boom.x,boom.y,60,60,this);
               }else if(boom.duration>=5){
                   g.drawImage(image2,boom.x,boom.y,60,60,this);
               }
               else if(boom.duration>=3){
                   g.drawImage(image3,boom.x,boom.y,60,60,this);
               }
               boom.DownDur();
               if (boom.duration == 0){
                   booms.remove(boom);
               }
           }
       }
    }
    /**
    @param x 坦克的起始横坐标
    @param y 坦克的起始纵坐标
    @param g 画笔
    @param direct 根据方向绘制不同朝向的坦克
    @param type  根据0或者1 绘制不同颜色的坦克
    */
    public void drawTank(int x,int y,Graphics g,int direct,int type){
        switch (type){
            case 0:
                g.setColor(Color.PINK);//自己的坦克就粉色
                break;
            case 1:
                g.setColor(Color.RED);//敌人的坦克就红色
                break;
        }
            switch (direct){//0上,1右,2下,3左
                case 0://0表示绘制方向朝上的坦克
                    g.fill3DRect(x,y,10,50,false);
                    g.fill3DRect(x+10,y+5,25,40,false);
                    g.fill3DRect(x+35,y,10,50,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+22,y-5,x+22,y+25);
                    break;
                case 1://1表示绘制方向朝右的坦克
                    g.fill3DRect(x,y+7,50,10,false);
                    g.fill3DRect(x+5,y+14,40,25,false);
                    g.fill3DRect(x,y+37,50,10,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+25,y+25,x+58,y+25);
                    break;
                case 2://2表示绘制方向朝下的坦克
                    g.fill3DRect(x,y,10,50,false);
                    g.fill3DRect(x+10,y+5,25,40,false);
                    g.fill3DRect(x+35,y,10,50,false);
                    g.fillOval(x+10,y+12,25,25);
                    g.drawLine(x+22,y+55,x+22,y+25);
                    break;
                case 3://3表示绘制方向朝左的坦克
                    g.fill3DRect(x,y+7,50,10,false);
                    g.fill3DRect(x+5,y+14,40,25,false);
                    g.fill3DRect(x,y+37,50,10,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+25,y+25,x-8,y+25);
                    break;
                default://其他情况暂不考虑
                    break;
            }
    }

    @Override //字符输出时,该方法触发
    public void keyTyped(KeyEvent e) {

    }

    @Override//有按键按下时,该方法触发
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_W){//前进
            moveUp();
            tank.direct = 0;
        }else if(e.getKeyCode() == KeyEvent.VK_S){
            moveDown();
            tank.direct = 2;
        }else if(e.getKeyCode() == KeyEvent.VK_A){
            moveLeft();
            tank.direct = 3;
        }
        else if(e.getKeyCode() == KeyEvent.VK_D){
            moveRight();
            tank.direct = 1;
        }else if(e.getKeyCode() == 32){
            System.out.println("氮气加速~");
            speek +=5;
        }else{
            System.out.println(e.getKeyCode());
        }
        if (e.getKeyCode() == KeyEvent.VK_J){
            if (tank.shoots.size() <= 5) {
                tank.fire();
            }
        }
        repaint();
    }

    @Override//有按键松开时,该方法触发
    public void keyReleased(KeyEvent e) {

    }
    public void moveUp(){
        tank.y-=speek;
    }
    public void moveLeft(){
        tank.x-=speek;
    }
    public void moveRight(){
        tank.x+=speek;
    }
    public void moveDown(){
        tank.y+=speek;
    }

    //此方法判断我方坦克子弹是否击中敌人坦克
    public  void himEnemyTank(Vector<shoot> shoots , EnemyTanks e ){
        for(int i = 0 ; i<shoots.size();i++){
            shoot s = shoots.get(i);
            //上下和左右的x,y是相同的,所以写两个判断即可
            switch (e.direct){
                case 0:
                case 2:
                    if (s.x>e.x&&s.x< e.x+40
                            && s.y>e.y&&s.y<e.y+60){//如果此条件成立就代表,地方在朝上或者朝下的方向被击中了
                        e.live = false;
                        s.lief = false;
                        booms.add(new boom(e.x, e.y));//当有一个坦克被击中就添加一个要执行的爆炸效果
                    }
                    break;
                case 1:
                case 3:
                    if (s.x>e.x&&s.x< e.x+60
                            && s.y>e.y&&s.y<e.y+40){//如果此条件成立就代表,地方在朝左或者朝右的方向被击中了
                        e.live = false;
                        s.lief = false;
                        booms.add(new boom(e.x, e.y));//当有一个坦克被击中就添加一个要执行的爆炸效果
                    }
                    break;
            }

        }

    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (tank.shoot!=null && tank.shoot.lief ){//如果我方坦克的子弹创建了,且线程未结束就对每个敌人坦克进行判断
                for (int i = 0; i < enemyTanks.size(); i++) {
                    EnemyTanks e = this.enemyTanks.get(i);
                    himEnemyTank(tank.shoots,e);
                }
            }
                for (int i = 0; i < enemyTanks.size(); i++) {//如果敌人坦克被击中,那么就把此坦克从集合中删除
                    if (!(enemyTanks.get(i).live)){
                        enemyTanks.remove(i);
                    }
                }
            this.repaint();
        }
    }
}

实现让敌人坦克的子弹消亡后再发射新子弹

在前面已经实现让敌人坦克发射一颗子弹,但是只能发射一颗。
要让坦克发射新的子弹,需要判断前一个子弹消亡再创建新的子弹。
因为在前面定义敌人坦克的子弹用的是集合存放,且绘制敌人坦克的子弹用的也是遍历。
所以只需在敌人坦克类EnemyTanks的run方法中做一个判断,当子弹集合 = =0时(如果要让敌人坦克发射多颗子弹,可以把0变大)
就创建新的子弹到集合中。

//敌人坦克类
public class EnemyTanks extends Tank implements Runnable{
    boolean live = true;
    Vector<shoot> shoots1 = new Vector<>();
    public EnemyTanks(int x, int y, int direct) {
        super(x, y, direct);
    }

    @Override
    public void run() {
        while (live){
            System.out.println("敌人坦克");
            if (live && shoots1.size() == 0){
                shoot Ds = null;
                switch (direct){//根据此时坦克的朝向创建对应的子弹发送初始位置
                    case 0://当坦克朝上时
                        System.out.println("上");
                        Ds = new shoot(x,y,0);
                        break;
                    case 1://当坦克朝右时
                        Ds = new shoot(x+60,y-35,1);
                        break;
                    case 2://当坦克朝下时
                        System.out.println("下");
                        Ds = new shoot(x,y,2);
                        break;
                    case 3://当坦克朝左边时
                        System.out.println("左");
                        Ds = new shoot(x,y-35,3);
                        break;
                }
                System.out.println("创建子弹");
                shoots1.add(Ds);
                new Thread(Ds).start();
            }
            for (int i = 0; i < 30; i++) {
                try {
                    Thread.sleep(200);//睡眠一会防止,还没动几步就换方向了
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                switch (direct){
                    case 0://if (y >= 0) {
                            y -= 2;
                        }
                        break;
                    case 1://if ( x+60 < 500) {
                            x += 2;
                        }
                        break;
                    case 2://if (y+50 < 600) {
                            y += 2;
                        }
                        break;
                    case 3://if (x >= 0 ) {
                            x -= 2;
                        }
                        break;
                }
            }
            direct = (int)(Math.random()*4);//随机调整方向
        }
    }
}
//MyPanel类
public class MyPanel extends Panel implements KeyListener,Runnable{
    int speek = 1;
    Tank tank;
    Vector<EnemyTanks> enemyTanks = new Vector<>();//用于保存敌人坦克
    Vector<boom>booms = new Vector<>();//用于保存要爆炸的地方
    Image image1 = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/b1.png"));//用于保存爆炸效果的图片
    Image image2 = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/b2.png"));
    Image image3 = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/b3.png"));
    public MyPanel(){
        //初始化我方坦克
        tank = new Tank(20,460,0);
        //初始化敌方坦克
        for (int i = 0; i < 3; i++) {
            EnemyTanks enemyTank = new EnemyTanks(100*(i+1),0,2);
            //启动敌人坦克线程
            new Thread(enemyTank).start();
            //初始化敌方坦克子弹
            shoot S = new shoot(enemyTank.x,enemyTank.y,enemyTank.direct);
            //将子弹放入敌方坦克的shoots集合进行管理
            enemyTank.shoots1.add(S);
            //启动敌方坦克的子弹
            new Thread(S).start();
            enemyTanks.add(enemyTank);
        }
    }
   @Override
    public void paint(Graphics g) {
        super.paint(g);
       System.out.println("调用paint");
       g.fillRect(0,0,500,600);
       //画我方坦克
       drawTank(tank.x,tank.y,g,tank.direct,0);

       //画敌方坦克
       for (int i = 0; i < enemyTanks.size(); i++) {
           EnemyTanks e = enemyTanks.get(i);
           if (e.live) {//如果敌方坦克的live不是false才进行绘制
               drawTank(e.x, e.y, g, e.direct, 1);
               //画出敌方坦克子弹
               for (int j = 0; j < e.shoots1.size(); j++) {
                   shoot s = e.shoots1.get(j);
                   if (s.lief) {
                       g.setColor(Color.RED);
                       g.drawRect(s.x + 20, s.y + 60, 3, 3);
                   } else {
                       e.shoots1.remove(s);
                   }
               }
           }
       }
       //画我方坦克的子弹
       for (int i = 0; i < tank.shoots.size(); i++) {
           shoot s = tank.shoots.get(i);
           if (s!= null && s.lief){
               g.setColor(Color.PINK);
               g.drawRect(s.x,s.y,3,3);
           }else if(!s.lief){
               tank.shoots.remove(s);//当该子弹线程消亡后就把此对象从集合中删除
           }
       }
       //当爆炸效果集合不是null时,说明有坦克被击中了,得开始绘制爆炸效果了
       if (booms!=null){
           try {
               Thread.sleep(50);//因为第一次会额外调用一次paint,所以最好休眠一下,不然第一次爆炸不显示
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           for (int i = 0; i < booms.size(); i++) {
               boom boom = booms.get(i);
               if (boom.duration>=7){
                   g.drawImage(image1,boom.x,boom.y,60,60,this);
               }else if(boom.duration>=5){
                   g.drawImage(image2,boom.x,boom.y,60,60,this);
               }
               else if(boom.duration>=3){
                   g.drawImage(image3,boom.x,boom.y,60,60,this);
               }
               boom.DownDur();
               if (boom.duration == 0){
                   booms.remove(boom);
               }
           }
       }
    }
    /**
    @param x 坦克的起始横坐标
    @param y 坦克的起始纵坐标
    @param g 画笔
    @param direct 根据方向绘制不同朝向的坦克
    @param type  根据0或者1 绘制不同颜色的坦克
    */
    public void drawTank(int x,int y,Graphics g,int direct,int type){
        switch (type){
            case 0:
                g.setColor(Color.PINK);//自己的坦克就粉色
                break;
            case 1:
                g.setColor(Color.RED);//敌人的坦克就红色
                break;
        }
            switch (direct){//0上,1右,2下,3左
                case 0://0表示绘制方向朝上的坦克
                    g.fill3DRect(x,y,10,50,false);
                    g.fill3DRect(x+10,y+5,25,40,false);
                    g.fill3DRect(x+35,y,10,50,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+22,y-5,x+22,y+25);
                    break;
                case 1://1表示绘制方向朝右的坦克
                    g.fill3DRect(x,y+7,50,10,false);
                    g.fill3DRect(x+5,y+14,40,25,false);
                    g.fill3DRect(x,y+37,50,10,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+25,y+25,x+58,y+25);
                    break;
                case 2://2表示绘制方向朝下的坦克
                    g.fill3DRect(x,y,10,50,false);
                    g.fill3DRect(x+10,y+5,25,40,false);
                    g.fill3DRect(x+35,y,10,50,false);
                    g.fillOval(x+10,y+12,25,25);
                    g.drawLine(x+22,y+55,x+22,y+25);
                    break;
                case 3://3表示绘制方向朝左的坦克
                    g.fill3DRect(x,y+7,50,10,false);
                    g.fill3DRect(x+5,y+14,40,25,false);
                    g.fill3DRect(x,y+37,50,10,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+25,y+25,x-8,y+25);
                    break;
                default://其他情况暂不考虑
                    break;
            }
    }

    @Override //字符输出时,该方法触发
    public void keyTyped(KeyEvent e) {

    }

    @Override//有按键按下时,该方法触发
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_W){//前进
            moveUp();
            tank.direct = 0;
        }else if(e.getKeyCode() == KeyEvent.VK_S){
            moveDown();
            tank.direct = 2;
        }else if(e.getKeyCode() == KeyEvent.VK_A){
            moveLeft();
            tank.direct = 3;
        }
        else if(e.getKeyCode() == KeyEvent.VK_D){
            moveRight();
            tank.direct = 1;
        }else if(e.getKeyCode() == 32){
            System.out.println("氮气加速~");
            speek +=5;
        }else{
            System.out.println(e.getKeyCode());
        }
        if (e.getKeyCode() == KeyEvent.VK_J){
            if (tank.shoots.size() <= 5) {
                tank.fire();
            }
        }
        repaint();
    }

    @Override//有按键松开时,该方法触发
    public void keyReleased(KeyEvent e) {

    }
    public void moveUp(){
        tank.y-=speek;
    }
    public void moveLeft(){
        tank.x-=speek;
    }
    public void moveRight(){
        tank.x+=speek;
    }
    public void moveDown(){
        tank.y+=speek;
    }

    //此方法判断我方坦克子弹是否击中敌人坦克
    public  void himEnemyTank(Vector<shoot> shoots , EnemyTanks e ){
        for(int i = 0 ; i<shoots.size();i++){
            shoot s = shoots.get(i);
            //上下和左右的x,y是相同的,所以写两个判断即可
            switch (e.direct){
                case 0:
                case 2:
                    if (s.x>e.x&&s.x< e.x+40
                            && s.y>e.y&&s.y<e.y+60){//如果此条件成立就代表,地方在朝上或者朝下的方向被击中了
                        e.live = false;
                        s.lief = false;
                        booms.add(new boom(e.x, e.y));//当有一个坦克被击中就添加一个要执行的爆炸效果
                    }
                    break;
                case 1:
                case 3:
                    if (s.x>e.x&&s.x< e.x+60
                            && s.y>e.y&&s.y<e.y+40){//如果此条件成立就代表,地方在朝左或者朝右的方向被击中了
                        e.live = false;
                        s.lief = false;
                        booms.add(new boom(e.x, e.y));//当有一个坦克被击中就添加一个要执行的爆炸效果
                    }
                    break;
            }

        }

    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (tank.shoot!=null && tank.shoot.lief ){//如果我方坦克的子弹创建了,且线程未结束就对每个敌人坦克进行判断
                for (int i = 0; i < enemyTanks.size(); i++) {
                    EnemyTanks e = this.enemyTanks.get(i);
                    himEnemyTank(tank.shoots,e);
                }
            }
                for (int i = 0; i < enemyTanks.size(); i++) {//如果敌人坦克被击中,那么就把此坦克从集合中删除
                    if (!(enemyTanks.get(i).live)){
                        enemyTanks.remove(i);
                    }
                }
            this.repaint();
        }
    }
}

实现敌人坦克子弹击中我方坦克,我方坦克消失且爆炸

将我方坦克定义一个live属性,然后在MyPanel类,定义一个方法用于判断我方坦克是否被击中。(被击中的方法前面写过)
如果被击中就跟之前判断敌人坦克一样,live改成false,且添加一个爆炸对象。再在绘制我方坦克时,添加一个判断,如果我方坦克的live是false就不绘制。

//MyPanel类
public class MyPanel extends Panel implements KeyListener,Runnable{
    int speek = 1;
    Tank tank;
    Vector<EnemyTanks> enemyTanks = new Vector<>();//用于保存敌人坦克
    Vector<boom>booms = new Vector<>();//用于保存要爆炸的地方
    Image image1 = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/b1.png"));//用于保存爆炸效果的图片
    Image image2 = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/b2.png"));
    Image image3 = Toolkit.getDefaultToolkit().getImage(MyPanel.class.getResource("/b3.png"));
    public MyPanel(){
        //初始化我方坦克
        tank = new Tank(20,460,0);
        //初始化敌方坦克
        for (int i = 0; i < 3; i++) {
            EnemyTanks enemyTank = new EnemyTanks(100*(i+1),0,2);
            //启动敌人坦克线程
            new Thread(enemyTank).start();
            //初始化敌方坦克子弹
            shoot S = new shoot(enemyTank.x,enemyTank.y,enemyTank.direct);
            //将子弹放入敌方坦克的shoots集合进行管理
            enemyTank.shoots1.add(S);
            //启动敌方坦克的子弹
            new Thread(S).start();
            enemyTanks.add(enemyTank);
        }
    }
   @Override
    public void paint(Graphics g) {
        super.paint(g);
       System.out.println("调用paint");
       g.fillRect(0,0,500,600);
       //画我方坦克
       if (tank.live) {
           drawTank(tank.x, tank.y, g, tank.direct, 0);
       }

       //画敌方坦克
       for (int i = 0; i < enemyTanks.size(); i++) {
           EnemyTanks e = enemyTanks.get(i);
           if (e.live) {//如果敌方坦克的live不是false才进行绘制
               drawTank(e.x, e.y, g, e.direct, 1);
               //画出敌方坦克子弹
               for (int j = 0; j < e.shoots1.size(); j++) {
                   shoot s = e.shoots1.get(j);
                   if (s.lief) {
                       g.setColor(Color.RED);
                       g.drawRect(s.x + 20, s.y + 60, 3, 3);
                   } else {
                       e.shoots1.remove(s);
                   }
               }
           }
       }
       //画我方坦克的子弹
       for (int i = 0; i < tank.shoots.size(); i++) {
           shoot s = tank.shoots.get(i);
           if (s!= null && s.lief){
               g.setColor(Color.PINK);
               g.drawRect(s.x,s.y,3,3);
           }else if(!s.lief){
               tank.shoots.remove(s);//当该子弹线程消亡后就把此对象从集合中删除
           }
       }
       //当爆炸效果集合不是null时,说明有坦克被击中了,得开始绘制爆炸效果了
       if (booms!=null){
           try {
               Thread.sleep(50);//因为第一次会额外调用一次paint,所以最好休眠一下,不然第一次爆炸不显示
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
           for (int i = 0; i < booms.size(); i++) {
               boom boom = booms.get(i);
               if (boom.duration>=7){
                   g.drawImage(image1,boom.x,boom.y,60,60,this);
               }else if(boom.duration>=5){
                   g.drawImage(image2,boom.x,boom.y,60,60,this);
               }
               else if(boom.duration>=3){
                   g.drawImage(image3,boom.x,boom.y,60,60,this);
               }
               boom.DownDur();
               if (boom.duration == 0){
                   booms.remove(boom);
               }
           }
       }
    }
    /**
    @param x 坦克的起始横坐标
    @param y 坦克的起始纵坐标
    @param g 画笔
    @param direct 根据方向绘制不同朝向的坦克
    @param type  根据0或者1 绘制不同颜色的坦克
    */
    public void drawTank(int x,int y,Graphics g,int direct,int type){
        switch (type){
            case 0:
                g.setColor(Color.PINK);//自己的坦克就粉色
                break;
            case 1:
                g.setColor(Color.RED);//敌人的坦克就红色
                break;
        }
            switch (direct){//0上,1右,2下,3左
                case 0://0表示绘制方向朝上的坦克
                    g.fill3DRect(x,y,10,50,false);
                    g.fill3DRect(x+10,y+5,25,40,false);
                    g.fill3DRect(x+35,y,10,50,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+22,y-5,x+22,y+25);
                    break;
                case 1://1表示绘制方向朝右的坦克
                    g.fill3DRect(x,y+7,50,10,false);
                    g.fill3DRect(x+5,y+14,40,25,false);
                    g.fill3DRect(x,y+37,50,10,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+25,y+25,x+58,y+25);
                    break;
                case 2://2表示绘制方向朝下的坦克
                    g.fill3DRect(x,y,10,50,false);
                    g.fill3DRect(x+10,y+5,25,40,false);
                    g.fill3DRect(x+35,y,10,50,false);
                    g.fillOval(x+10,y+12,25,25);
                    g.drawLine(x+22,y+55,x+22,y+25);
                    break;
                case 3://3表示绘制方向朝左的坦克
                    g.fill3DRect(x,y+7,50,10,false);
                    g.fill3DRect(x+5,y+14,40,25,false);
                    g.fill3DRect(x,y+37,50,10,false);
                    g.fillOval(x+10,y+15,25,25);
                    g.drawLine(x+25,y+25,x-8,y+25);
                    break;
                default://其他情况暂不考虑
                    break;
            }
    }

    @Override //字符输出时,该方法触发
    public void keyTyped(KeyEvent e) {

    }

    @Override//有按键按下时,该方法触发
    public void keyPressed(KeyEvent e) {
        if (e.getKeyCode() == KeyEvent.VK_W){//前进
            moveUp();
            tank.direct = 0;
        }else if(e.getKeyCode() == KeyEvent.VK_S){
            moveDown();
            tank.direct = 2;
        }else if(e.getKeyCode() == KeyEvent.VK_A){
            moveLeft();
            tank.direct = 3;
        }
        else if(e.getKeyCode() == KeyEvent.VK_D){
            moveRight();
            tank.direct = 1;
        }else if(e.getKeyCode() == 32){
            System.out.println("氮气加速~");
            speek +=5;
        }else{
            System.out.println(e.getKeyCode());
        }
        if (e.getKeyCode() == KeyEvent.VK_J){
            if (tank.shoots.size() <= 5) {
                tank.fire();
            }
        }
        repaint();
    }

    @Override//有按键松开时,该方法触发
    public void keyReleased(KeyEvent e) {

    }
    public void moveUp(){
        tank.y-=speek;
    }
    public void moveLeft(){
        tank.x-=speek;
    }
    public void moveRight(){
        tank.x+=speek;
    }
    public void moveDown(){
        tank.y+=speek;
    }
    //此方法判断敌人坦克是否击中我方坦克
    public void hitMyTank(){
        //便利敌人坦克
        for (int i = 0; i < enemyTanks.size(); i++) {
            EnemyTanks e = this.enemyTanks.get(i);
            for (int j = 0; j < e.shoots1.size(); j++) {
                shoot Dshoot = e.shoots1.get(j);
                himMyTank(Dshoot,tank);
            }

        }
    }
    //此方法判断敌人坦克是否击中我方坦克
    public  void himMyTank(shoot s , Tank e ){
            //上下和左右的x,y是相同的,所以写两个判断即可
            switch (e.direct){
                case 0:
                case 2:
                    if (s.x>e.x&&s.x< e.x+40
                            && s.y-40>e.y&&s.y<e.y+60){//如果此条件成立就代表,地方在朝上或者朝下的方向被击中了
                        e.live = false;
                        s.lief = false;
                        booms.add(new boom(e.x, e.y));//当有一个坦克被击中就添加一个要执行的爆炸效果
                    }
                    break;
                case 1:
                case 3:
                    if (s.x>e.x&&s.x< e.x+60
                            && s.y>e.y&&s.y<e.y-20){//如果此条件成立就代表,地方在朝左或者朝右的方向被击中了
                        e.live = false;
                        s.lief = false;
                        booms.add(new boom(e.x, e.y));//当有一个坦克被击中就添加一个要执行的爆炸效果
                    }
                    break;
            }



    }
    //此方法判断我方子弹是否击中敌人坦克
    public  void himEnemyTank(Vector<shoot> shoots , Tank e ){
        for(int i = 0 ; i<shoots.size();i++){
            shoot s = shoots.get(i);
            //上下和左右的x,y是相同的,所以写两个判断即可
            switch (e.direct){
                case 0:
                case 2:
                    if (s.x>e.x&&s.x< e.x+40
                            && s.y>e.y&&s.y<e.y+60){//如果此条件成立就代表,地方在朝上或者朝下的方向被击中了
                        e.live = false;
                        s.lief = false;
                        booms.add(new boom(e.x, e.y));//当有一个坦克被击中就添加一个要执行的爆炸效果
                    }
                    break;
                case 1:
                case 3:
                    if (s.x>e.x&&s.x< e.x+60
                            && s.y>e.y&&s.y<e.y+40){//如果此条件成立就代表,地方在朝左或者朝右的方向被击中了
                        e.live = false;
                        s.lief = false;
                        booms.add(new boom(e.x, e.y));//当有一个坦克被击中就添加一个要执行的爆炸效果
                    }
                    break;
            }

        }

    }

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (tank.shoot!=null && tank.shoot.lief ){//如果我方坦克的子弹创建了,且线程未结束就对每个敌人坦克进行判断
                for (int i = 0; i < enemyTanks.size(); i++) {
                    EnemyTanks e = this.enemyTanks.get(i);
                    himEnemyTank(tank.shoots,e);
                }
            }
                for (int i = 0; i < enemyTanks.size(); i++) {//如果敌人坦克被击中,那么就把此坦克从集合中删除
                    if (!(enemyTanks.get(i).live)){
                        enemyTanks.remove(i);
                    }
                }
                //判断我方坦克是否被击中
            hitMyTank();
            this.repaint();
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/2415.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

51单片机之喝水提醒器

定时器定时器介绍晶振晶体震荡器&#xff0c;又称数字电路的“心脏”&#xff0c;是各种电子产品里面必不可少的频率元器件。数字电路的所有工作都离不开时钟&#xff0c;晶振的好坏、晶振电路设计的好坏&#xff0c;会影响到整个系统的稳定性。时钟周期时钟周期也称为振荡周期…

点云目标检测

3D点云特点 云特性 : 无序性 三维空间中的点云不会像图像那样&#xff0c;规规矩矩的在一个个的像素点排好队&#xff0c;点云并没有顺序&#xff0c;这给处理上带来极大的困难。一个物体如果使用N个点云表示&#xff0c;那么它的排列方式具有N!种&#xff0c;但是表示的却是同…

【Linux】进程优先级 环境变量

进程优先级 环境变量 一、进程优先级1、基本概念2、查看以及修改系统进程的优先级3、一些其他的关于进程优先级的指令和函数调用4、与进程优先级有关的一些进程性质二、环境变量1、基本概念2、和环境变量相关的命令3、Linux中的常见环境变量介绍4、环境变量的组织方式以及在C代…

win10远程桌面:通过系统自带mstsc可远程,RDO不能远程

摘要&#xff1a;在 esxi 7.1 上安装 win10系统&#xff08;企业版&#xff09;虚拟机后&#xff0c;通过以往方式设置远程桌面发现不能通过 RDO&#xff08;Remote Desktop Organizer&#xff09; 方式远程控制桌面&#xff0c;但通过系统自带 远程桌面连接&#xff08;mstsc&…

【web前端开发】CSS背景相关内容

文章目录背景颜色背景图片背景平铺背景位置background(复合属性)背景颜色 属性名:background-color 取值:表示颜色的取值都可以填写,如:rgb注意点: 背景颜色默认是透明的背景颜色不影响盒子的大小 实用技巧:在平时使用一些盒子时,可以给盒子设置背景颜色,这样可以看清盒子的…

应用层协议 HTTP HTTPS

目录 应用层 再谈 "协议" 序列化和反序列化 关于 json库 request序列化 request反序列化 response序列化 response反序列化 PS&#xff1a;命令宏 HTTP协议 认识URL urlencode和urldecode HTTP协议格式 HTTP请求 HTTP响应 请求方法 ​编辑 HT…

C语言蓝桥杯刷题:修剪灌木

题目链接 解题思路&#xff1a; 本题需要注意的是树是白天长&#xff0c;然后爱丽丝傍晩对某棵树进行修剪&#xff0c;也就是说树高度是可能在白天达到最大值而在傍晩变成0。我一开始也有一个误区&#xff0c;以为是要修剪的那棵树当天就变成0而不能生长&#xff0c;其实是先…

为什么说网络安全是风口行业?是IT行业最后的红利?

前言 “没有网络安全就没有国家安全”。当前&#xff0c;网络安全已被提升到国家战略的高度&#xff0c;成为影响国家安全、社会稳定至关重要的因素之一。 网络安全行业特点 1、就业薪资非常高&#xff0c;涨薪快 2021年猎聘网发布网络安全行业就业薪资行业最高人均33.77万&…

Chapter7.1:频域分析法理论基础

该系列博客主要讲述Matlab软件在自动控制方面的应用&#xff0c;如无自动控制理论基础&#xff0c;请先学习自动控制系列博文&#xff0c;该系列博客不再详细讲解自动控制理论知识。 自动控制理论基础相关链接&#xff1a;https://blog.csdn.net/qq_39032096/category_10287468…

【数据结构】KMP算法细节详解

KMP算法细节详解前言一、字符串匹配问题1.BF算法2.KMP算法二、next数组三、手写nex思想四、机算next思想五、next数组细节理解六、nextVal数组七、KMP算法代码实现八、nextVal数组代码实现完结前言 KMP算法是为了字符串匹配问题而被研究出来的&#xff0c;字符串匹配问题就是查…

SQL语句性能分析

1. 数据库服务器的优化步骤 当我们遇到数据库调优问题的时候&#xff0c;该如何思考呢&#xff1f;这里把思考的流程整理成下面这张图。 整个流程划分成了 观察&#xff08;Show status&#xff09; 和 行动&#xff08;Action&#xff09; 两个部分。字母 S 的部分代表观察&…

openpnp - 调整轴的步进精度

文章目录openpnp - 调整轴的步进精度概述笔记沙冰主板的配置 - 每mm步数openpnp的配置 - 每mm步数钢板尺的测量效果备注ENDopenpnp - 调整轴的步进精度 概述 拿到手的设备, 验证X,Y轴的精度. 拿钢板尺放在顶部相机下面, 用JOG面板和顶部相机配合. 发现X轴每走100mm, 多走0.1m…

浏览器工作原理

一、JavaScript 的历史 JavaScript&#xff08;简称JS&#xff09;Web前端开发的脚本语言。 它诞生1995年&#xff0c;由网景公司的 Brendan Eich 开发。最初&#xff0c;JavaScript 被设计用于在网页上嵌入动态内容和交互式功能。 1996年&#xff0c;JavaScript 1.1 成为国…

基于ESP32做低功耗墨水屏时钟

基于ESP32做低功耗墨水屏时钟电子墨水屏概述ESP32实验低功耗电子时钟功能描述接线开发实验结果电子墨水屏 概述 电子墨水是一种革新信息显示的新方法和技术。和传统纸差异是电子墨水在通电时改变颜色&#xff0c;并且可以显示变化的图象&#xff0c;像计算器或手机那样的显示。…

基于深度学习的瓶子检测软件(UI界面+YOLOv5+训练数据集)

摘要&#xff1a;基于深度学习的瓶子检测软件用于自动化瓶子检测与识别&#xff0c;对于各种场景下的塑料瓶、玻璃瓶等进行检测并计数&#xff0c;辅助计算机瓶子生产回收等工序。本文详细介绍深度学习的瓶子检测软件&#xff0c;在介绍算法原理的同时&#xff0c;给出Python的…

Mongodb 常用基本语法与操作

常用操作 1、 Help查看命令提示 db.help(); 2、 切换/创建数据库 use test 如果数据库不存在&#xff0c;则创建数据库&#xff0c;否则切换到指定数据库 3、 查询所有数据库 show dbs; 4、 删除当前使用数据库 db.dropDatabase(); 5、 查看当前使用的数据库 db.getName(); 6、…

【STL三】序列容器——array容器

【STL三】序列容器——array一、array简介二、头文件三、模板类四、成员函数1、迭代器2、元素访问3、容量4、操作五、demo1、容量&#xff08;不使用迭代器&#xff09;2、使用迭代器3、元素访问 at()、front()、back()、data()一、array简介 array 容器是 C 11 标准中新增的序…

【面试题】大厂面试官:你做过什么有亮点的项目吗?

大厂面试题分享 面试题库前后端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面试题库前言大厂面试中除了问常见的算法网络基础&#xff0c;和一些八股文手写体之外&#xff0c;经常出现的一个问题就是&#xff0c;你做过什么项目…

WEB安全基础知识

WEB安全基础知识 渗透测试中的常用编码 1. 页面编码 在网页设置网页编码 在<head></head>中加入设置特定html标签 <meta charset"utf-8" /> 这样页面的编码就会变成utf-8 &#xff0c;如果没有设置编码就会使用默认 的编码&#xff0c;而浏览器默…

【JDBC】JDBC 简介 ( JDBC 概念 | JDBC 本质 | 使用 JDBC 操作数据库的好处 | JDBC 提供的 API 组件 )

文章目录一、JDBC 概念二、JDBC 本质三、使用 JDBC 操作数据库的好处四、JDBC 提供的 API 组件一、JDBC 概念 JDBC 全称 Java DataBase Connectivity , 中文名称是 Java 数据库连接 ; JDBC 是 通过 Java 代码 操作 关系型 数据库 的 API ; JDBC 提供了 与 数据库平台 无关的…
最新文章