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

项目代码

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的帮助)

    @Overridepublic 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}}@Overridepublic 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.hqwc.cn/news/162807.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pytest中的pytest.ini

[pytest] filterwarnings ignore::DeprecationWarning addopts -v -s markers uat:1 smok:2 log_cli1 xfail_strict True filterwarnings ignore::DeprecationWarning 这个的功能就是 test_login.py::Test_login::test_login_correct_password PASSEDwarnings summary …

【复盘】记录一次JVM 异常问题 java.lang.OutOfMemoryError: unable to create new native thread

背景是最新运营提了一个需求&#xff0c;需要根据用户信息拉去三分机构的信贷数据&#xff0c;需要达到一天百万级别&#xff0c;但是经过实际测试&#xff0c;也只能达到40W量级&#xff0c;具体就是通过起多个Spring Boot项目&#xff0c;每个项目1S拉一个用户&#xff0c;基…

《Webpack 5 基础配置》- 禁止在出现编译错误或警告时,覆盖浏览器全屏显示

Webpack5 overlay 配置地址默认编译错误或警告为 true&#xff0c;即浏览器全屏显示&#xff1b;overlay 属性可以是 boolean 型&#xff0c;也可是 object 类型&#xff1b;还有其它设置说明&#xff0c;详见上述官网地址&#xff1b; module.exports {devServer: {client: {…

【使用Python编写游戏辅助工具】第二篇:键盘监听的应用

前言 这里是【使用Python编写游戏辅助工具】的第二篇&#xff1a;键盘监听的应用。本文主要介绍使用Python实现事件监听功能。 键盘监听是指通过编程的方式监控用户在键盘上的按键操作。 在这里键盘监听的主要用途是&#xff1a; 监听我们按下的按键&#xff0c;如果按下了指…

找出字符串中第一个匹配项的下标

给你两个字符串 haystack 和 needle &#xff0c;请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标&#xff08;下标从 0 开始&#xff09;。如果 needle 不是 haystack 的一部分&#xff0c;则返回 -1 。 示例 1&#xff1a; 输入&#xff1a;haystack “s…

【MongoDB】索引 - 复合索引

一、准备工作 这里准备一些学生数据 db.students.insertMany([{ _id: 1, name: "张三", age: 20, class: { id: 1, name: "1班" }},{ _id: 2, name: "李四", age: 22, class: { id: 2, name: "2班" }},{ _id: 3, name: "王五…

docker存储卷

docker存储卷 COW机制 Docker镜像由多个只读层叠加而成&#xff0c;启动容器时&#xff0c;Docker会加载只读镜像层并在镜像栈顶部添加一个读写层。 如果运行中的容器修改了现有的一个已经存在的 文件&#xff0c;那么该文件将会从读写层下面的只读层复制到读写层&#xff0…

Node Sass version 9.0.0 is incompatible with ^4.0.0.

1.错误产生原因&#xff1a; node、 node-sass 和sass-loader的版本对应问题 2.解决方案&#xff1a; 删除之前的 npm uninstall node-sass sass-loader 安装指定的 npm i node-sass4.14.1 sass-loader7.3.1 --save -dev

【微服务】一体化智慧工地管理平台源码

智慧工地系统是一种利用人工智能和物联网技术来监测和管理建筑工地的系统。它可以通过感知设备、数据处理和分析、智能控制等技术手段&#xff0c;实现对工地施工、设备状态、人员安全等方面的实时监控和管理。 一、智慧工地让工程施工智能化 1、内容全面&#xff0c;多维度数…

STM8单片机在医疗设备中的应用和优势

STM8单片机作为一种高性能、低功耗的微控制器&#xff0c;在医疗设备领域得到了广泛的应用。本文对STM8单片机在医疗设备中的应用进行了研究&#xff0c;探讨了它在医疗设备中的优势和特点&#xff0c;并分析了其在提升医疗设备性能、精确控制和数据处理等方面的应用效果。 一…

基于ruoyi框架项目-部署到服务器上

基于ruoyi框架项目-部署到服务器上 文章目录 基于ruoyi框架项目-部署到服务器上1.前端vue编译&#xff0c;后的dist下内容打包&#xff08;前后端分离版本需要&#xff09;2.后端打包成jar包&#xff08;如果是thymeleaf仅需打包jar&#xff09;3.上传到服务器目录下4. docker部…

webgoat-Insecure Deserialization不安全的序列化

A&#xff08;8&#xff09;不安全的反序列化 反序列化是将已序列化的数据还原回对象的过程。然而&#xff0c;如果反序列化是不安全的&#xff0c;那么恶意攻击者可以在序列化的数据中夹带恶意代码&#xff0c;从而在反序列化时执行这些代码。这种攻击被称为反序列化。 什么…