项目代码
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坦克,如果子弹消亡,记得回收该子弹
这一段代码类似于Hero类,有shotBullet方法,该方法创建了子弹对象,和1.1版本的功能一样,启动shot线程,这个类也创建了enemyBullets用于存放敌人射出的子弹对象
- public class Enemy extends Tank {
- Vector
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;
- }
- }
该方法改进,将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
bullets,Graphics g) { - Vector
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)在画板中判断是否击中,写两个方法纯粹是塞到一块太难看了,一个方法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
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);
- }
- }
- }

在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());
- }
- }
中间量为isLive


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



坦克只在被击中的时候死亡,所以当一个坦克死亡的时候把坦克的位置用这三张图片替代,然后如果不做成一个像子弹一样的类的话很难保证不堵塞,因为图片太快了需要休眠让图片依次走,单独写一个炸弹类,类内定义一个Life,每执行一次线程就life--,相当于执行完爆炸效果需要9个线程的时间
该类写了一个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;
- }
- }
- }
当我们击中坦克时,在该坦克处创建一个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;
-
- }
- }
因为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方法,实现随机方向,加一个判断是否移动,然后再定义个值,判断是否转向
设置为多线程后,重写run方法,在run里实现坦克的移动,记得在创建enemy对象的地方启动该线程
使用math.random的方式来随机移动,
- public class Enemy extends Tank implements Runnable {
- Vector
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内使用
在该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;
- }
- }
这样的好处就是不用动之前的代码,动来动去自己都忘了
- 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坦克
