• 【JAVA学习笔记】63 -坦克大战1.3-敌方发射子弹,击中坦克消失并爆炸,敌人坦克随机移动,规定范围限制移动


    项目代码

    https://github.com/yinhai1114/Java_Learning_Code/tree/main/IDEA_Chapter18/src/com/yinhai/tankgame1_3

    目录

    项目代码

    〇、要求

    一、敌人坦克也能发射子弹

    思路

    1.新建Enemy类

    2.MyPanel类的paint方法

    效果

    二、击中敌人坦克时消失

    思路

    1.判断是否击中

    2.在画板线程里调用方法

    3.如何让坦克消失

    4.记得将Bullet线程以通知的方式结束

    效果

    二(加强)、爆炸效果

    思路 

    1.定义Bomb类

    2.添加Bomb对象

    3.通过在paint方法内绘出炸弹效果

    效果

    三、敌人坦克随机移动

    思路

    1.将enemy设置为多线程

    2.move方法

    效果

    四、控制我方坦克和敌人的坦克在规定范围内移动

    思路

    1.定义map类,编写判断方法

    2.在hero和enemy的移动方法内调用该方法

    效果


    〇、要求

    增加功能

    1.让敌人的坦克也能够发射子弹(可以有多颗子弹)

    2.当我方坦克击中敌人坦克时,敌人的坦克就消失,如果能做出爆炸效果更好.

    3.让敌人的坦克也可以自由随机的上下左右移动

    4.控制我方的坦克和敌人的坦克在规定的范围移动

    一、敌人坦克也能发射子弹

    思路

            1.敌人的坦克也使用Vector来保存它的子弹,因为多个敌人有多个子弹

            2.调用设计方法,就给该坦克初始化一个Shot对象,同时启动Shot

            3.在绘制敌人坦克时,需要Enemy坦克,如果子弹消亡,记得回收该子弹

    1.新建Enemy类

    这一段代码类似于Hero类,有shotBullet方法,该方法创建了子弹对象,和1.1版本的功能一样,启动shot线程,这个类也创建了enemyBullets用于存放敌人射出的子弹对象

    1. public class Enemy extends Tank {
    2. Vector enemyBullets = new Vector<>();
    3. private int type = 1;
    4. public Enemy(int x, int y,double speed) {
    5. super(x, y,speed);
    6. setDirect(2);
    7. }
    8. public int getTYPE() {
    9. return type;
    10. }
    11. public Bullet shotBullet(){
    12. Bullet bullet = null;
    13. switch (getDirect()){
    14. case 0:
    15. bullet = new Bullet(this.getX() + 18,this.getY() - 10,50,getDirect());
    16. break;
    17. case 1:
    18. bullet = new Bullet(this.getX() + 60,this.getY() +18,50,getDirect());
    19. break;
    20. case 2:
    21. bullet = new Bullet(this.getX() + 18,this.getY() +60,50,getDirect());
    22. break;
    23. case 3:
    24. bullet = new Bullet(this.getX() - 10,this.getY()+18,50,getDirect());
    25. break;
    26. }
    27. enemyBullets.add(bullet);
    28. Bullet.Shot shot = bullet.new Shot();
    29. Thread thread = new Thread(shot);
    30. thread.start();
    31. return bullet;
    32. }
    33. }

    2.MyPanel类的paint方法

    该方法改进,将1.2的绘画子弹方法进行封装,paintBullet方法,其本质还是1.2版本的思路,循环遍历列表,消亡我就添加到消亡列表,然后remove子弹列表里的所有消亡列表,最后清空消亡列表,我们的Enemy保存为Vector类,记得取出后再调用特有属性

    1. public void paint(Graphics g) {
    2. super.paint(g);
    3. paintBullet(hero.heroBullets, g);
    4. for (int i = 0;i < enemies.size();i++){
    5. Enemy enemy = enemies.get(i);
    6. enemy.shotBullet();
    7. paintBullet(enemy.enemyBullets, g);
    8. }
    9. }
    10. public void paintBullet(Vector bullets,Graphics g){
    11. Vector unliveBullets = new Vector<>();
    12. bullets.removeAll(unliveBullets);
    13. for (int i = 0; i < bullets.size(); i++) {
    14. Bullet bullet = bullets.get(i);
    15. if(!bullet.isLive()){
    16. unliveBullets.add(bullet);
    17. }
    18. if(bullet != null && bullet.isLive()){
    19. drawBullet(g,bullet,hero.getTYPE());
    20. }
    21. }
    22. unliveBullets.clear();
    23. }

    效果

    最后调用shotBullet即可发射子弹,将调用方法写在画板的paint方法里,效果如下

    二、击中敌人坦克时消失

    思路

            1.应当编写一个判断方法,判断是否击中

            2.如果击中,敌人坦克消亡应当有一个属性值,将其置为false,子弹也需要置为false

            3.什么时候判断,应当在一个线程的循环里进行重复的判断

            4.应当再paint方法内停止绘画已经消亡的坦克,并且溢出列表内的坦克

    1.判断是否击中

    1)在画板中判断是否击中,写两个方法纯粹是塞到一块太难看了,一个方法hitEnemyTank是负责判断子弹的范围,另外一个hitIf是循环取出子弹和循环取出敌人对象塞到hitEnemyTank方法里,如果击中,将新增的isLive置为false;

    1. public static void hitEnemyTank(Bullet b, Enemy enemy) {
    2. switch (enemy.getDirect()) {
    3. case 0:
    4. case 2:
    5. if (b.getX() > enemy.getX() && b.getX() < enemy.getX() + 40
    6. && b.getY() > enemy.getY() && b.getY() < enemy.getY() + 60) {
    7. b.setLive(false);
    8. enemy.setLive(false);
    9. }break;
    10. case 1:
    11. case 3:
    12. if (b.getX() > enemy.getX() && b.getX() < enemy.getX() + 60
    13. && b.getY() > enemy.getY() && b.getY() < enemy.getY() + 40) {
    14. b.setLive(false);
    15. enemy.setLive(false);
    16. }break;
    17. }
    18. }
    19. public static void hitIf(Hero hero,Vector enemies){
    20. for (int i = 0; i < hero.heroBullets.size(); i++) {
    21. if(hero.heroBullets.get(i) == null){
    22. continue;
    23. }
    24. Bullet bullet = hero.heroBullets.get(i);
    25. for (int j = 0; j < enemies.size() ; j++) {
    26. Enemy enemy = enemies.get(j);
    27. hitEnemyTank(bullet,enemy);
    28. }
    29. }
    30. }

    2.在画板线程里调用方法

            

    3.如何让坦克消失

    在paint方法内设置门槛,循环取出列表内的敌人,如果为空就继续跳到for开头(因为我们可能已经移除过一次中间的元素,如果不判断会抛出异常)。不为空,获取该元素,并查看是否还存活,如果不存活remove该元素,然后继续循环,最后绘出坦克,注意这里为什么要使用i--,因为不使用i--会跳过一个敌人

     remove会自动前移数组,如果不i--,会导致这次线程不绘画本应该存在的下一个坦克,下一个坦克会在下一次线程中继续被绘出来,所以会闪一下(来自GPT的帮助)

    1. @Override
    2. public void paint(Graphics g) {
    3. super.paint(g);
    4. for (int i = 0; i < enemies.size(); i++) {
    5. if (enemies.get(i) == null) {
    6. continue;
    7. }
    8. Enemy enemy = enemies.get(i);
    9. if(!enemy.isLive()){
    10. enemies.remove(enemy);
    11. i--;//为什么需要i-- 是因为在处理敌人数组时,如果你使用 remove 方法来删除一个元素,它会将数组中的元素往前移动填补被删除元素的位置,这样数组中不会存在 null 元素。
    12. continue;
    13. }
    14. drawTank(enemy.getX(), enemy.getY(), g, enemy.getDirect(), enemy.getTYPE());
    15. }
    16. }

    4.记得将Bullet线程以通知的方式结束

    中间量为isLive

    效果

    二(加强)、爆炸效果

    思路 

            使用绘图里的输出图片完成

    坦克只在被击中的时候死亡,所以当一个坦克死亡的时候把坦克的位置用这三张图片替代,然后如果不做成一个像子弹一样的类的话很难保证不堵塞,因为图片太快了需要休眠让图片依次走,单独写一个炸弹类,类内定义一个Life,每执行一次线程就life--,相当于执行完爆炸效果需要9个线程的时间

    1.定义Bomb

    该类写了一个life,用于执行坦克的图片的消亡过程

    1. public class Bomb {
    2. private int x;
    3. private int y;
    4. private int life = 9;
    5. private boolean isLive = true;
    6. public int getLife() {
    7. return life;
    8. }
    9. public int getX() {
    10. return x;
    11. }
    12. public int getY() {
    13. return y;
    14. }
    15. public Bomb(int x, int y) {
    16. this.x = x;
    17. this.y = y;
    18. }
    19. public boolean isLive() {
    20. return isLive;
    21. }
    22. public void lifeDown(){
    23. if(life > 0){
    24. --life;
    25. }else{
    26. isLive = false;
    27. }
    28. }
    29. }

    2.添加Bomb对象

    当我们击中坦克时,在该坦克处创建一个Bomb对象,该对象记录当前enemy的坐标。

    1. public void hitEnemyTank(Bullet b, Enemy enemy) {
    2. switch (enemy.getDirect()) {
    3. case 0:
    4. case 2:
    5. if (b.getX() > enemy.getX() && b.getX() < enemy.getX() + 40
    6. && b.getY() > enemy.getY() && b.getY() < enemy.getY() + 60) {
    7. b.setLive(false);
    8. enemy.setLive(false);
    9. bombs.add(new Bomb(enemy.getX(), enemy.getY()));
    10. System.out.println("子弹击中");
    11. }
    12. break;
    13. case 1:
    14. case 3:
    15. if (b.getX() > enemy.getX() && b.getX() < enemy.getX() + 60
    16. && b.getY() > enemy.getY() && b.getY() < enemy.getY() + 40) {
    17. b.setLive(false);
    18. enemy.setLive(false);
    19. bombs.add(new Bomb(enemy.getX(), enemy.getY()));
    20. System.out.println("子弹击中");
    21. }
    22. break;
    23. }
    24. }

    3.通过在paint方法内绘出炸弹效果

    因为paint方法是在线程内被run方法反复执行,所以每调用一次bombEffect都会让bomb对象的life--,当处理完后移除该炸弹对象,注意如果只设置一个对象存放bomb会导致多个坦克的爆炸效果出现问题 

    1. public void paint(Graphics g) {
    2. super.paint(g);
    3. bombEffect(g);
    4. }
    5. public void bombEffect(Graphics g) {
    6. for (int i = 0; i < bombs.size(); i++) {
    7. Bomb bomb = bombs.get(i);
    8. if(bomb.getLife()>0){
    9. bomb.lifeDown();
    10. if (bomb.getLife() > 6) {
    11. g.drawImage(image, bomb.getX(), bomb.getY(), 60, 60, this);
    12. } else if (bomb.getLife() > 3) {
    13. g.drawImage(image1, bomb.getX(), bomb.getY(), 60, 60, this);
    14. } else {
    15. g.drawImage(image2, bomb.getX(), bomb.getY(), 60, 60, this);
    16. }
    17. }else {
    18. bombs.remove(bomb);
    19. }
    20. }
    21. }

    效果

            目前存在一个问题,就是第一个对象不会正常显示爆炸效果,考虑并行导致出现单线程里语句的干扰,找不到合理的解释。

    三、敌人坦克随机移动

    思路

    敌人坦克可以自由移动,则需要将其设置为多线程(多个敌人同时移动), 其次在重写的run方法内实现randomMove方法,实现随机方向,加一个判断是否移动,然后再定义个值,判断是否转向

    1.将enemy设置为多线程

    设置为多线程后,重写run方法,在run里实现坦克的移动,记得在创建enemy对象的地方启动该线程

    2.move方法

    使用math.random的方式来随机移动,

    1. public class Enemy extends Tank implements Runnable {
    2. Vector enemyBullets = new Vector<>();
    3. private int type = 1;
    4. private boolean isLive = true;
    5. private int count;
    6. public boolean isLive() {
    7. return isLive;
    8. }
    9. public void randomMove() {
    10. //先随机是否移动
    11. if ((int)(Math.random() * 4) == 3) {//判断是否可以移动,0-3,四分之3的概率可以移动
    12. return;
    13. }
    14. count++;//一个计数器,增加移动的次数
    15. switch (getDirect()) {//根据方向进行移动
    16. case 0:
    17. moveUp();
    18. break;
    19. case 1:
    20. moveRight();
    21. break;
    22. case 2:
    23. moveDown();
    24. break;
    25. case 3:
    26. moveLeft();
    27. break;
    28. }
    29. if (count >= (int) (Math.random() * 40)) {//当移动的次数大于某个值的时候,改变方向,0-39的范围
    30. setDirect((int) (Math.random() * 4));//随机给一个方向
    31. count = 0;//计数为0
    32. }
    33. }
    34. @Override
    35. public void run() {
    36. while (isLive) {
    37. try {
    38. Thread.sleep(500);
    39. randomMove();
    40. } catch (InterruptedException e) {
    41. e.printStackTrace();
    42. }
    43. }
    44. }
    45. }

    效果

    实现了坦克的随机移动

    不过没有设置碰撞,和边界,坦克会瞎跑不见或者叠在其他坦克上

    四、控制我方坦克和敌人的坦克在规定范围内移动

    思路

            创建一个静态的Map,用于表示当前地图的大小,然后在地图类内定义方法判断tank是否还在游戏游戏区域,该方法在tank的move内使用

    1.定义map类,编写判断方法

            在该map类初始化时赋值,然后写判断方法

    注意,判断方法不能写成

    if(tank.x < mapminX){return false;}

    if(tank.x > mapmaxX){return false;}

    if(tank.x < mapminY){return false;}

    if(tank.x > mapmaxY){return false;}

    return ture;

            写成这样会导致方法调用在移动执行之前,但是每次判断都是false,导致执行不到移动方法,后果就是我们的tank被边界抓住了,无法移动,所以我们获取面向,如果是上,我们就只限制tank的y不能大于minY即可。为什么mapmaxX要减tank.speed,因为如果不减,如果本来的边界是1600 - 60 = 1540 ,判断完之后坦克是还能往右边走的,就会 变成 1540 + speed = 1560的位置才不能往前走,炮管会突出去。所以最好是加个speed。

    1. public class Map {
    2. private static int mapMinX;
    3. private static int mapMaxX;
    4. private static int mapMinY;
    5. private static int mapMaxY;
    6. public static int getMapMinX() {
    7. return mapMinX;
    8. }
    9. public static int getMapMaxX() {
    10. return mapMaxX;
    11. }
    12. public static int getMapMinY() {
    13. return mapMinY;
    14. }
    15. public static int getMapMaxY() {
    16. return mapMaxY;
    17. }
    18. public Map(int mapMinX, int mapMinY, int mapMaxX, int mapMaxY) {
    19. this.mapMinX = mapMinX;
    20. this.mapMinY = mapMinY;
    21. this.mapMaxX = mapMaxX;
    22. this.mapMaxY = mapMaxY;
    23. }
    24. public static boolean scopeIf(Tank tank) {
    25. switch (tank.getDirect()) {
    26. case 0:
    27. if (tank.getY() < mapMinY +tank.getSpeed()) {
    28. return false;
    29. }
    30. break;
    31. case 1:
    32. if (tank.getX() > mapMaxX - 60 - tank.getSpeed()) {
    33. return false;
    34. }
    35. break;
    36. case 2:
    37. if (tank.getY() > mapMaxY - 60 - tank.getSpeed()) {
    38. return false;
    39. }
    40. break;
    41. case 3:
    42. if (tank.getX() < mapMinX + tank.getSpeed()) {
    43. return false;
    44. }
    45. break;
    46. }
    47. return true;
    48. }
    49. }

    2.在hero和enemy的移动方法内调用该方法

    这样的好处就是不用动之前的代码,动来动去自己都忘了

    1. public void heroMove() {
    2. if(!Map.scopeIf(this)){
    3. return;
    4. }
    5. {/*...根据面向执行移动*/}
    6. }
    7. public void randomMove() {
    8. //先随机是否移动
    9. if ((int) (Math.random() * 4) == 3) {
    10. return;
    11. }
    12. if(!Map.scopeIf(this)){
    13. setDirect((int) (Math.random() * 4));
    14. return;
    15. }
    16. {/*...根据面向执行移动*/}
    17. }

    效果

    现在都已经限制在这个黑色区域内了包括hero坦克

  • 相关阅读:
    使用 Grafana 使用JSON API 请求本地接口 报错 bad gateway(502)解决
    R语言dplyr包summarise_at函数计算dataframe数据中多个数据列(通过向量指定)的计数个数、均值和中位数、使用funs函数指定函数列表
    ChatGPT追祖寻宗:GPT-3技术报告要点解读
    代码+视频,R语言对数据进行多重插补后回归分析
    bandit agent下棋AI(python编写) 通过强化学习RL 使用numpy
    e签宝,再「进化」
    【算法-链表2】反转链表 和 两两交换链表节点
    Win11的WSL2系统更换磁盘和wsl使用简介
    IPSEC VXN 及 NAT BYPASS配置及详解
    Spring Cloud
  • 原文地址:https://blog.csdn.net/qq_41891655/article/details/134233744