• 坦克大战②


    上文中, 我们学习到了 坦克大战系列一:从零编写【坦克大战】

    在这里插入图片描述

    1. 我方坦克发射单颗子弹

    1. 当发射一颗子弹后,就相当于启动一个线程来控制它的位置坐标;
    2. Hero[我方坦克]有子弹的对象,当按下J时,就创建一个发射子弹的线程,通过坐标变化让子弹不停的移动,形成一个射击的效果;
    3. 我们的MyPanel类需要通过不停地重绘画面来重绘子弹,这样才能让子弹实时出现在画面上;
    4. 当子弹移动到面板的边界时,就应该销毁(isLive设置为false,线程结束);
    1. 创建子弹类
    public class Bullet implements Runnable {//子弹创建线程
        private int x;//子弹x坐标
        private int y;//y坐标
        private int speed = 2;//速度
        private int direct;//方向
        private boolean isLive = true;
    
        public Bullet(int x, int y, int direct) {//子弹初始位置
            this.x = x;
            this.y = y;
            this.direct = direct;
        }
    
        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 boolean isLive() {
            return isLive;
        }
    
        public void setLive(boolean live) {
            isLive = live;
        }
    
        @Override
        public void run() {
            while (true) {
                if (direct == 0) {//线程中根据方向调节坐标
                    y -= speed;
                } else if (direct == 1) {
                    x += speed;
                } else if (direct == 2) {
                    y += speed;
                } else if (direct == 3) {
                    x -= speed;
                }
                System.out.println("子弹 x=" + x + ",y=" + y);
                if (!(x >= 0 && x <= 500 && y >= 0 && y <= 400)) {
                    System.out.println("子弹超出范围,线程结束");
                    isLive = false;
                    break;
                }
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    1. 坦克射击子弹
    public class Hero extends Tank {
        private Bullet bullet;//坦克射击子弹
        public Hero(int x, int y) {//坦克的初始位置
            super(x, y);
        }
    
        public Bullet getBullet() {
            return bullet;
        }
    
        public void shotBullet() {//根据坦克的方向确定子弹的位置
            switch (getDirect()) {
                case 0:
                    bullet = new Bullet(getX() + 20, getY(), 0);
                    break;
                case 1:
                    bullet = new Bullet(getX() + 60, getY() + 20, 1);
                    break;
                case 2:
                    bullet = new Bullet(getX() + 20, getY() + 60, 2);
                    break;
                case 3:
                    bullet = new Bullet(getX(), getY() + 20, 3);
                    break;
            }
            new Thread(bullet).start();//发射子弹之前要确定子弹的位置和射击方向
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    1. 面板通过线程实现重绘
    new Thread(myPanel).start();
    
    • 1
    @Override
        public void run() {
            while (true) {
                this.repaint();//面板时刻被重绘
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    1. 画出子弹图形
    if (hero.getBullet() != null && hero.getBullet().isLive() != false) {
                System.out.println("子弹被重绘");
                g.fillOval(hero.getBullet().getX()-2, hero.getBullet().getY()-2, 4, 4);
            }
    
    • 1
    • 2
    • 3
    • 4
    1. 按J发射子弹
    if (e.getKeyCode() == KeyEvent.VK_J) {
                System.out.println("发射子弹");
                hero.shotBullet();
            }
    
    • 1
    • 2
    • 3
    • 4

    2.敌方坦克发射子弹

    让敌方坦克也能够发射子弹(可以有多颗子弹)

    1. 在敌人坦克类,使用Vector保存多个Bullet;
      在这里插入图片描述
    2. 当每创建一个敌人坦克对象时,给该敌人坦克对象初始化一个Bullet对象,同时启动Bullet线程;
      在这里插入图片描述
    3. 在绘制敌人坦克时,需要遍历敌人坦克对象Vector。绘制所有的子弹,当子弹isLive为false时,就从Vecotr上移除;
      在这里插入图片描述

    3. 击中敌方坦克

    1. 给敌方坦克类创建一个isLive属性
      在这里插入图片描述
    1. 在MyPanel类中判断我方子弹是否击中敌方坦克
      调用时需要注意:(1)我方的子弹参数必须是存活的;(2)敌方坦克不止一辆;
    //判断我方子弹是否击中敌方坦克
        public void hitTank(Bullet bullet, EnemyTank enemyTank) {
            switch (enemyTank.getDirect()) {
                case 0://向上
                case 2://向下
                    if (bullet.getX() > enemyTank.getX() && bullet.getX() < enemyTank.getX() + 40
                            && bullet.getY() > enemyTank.getY() && bullet.getY() < enemyTank.getY() + 60) {
                        bullet.setLive(false);
                        enemyTank.setLive(false);
                    }
                    break;
                case 1://向右
                case 3://向左
                    if (bullet.getX() > enemyTank.getX() && bullet.getX() < enemyTank.getX() + 60
                            && bullet.getY() > enemyTank.getY() && bullet.getY() < enemyTank.getY() + 40) {
                        bullet.setLive(false);
                        enemyTank.setLive(false);
                    }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    1. 什么时候判断我方坦克是否击中敌方坦克?(run方法中判断)
      在这里插入图片描述
    2. 在绘制敌方坦克以及绘制敌方坦克的子弹时,要先判断敌方坦克是否还存活;
      在这里插入图片描述
    3. 子弹有两种途径被销毁:第一是碰到边界,第二是碰到敌方坦克;
      在Bullet类中加个条件
      在这里插入图片描述

    4. 爆炸效果

    1. 创建一个炸弹类;
    public class Bomb {//定义一个炸弹类
        int x,y;
        int life;//炸弹的生命周期
        boolean isLive = true;//炸弹是否存活
    
        public Bomb(int x, int y) {
            this.x = x;
            this.y = y;
        }
    
        //减少生命值
        public void HpDown() {
            if (life > 0) {
                life--;
            } else {
                isLive = false;
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    1. 在MyPanel类创建炸弹集合
      在这里插入图片描述
      2.1 在out文件夹的chapter18文件夹的下一级里放入炸弹爆炸的图片;
      在这里插入图片描述
      2.2在MyPanel构造器中初始化图片对象
      在这里插入图片描述
    2. 在hitTank()方法中,当子弹击中坦克时,坦克被销毁,从坦克集合中去除这辆坦克;并且创建一个bomb对象加入到bombs集合中;
      在这里插入图片描述
    3. 如果bombs集合中有炸弹,就绘制;
    //绘制爆炸图片
            for (int i = 0; i < bombs.size(); i++) {
                Bomb bomb = bombs.get(i);//取出炸弹
                if (bomb.getLife() > 6) {//根据当前这个bomb对象的life值判断绘制哪张图片
                    g.drawImage(image1, bomb.getX(), bomb.getY(), 40, 60, this);
                } else if (bomb.getLife() > 3) {
                    g.drawImage(image2, bomb.getX(), bomb.getY(), 40, 60, this);
                } else {
                    g.drawImage(image3, bomb.getX(), bomb.getY(), 40, 60, this);
                }
                //每绘制一次,生命值减一
                bomb.HpDown();
                //最后不要忘了移除集合中的炸弹
                if (bomb.getLife() == 0) {
                    bombs.remove(bomb);
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5. 敌方坦克自由移动

    1. 将敌方坦克当做线程使用,需要让敌方坦克类继承Runnable接口实现线程,在run方法里实现坦克的自由移动;
    @Override
        public void run() {
            while (true) {
                double random = Math.random();
                long beginTime = System.currentTimeMillis();
                while (true) {
                    if (random < 0.25) {
                        moveUp();
                        setDirect(0);
                    } else if (random >= 0.25 && random < 0.5) {
                        moveRight();
                        setDirect(1);
                    } else if (random >= 0.5 && random < 0.75) {
                        moveDown();
                        setDirect(2);
                    } else {
                        moveLeft();
                        setDirect(3);
                    }
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    long nowTime = System.currentTimeMillis();
                    if (nowTime - beginTime > 2000) {
                        break;
                    }
                    if (!(isLive == true)) {
                        break;
                    }
                }
                if (!(isLive == true)) {
                    break;
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    改进后:

    @Override
        public void run() {
            while (true) {
                switch (getDirect()) {
                    case 0:
                        for (int i = 0; i < 30; i++) {
                            moveUp();
                            try {
                                Thread.sleep(50);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        break;
                    case 1:
                        for (int i = 0; i < 30; i++) {
                            moveRight();
                            try {
                                Thread.sleep(50);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        break;
                    case 2:
                        for (int i = 0; i < 30; i++) {
                            moveDown();
                            try {
                                Thread.sleep(50);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        break;
                    case 3:
                        for (int i = 0; i < 30; i++) {
                            moveLeft();
                            try {
                                Thread.sleep(50);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        break;
                }
                int random = (int) (Math.random() * 4);
                setDirect(random);
                if (!(isLive == true)) {
                    break;
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    1. 在创建敌人坦克对象时启动线程;
      在这里插入图片描述

    6.移动范围问题

    我方坦克规定移动范围

    if (e.getKeyCode() == KeyEvent.VK_W) {
                if (hero.getY() > 0) {
                    hero.moveUp();
                    hero.setDirect(0);
                }
            } else if (e.getKeyCode() == KeyEvent.VK_D) {
                if (hero.getX() + 60 < 1000) {
                    hero.moveRight();
                    hero.setDirect(1);
                }
            } else if (e.getKeyCode() == KeyEvent.VK_S) {
                if (hero.getY() + 60 < 600) {
                    hero.moveDown();
                    hero.setDirect(2);
                }
            } else if (e.getKeyCode() == KeyEvent.VK_A) {
                if (hero.getX() > 0) {
                    hero.moveLeft();
                    hero.setDirect(3);
                }
            }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    敌方坦克规定移动范围

    switch (getDirect()) {
                    case 0://for (int i = 0; i < 30; i++) {
                            if (getY() > 0) {
                                moveUp();
                            }
                            try {
                                Thread.sleep(50);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        break;
                    case 1://for (int i = 0; i < 30; i++) {
                            if (getX()+60 < 1000) {
                                moveRight();
                            }
                            try {
                                Thread.sleep(50);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        break;
                    case 2://for (int i = 0; i < 30; i++) {
                            if (getY()+60 < 600) {
                                moveDown();
                            }
                            try {
                                Thread.sleep(50);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        break;
                    case 3://for (int i = 0; i < 30; i++) {
                            if (getX() > 0) {
                                moveLeft();
                            }
                            try {
                                Thread.sleep(50);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        break;
                }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    7. 我方坦克发射多颗子弹

    1. 需求一:我方坦克发射的子弹消亡后,才可以发射新的子弹;

    思路:(1)在按下J键时,我们判断当前hero对象的子弹,是否已经销毁;
    (2)如果没有销毁,就不去触发shotBullt()方法;
    (3)如果已经销毁,才去触发shotBullt()方法;
    在这里插入图片描述
    发现的第一颗子弹射出边界销毁后,第二颗子弹发射不出来;
    改进:
    在这里插入图片描述

    扩展:我方坦克发射多颗子弹
    在这里插入图片描述
    在这里插入图片描述
    在绘制我方子弹时,需要遍历hero的bullets子弹集合
    在这里插入图片描述
    or(不清楚为什么要判断非空)
    在这里插入图片描述
    keyPress中条件去掉
    在这里插入图片描述
    如何控制面板上同时只能出现5颗子弹
    在这里插入图片描述
    or
    在这里插入图片描述
    当我方坦克可以发射多颗子弹时,在判断我方子弹是否命中敌方坦克时,需要拿出我方子弹集合中的每一颗子弹,和敌方坦克集合中的每一辆坦克进行判断;然后在MyPanel类的run方法中调用即可;
    在这里插入图片描述

    8. 敌方发射的子弹消亡后可以再发射子弹

    在地方坦克类中创建这个方法

    public void createShot() {
            switch (getDirect()) {
                case 0://上
                    bullet = new Bullet(getX() + 20, getY(), 0);
                    break;
                case 1://右
                    bullet = new Bullet(getX() + 60, getY() + 20, 1);
                    break;
                case 2://下
                    bullet = new Bullet(getX() + 20, getY() + 60, 2);
                    break;
                case 3://:
                    bullet = new Bullet(getX(), getY() + 20, 3);
                    break;
            }
            bullets.add(bullet);
            new Thread(bullet).start();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    方案一:可以在paint()方法中,在移除被销毁的子弹时调用createShot()方法,给这辆坦克再赋予一颗子弹;(老师没有使用)
    在这里插入图片描述
    方案二:在敌人坦克类的run方法里,当子弹集合里没有子弹时,创建子弹;在这里,当坦克掉头后才能发射子弹;
    在这里插入图片描述
    在这里插入图片描述

    9. 我方坦克爆炸

    需求:当敌方坦克击中我方坦克时,我方坦克消失,并出现爆炸效果;

    1. 在父类坦克中添加isLive属性;
      在这里插入图片描述
    2. 编写一个方法,判断敌人坦克是否击中我方坦克;
      在这里插入图片描述
    3. 将hitTank()方法的参数修改如下,造成多态效果(可以实现重载,这里使用多态)在这里插入图片描述
    4. 在绘制我方坦克时,加上判断条件;
      在这里插入图片描述
      5.hero的方法中加上一个判断条件 ! isLive,以做到当我方坦克销毁后,不能再发射子弹在这里插入图片描述

    ❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀坦克大战系列三:从零编写【坦克大战】❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀❀

  • 相关阅读:
    Rust腐蚀服务器定制地图开服
    C--函数指针和回调函数
    【数据结构】链表(及其单链表实现)
    【Android打包】gradlew : 无法将“gradlew”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。
    (最新版2022版)剑指offer之动态规划题解
    含文档+PPT+源码等]精品微信小程序食堂订餐点餐项目+后台管理系统|前后分离VUE[包运行成功]微信小程序项目源码Java毕业设计
    大模型chatgpt4分析功能初探
    如何通过上传图片判断参数设置是否正确
    async-validator 源码学习笔记(四):validator
    解决Linux提示:/lib64/libc.so.6: version GLIBC_2.XX‘ not found
  • 原文地址:https://blog.csdn.net/qq_18817831/article/details/128066019