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

news2024/11/25 1:08:13

项目代码

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

〇、要求

增加功能

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

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

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

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

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

思路

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

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

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

1.新建Enemy类

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

public class Enemy extends Tank {
    Vector<Bullet> enemyBullets = new Vector<>();
    private int type = 1;
    public Enemy(int x, int y,double speed) {
        super(x, y,speed);
        setDirect(2);
    }

    public int getTYPE() {
        return type;
    }
    public Bullet shotBullet(){
        Bullet bullet = null;
        switch (getDirect()){
            case 0:
                bullet = new Bullet(this.getX() + 18,this.getY() - 10,50,getDirect());
                break;
            case 1:
                bullet  = new Bullet(this.getX() + 60,this.getY() +18,50,getDirect());
                break;
            case 2:
                bullet = new Bullet(this.getX() + 18,this.getY() +60,50,getDirect());
                break;
            case 3:
                bullet = new Bullet(this.getX() - 10,this.getY()+18,50,getDirect());
                break;
        }
        enemyBullets.add(bullet);
        Bullet.Shot shot = bullet.new Shot();
        Thread thread = new Thread(shot);
        thread.start();
        return bullet;
    }
}

2.MyPanel类的paint方法

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

public void paint(Graphics g) {

        super.paint(g);
        paintBullet(hero.heroBullets, g);
        for (int i = 0;i < enemies.size();i++){
            Enemy enemy = enemies.get(i);
            enemy.shotBullet();
            paintBullet(enemy.enemyBullets, g);
        }
    }
    public void paintBullet(Vector<Bullet> bullets,Graphics g){
        Vector<Bullet> unliveBullets = new Vector<>();
        bullets.removeAll(unliveBullets);
        for (int i = 0; i < bullets.size(); i++) {
            Bullet bullet = bullets.get(i);
            if(!bullet.isLive()){
                unliveBullets.add(bullet);
            }
            if(bullet != null && bullet.isLive()){
                drawBullet(g,bullet,hero.getTYPE());
            }
        }
        unliveBullets.clear();
    }

效果

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

二、击中敌人坦克时消失

思路

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

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

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

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

1.判断是否击中

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

    public static void hitEnemyTank(Bullet b, Enemy enemy) {
        switch (enemy.getDirect()) {
            case 0:
            case 2:
                if (b.getX() > enemy.getX() && b.getX() < enemy.getX() + 40
                        && b.getY() > enemy.getY() && b.getY() < enemy.getY() + 60) {
                    b.setLive(false);
                    enemy.setLive(false);
                }break;
            case 1:
            case 3:
                if (b.getX() > enemy.getX() && b.getX() < enemy.getX() + 60
                        && b.getY() > enemy.getY() && b.getY() < enemy.getY() + 40) {
                    b.setLive(false);
                    enemy.setLive(false);
                }break;

        }
    }
    public static void hitIf(Hero hero,Vector<Enemy> enemies){
        for (int i = 0; i < hero.heroBullets.size(); i++) {
            if(hero.heroBullets.get(i) == null){
                continue;
            }
            Bullet bullet = hero.heroBullets.get(i);
            for (int j = 0; j < enemies.size() ; j++) {
                Enemy enemy = enemies.get(j);
                hitEnemyTank(bullet,enemy);
            }
        }
    }

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

        

3.如何让坦克消失

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

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

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

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

中间量为isLive

效果

二(加强)、爆炸效果

思路 

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

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

1.定义Bomb类

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

public class Bomb {
    private int x;
    private int y;
    private int life = 9;
    private boolean isLive = true;

    public int getLife() {
        return life;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    public Bomb(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public boolean isLive() {
        return isLive;
    }

    public void lifeDown(){
            if(life > 0){
                --life;
            }else{
                isLive = false;
            }
    }
}

2.添加Bomb对象

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

 public void hitEnemyTank(Bullet b, Enemy enemy) {
        switch (enemy.getDirect()) {
            case 0:
            case 2:
                if (b.getX() > enemy.getX() && b.getX() < enemy.getX() + 40
                        && b.getY() > enemy.getY() && b.getY() < enemy.getY() + 60) {
                    b.setLive(false);
                    enemy.setLive(false);
                    bombs.add(new Bomb(enemy.getX(), enemy.getY()));
                    System.out.println("子弹击中");
                }
                break;
            case 1:
            case 3:
                if (b.getX() > enemy.getX() && b.getX() < enemy.getX() + 60
                        && b.getY() > enemy.getY() && b.getY() < enemy.getY() + 40) {
                    b.setLive(false);
                    enemy.setLive(false);
                    bombs.add(new Bomb(enemy.getX(), enemy.getY()));
                    System.out.println("子弹击中");
                }
                break;

        }
    }

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

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

    public void paint(Graphics g) {

        super.paint(g);
        bombEffect(g);
    }
     public void bombEffect(Graphics g) {
        for (int i = 0; i < bombs.size(); i++) {
            Bomb bomb = bombs.get(i);
            if(bomb.getLife()>0){
                bomb.lifeDown();
                if (bomb.getLife() > 6) {
                    g.drawImage(image, bomb.getX(), bomb.getY(), 60, 60, this);
                } else if (bomb.getLife() > 3) {
                    g.drawImage(image1, bomb.getX(), bomb.getY(), 60, 60, this);
                } else {
                    g.drawImage(image2, bomb.getX(), bomb.getY(), 60, 60, this);
                }
            }else {
                bombs.remove(bomb);
            }
        }
    }

效果

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

三、敌人坦克随机移动

思路

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

1.将enemy设置为多线程

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

2.move方法

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

public class Enemy extends Tank implements Runnable {
    Vector<Bullet> enemyBullets = new Vector<>();
    private int type = 1;
    private boolean isLive = true;
    private int count;

    public boolean isLive() {
        return isLive;
    }
    public void randomMove() {
        //先随机是否移动

        if ((int)(Math.random() * 4) == 3) {//判断是否可以移动,0-3,四分之3的概率可以移动
            return;
        }
        count++;//一个计数器,增加移动的次数
        switch (getDirect()) {//根据方向进行移动
            case 0:
                moveUp();
                break;
            case 1:
                moveRight();
                break;
            case 2:
                moveDown();
                break;
            case 3:
                moveLeft();
                break;
        }
        if (count >= (int) (Math.random() * 40)) {//当移动的次数大于某个值的时候,改变方向,0-39的范围
            setDirect((int) (Math.random() * 4));//随机给一个方向
            count = 0;//计数为0
        }
    }

    @Override
    public void run() {
        while (isLive) {
            try {
                Thread.sleep(500);
                randomMove();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

效果

实现了坦克的随机移动

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

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

思路

        创建一个静态的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。

public class Map {
    private static int mapMinX;
    private static int mapMaxX;
    private static int mapMinY;
    private static int mapMaxY;

    public static int getMapMinX() {
        return mapMinX;
    }

    public static int getMapMaxX() {
        return mapMaxX;
    }

    public static int getMapMinY() {
        return mapMinY;
    }

    public static int getMapMaxY() {
        return mapMaxY;
    }

    public Map(int mapMinX, int mapMinY, int mapMaxX, int mapMaxY) {
        this.mapMinX = mapMinX;
        this.mapMinY = mapMinY;
        this.mapMaxX = mapMaxX;
        this.mapMaxY = mapMaxY;
    }

    public static boolean scopeIf(Tank tank) {
        switch (tank.getDirect()) {
            case 0:
                if (tank.getY() < mapMinY +tank.getSpeed()) {
                    return false;
                }
                break;
            case 1:
                if (tank.getX() > mapMaxX - 60 - tank.getSpeed()) {
                    return false;
                }
                break;
            case 2:
                if (tank.getY() > mapMaxY - 60 - tank.getSpeed()) {
                    return false;
                }
                break;
            case 3:
                if (tank.getX() < mapMinX + tank.getSpeed()) {
                    return false;
                }
                break;
        }
        return true;
    }
}

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

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

    public void heroMove() {
        if(!Map.scopeIf(this)){
            return;
        }
        {/*...根据面向执行移动*/}
    }
    public void randomMove() {
        //先随机是否移动
        if ((int) (Math.random() * 4) == 3) {
            return;
        }
        if(!Map.scopeIf(this)){
            setDirect((int) (Math.random() * 4));
            return;
        }
        {/*...根据面向执行移动*/}
    }

效果

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Thread类的基本操作(JAVA多线程)

线程是操作系统中的概念&#xff0c;操作系统内核实现了线程这样的机制&#xff0c;并提供了一些API供外部使用。 JAVA中 Thread类 将系统提供的API又近一步进行了抽象和封装&#xff0c;所以如果想要使用多线程就离不开 Thread 这个类。 线程的创建(Thread类) 在JAVA中 创建…

SPSS多元方差分析

前言&#xff1a; 本专栏参考教材为《SPSS22.0从入门到精通》&#xff0c;由于软件版本原因&#xff0c;部分内容有所改变&#xff0c;为适应软件版本的变化&#xff0c;特此创作此专栏便于大家学习。本专栏使用软件为&#xff1a;SPSS25.0 本专栏所有的数据文件请点击此链接下…

大容量中间继电器 RXMH2 RK223 067 DC110V JOSEF约瑟

系列型号 RXMH2 RK 223 067大容量中间继电器&#xff1b; RXMH2 RK 223 068大容量中间继电器&#xff1b; RXMH2 RK 223 069大容量中间继电器&#xff1b; RXMH2 RK 223 070大容量中间继电器&#xff1b; 一、用途 RXMH2系列大容量中间继电器用于工业自动化控制及电力系统…

Git介绍及使用

目录 一、Git 的基本概念 1. 仓库&#xff08;Repository&#xff09;: 仓库是存储代码的地方。可以通过 命令将本地文件夹初始化为 Git 仓库&#xff0c;并使用 命令从远程仓库克隆到本地 2. 分支&#xff08;Branch&#xff09;: 分支是指从主分支上创建出来的一个分支&…

Python画一个爱心

Python画一个爱心 一、效果图二、Python代码 一、效果图 二、Python代码 import random from math import sin, cos, pi, log from tkinter import *CANVAS_WIDTH 640 # 画布的宽 CANVAS_HEIGHT 480 # 画布的高 CANVAS_CENTER_X CANVAS_WIDTH / 2 # 画布中心的X轴坐标 CA…

DCU集群搭建虚拟环境方法简介

1.conda安装方法&#xff1a; wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh #下载miniconda安装包chmod 750 Miniconda3-latest-Linux-x86_64.sh #添加执行权限bash ./Miniconda3-latest-Linux-x86_64.sh #安装下载的minnconda32.集群安装…

FreeRTOS源码阅读笔记1--task.c

在FreeRTOS中&#xff0c;创建任务有两种方式&#xff1a;动态创建和静态创建。区别就是&#xff1a;动态创建任务的栈和任务TCB是由操作系统动态分配&#xff08;malloc&#xff09;内存空间&#xff0c;任务删除时可以释放内存&#xff08;free&#xff09;&#xff1b;而静态…

单链表(无头单项非循环)

文章目录 前言概述链表的实现初始化遍历单链表创建新节点尾插头插尾删头删单链表的查找在pos位置之前插入一个节点在pos位置删除节点在pos位置后插入节点删除pos后一个节点销毁 结尾 前言 链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通…

HDFS系统操作命令大全

一&#xff0c;前言 HDFS作为分布式存储的文件系统&#xff0c;有其对数据的路径表达方式 HDFS同linux系统一样&#xff0c;均是以/作为根目录的组织形式 linux&#xff1a;/usr/local/hello.txt HDFS&#xff1a;/usr/local/hello.txt 二&#xff0c;如何区分呢&#xff1f; L…

【数据结构】树与二叉树(三):二叉树的定义、特点、性质及相关证明

文章目录 5.1 树的基本概念5.1.1 树的定义5.1.2 森林的定义5.1.3 树的术语5.1.4 树的表示 5.2 二叉树5.2.1 二叉树1. 定义2. 特点3. 性质引理5.1&#xff1a;二叉树中层数为i的结点至多有 2 i 2^i 2i个&#xff0c;其中 i ≥ 0 i \geq 0 i≥0。引理5.2&#xff1a;高度为k的二叉…

yolov5--ptq--qat量化之敏感层分析

敏感层分析&#xff0c;应该是发生在ptq量化之前进行分析的操作&#xff0c;经过该操作&#xff0c;可得出哪些层不适合进行量化&#xff0c;则在接下来ptq时可以手动关闭这些层的量化。 进入敏感层分析函数sensitive_analysis中&#xff0c; 具体流程为&#xff1a; 首先验证…

打印机共享连接0x000003e3错误

重点重点&#xff1a;如使用下面教程还不行的朋友&#xff0c;请这样操作。 不管主机还是客户机&#xff0c;都运行“局域网共享一键修复工具”&#xff0c;并选择“简单共享&#xff08;无密码&#xff09;” 不管主机还是客户机&#xff0c;都操作一次此教程。 不管主机还是客…

Langchain-Chatchat项目:5.1-ChatGLM3-6B工具调用

在语义、数学、推理、代码、知识等不同角度的数据集上测评显示&#xff0c;ChatGLM3-6B-Base 具有在10B以下的基础模型中最强的性能。ChatGLM3-6B采用了全新设计的Prompt格式&#xff0c;除正常的多轮对话外。同时原生支持工具调用&#xff08;Function Call&#xff09;、代码…

如何用思维导图拆书

思维导图是一种非常有用的工具&#xff0c;可以被广泛应用于不同领域的人群。 阅读是我们获取知识和提升自己的重要途径&#xff0c;而做好读书笔记有助于加深对书中内容的理解和记忆。其中&#xff0c;使用思维导图作为读书笔记的工具&#xff0c;不仅能够帮助我们更好地整理…

lombok依赖介绍(帮助我们消除冗长代码,如get,set方法)

前言 lombok 是一个 Java 工具库&#xff0c;通过注解的方式&#xff0c;简化 Java 开发。要想使用 lombok 中的注解&#xff0c;我们需要先引入依赖&#xff0c;推荐看idea必装插件EditStarters&#xff08;快速引入依赖&#xff09;&#xff0c;lombok是⼀款在编译期⽣成代码…

k8s提交spark应用消费kafka数据写入elasticsearch7

一、k8s集群环境 k8s 1.23版本&#xff0c;三个节点&#xff0c;容器运行时使用docker。 spark版本时3.3.3 k8s部署单节点的zookeeper、kafka、elasticsearch7 二、spark源码 https://download.csdn.net/download/TT1024167802/88509398 命令行提交方式 /opt/module/spark…

技术分享 | 被测项目需求你理解到位了么?

需求分析是开始测试工作的第一步&#xff0c;产品会先产出一个需求文档&#xff0c;然后会组织需求宣讲&#xff0c;在需求宣讲中分析需求中是否存在问题&#xff0c;然后宣讲结束后&#xff0c;通过需求文档分析测试点并且预估排期。所以对于需求的理解非常重要。 需求文档 …

壹[1],QT自定义控件创建(QtDesigner)

1&#xff0c;环境 Qt 5.14.2 VS2022 原因&#xff1a;厌烦了控件提升的繁琐设置&#xff0c;且看不到界面预览显示。 2&#xff0c;QT制作自定义控件 2.1&#xff0c;New/其他项目/Qt4 设计师自定义控件 2.2&#xff0c;设置项目名称 2.3&#xff0c;设置 2.4&#xff0c;设…

YOLOv7改进策略:一种新颖的可扩张残差(DWR)注意力模块,增强多尺度感受野特征,助力小目标检测

💡💡💡本文全网首发独家改进:一种新颖的可扩张残差(DWR)注意力模块,加强不同尺度特征提取能力,创新十足,独家首发适合科研 推荐指数:五星 DWR | 亲测在多个数据集能够实现涨点,多尺度特性在小目标检测表现也十分出色。 💡💡💡Yolov5/Yolov7魔术师,独…

【Java 进阶篇】Cookie 使用详解

欢迎阅读本篇博客&#xff0c;我们将深入研究 Java 中的 Cookie&#xff0c;从入门到精通&#xff0c;包括 Cookie 的基本概念、原理、使用方法以及一些高级技巧。无论你是新手还是有经验的开发者&#xff0c;希望这篇博客对你有所帮助。 第一部分&#xff1a;Cookie 是什么&a…