【问题标题】:Issues with iterating through arraylists in libgdx在 libgdx 中遍历数组列表的问题
【发布时间】:2019-10-09 01:00:03
【问题描述】:

我正在 libgdx 中进行 Missile Command 重制。

我有 3 个数组列表:bulletsmissileshouses。 我一直在尝试遍历列表以检测missileshouses 以及missilesbullets 之间的冲突,但是到目前为止我尝试过的所有内容都收到了这个非常广泛的错误消息:

Exception in thread "LWJGL Application" java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
    at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
    at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
    at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:248)
    at java.base/java.util.Objects.checkIndex(Objects.java:372)
    at java.base/java.util.ArrayList.get(ArrayList.java:458)
    at com.mygdx.game.Screen.MCGameScreen.render(MCGameScreen.java:112)
    at com.badlogic.gdx.Game.render(Game.java:46)
    at com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:215)
    at com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:120)

我尝试遍历导致(可能是missiles)数组列表检查由于碰撞而已被删除的位置的某些内容的所有方式都存在相同的问题。我不想“重用”数组中的对象,我更愿意删除它们。

这是创建新导弹的代码,并将它们添加到missiles 数组中,每 2 秒创建一个新导弹,在屏幕顶部给定一个随机位置,并添加到 missiles

timer += delta;

        if(timer >= 2) {
            Sprite temp = new Sprite(missile);
            temp.setPosition((float) (Math.random() * Gdx.graphics.getWidth() - temp.getWidth()), Gdx.graphics.getHeight());
            missiles.add(temp);
            timer = 0;
        }

这是创建新子弹的代码,并将其添加到bullets数组中,每次按下空格键时,都会创建一个新子弹,设置到坦克位置的中心,并添加到@ 987654334@.

if(keycode == Input.Keys.SPACE) {
            Sprite temp = new Sprite(bullet);
            temp.setPosition(tank.getX() + tank.getWidth()/2 - temp.getWidth()/2, tank.getY() + tank.getHeight());
            bullets.add(temp);
        }

这是我在创建要添加到列表的temp 变量时使用的预设精灵的初始化,以及数组列表本身。

bullet = new Sprite(new Texture("bullet.png"));
bullets = new ArrayList<>();

missile = new Sprite(new Texture("missile.png"));
missiles = new ArrayList<>();

house = new Sprite(new Texture("house.png"));
houses = new ArrayList<>();

这是我尝试检查碰撞的第一种方法,我也使用相同的循环来移动导弹和子弹。

        for(int i = bullets.size()-1; i >= 0; i--) {
            bullets.get(i).translateY(bulletSpeed);
            if(bullets.get(i).getY() >= Gdx.graphics.getHeight() + bullet.getHeight()) {
                bullets.remove(i);
            }
            for(int j = missiles.size()-1; j >= 0; j--) {
                if(missiles.get(j).getBoundingRectangle().overlaps(bullets.get(i).getBoundingRectangle())) {
                    missiles.remove(j);
                    bullets.remove(i);
                }
            }
        }

        for(int i = missiles.size()-1; i >= 0; i--) {
            missiles.get(i).translateY(missileSpeed);
            if(missiles.get(i).getY() <= 0) {
                missiles.remove(i);
            }
            for(int j = houses.size()-1; j >= 0; j--) {
                if(houses.get(j).getBoundingRectangle().overlaps(missiles.get(i).getBoundingRectangle())) {
                    houses.remove(j);
                    missiles.remove(i);
                }
            }
        }

这是我尝试过的第二种方法,我将所有内容拆分为单独的循环,以防数组的索引在被删除后仍在循环中使用。

        for(int i = bullets.size()-1; i >= 0; i--) {
            bullets.get(i).translateY(bulletSpeed);
            if(bullets.get(i).getY() >= Gdx.graphics.getHeight() + bullet.getHeight()) {
                bullets.remove(i);
            }
        }

        for(int i = missiles.size()-1; i >= 0; i--) {
            missiles.get(i).translateY(missileSpeed);
            if(missiles.get(i).getY() <= 0) {
                missiles.remove(i);
            }
        }

        for(int i = missiles.size()-1; i >= 0; i--) {
            for(int j = bullets.size()-1; j >= 0; j--) {
                if(missiles.get(i).getBoundingRectangle().overlaps(bullets.get(j).getBoundingRectangle())) {
                    missiles.remove(i);
                    bullets.remove(j);
                }
            }
        }

        for(int i = missiles.size()-1; i >= 0; i--) {
            for(int j = houses.size()-1; j >= 0; j--) {
                if(missiles.get(i).getBoundingRectangle().overlaps(houses.get(j).getBoundingRectangle())) {
                    missiles.remove(i);
                    houses.remove(j);
                }
            }
        }

我也尝试过这种变体,使用增强的 for 循环。

        for(Sprite missile: missiles) {
            missile.translateY(missileSpeed);
            if(missile.getY() <= 0) {
                missiles.remove(missiles.indexOf(missile));
            }
        }

        for(Sprite bullet: bullets) {
            bullet.translateY(bulletSpeed);
            if(bullet.getY() >= Gdx.graphics.getHeight() + bullet.getHeight()) {
                bullets.remove(bullets.indexOf(bullet));
            }
        }

        for(Sprite missile: missiles) {
            for(Sprite bullet: bullets) {
                if(missile.getBoundingRectangle().overlaps(bullet.getBoundingRectangle())) {
                    missiles.remove(missiles.indexOf(missile));
                    bullets.remove(bullets.indexOf(bullet));
                }
            }
        }

如果您需要我遗漏的任何细节,请告诉我!

【问题讨论】:

    标签: java arrays loops libgdx


    【解决方案1】:

    以下是您的每种方法的问题:

    第一种方式:

        for(int i = bullets.size()-1; i >= 0; i--) {
            bullets.get(i).translateY(bulletSpeed);
            if(bullets.get(i).getY() >= Gdx.graphics.getHeight() + bullet.getHeight()) {
                bullets.remove(i);
            }
            for(int j = missiles.size()-1; j >= 0; j--) {
                if(missiles.get(j).getBoundingRectangle().overlaps(bullets.get(i).getBoundingRectangle())) {
                    missiles.remove(j);
                    bullets.remove(i);
                }
            }
        }
    

    您正在向后遍历项目符号列表,这通常是可以的,但在项目符号被删除后,您继续尝试访问循环中的项目符号。当子弹到达屏幕顶部时,您将其移除。然后你在你的内部导弹循环中再次调用bullets.get(i),但是那个项目符号已经从列表中消失了,所以如果它是列表中的最后一个项目符号,你会得到 IndexOutOfBoundsException。

    第二种方式:

        for(int i = missiles.size()-1; i >= 0; i--) {
            for(int j = bullets.size()-1; j >= 0; j--) {
                if(missiles.get(i).getBoundingRectangle().overlaps(bullets.get(j).getBoundingRectangle())) {
                    missiles.remove(i);
                    bullets.remove(j);
                }
            }
        }
    

    在这里,当发现碰撞时,您移除了导弹,但随后您继续遍历子弹,因此如果 missiles.get(i) 是列表中的最后一个,则调用 missiles.get(i) 将失败并返回 IndexOutOfBoundsException。您可以通过在移除导弹后添加break 语句来解决此问题,因为无需继续检查移除的导弹与其余子弹:

    for(int i = missiles.size()-1; i >= 0; i--) {
        for(int j = bullets.size()-1; j >= 0; j--) {
            if(missiles.get(i).getBoundingRectangle().overlaps(bullets.get(j).getBoundingRectangle())) {
                missiles.remove(i);
                bullets.remove(j);
                break;
            }
        }
    }
    

    你会想对你的房子循环做同样的事情。

    第三种方式:

    使用增强的迭代语法时,您不能安全地删除项目(因为它是从低到高迭代而不是向后迭代)。您可以做的是专门使用迭代器并在迭代器上调用 remove:

        for(Iterator<Sprite> iterator = missiles.iterator(); iterator.hasNext();) {
            Sprite missile = iterator.next()
            missile.translateY(missileSpeed);
            if(missile.getY() <= 0) {
                iterator.remove();
            }
        }
    

    【讨论】:

      【解决方案2】:

      我回到了我的答案,我将每个操作分成了自己的 for 循环:

              for(int i = bullets.size()-1; i >= 0; i--) {
                  bullets.get(i).translateY(bulletSpeed);
              }
      
              for(int i = bullets.size()-1; i >= 0; i--) {
                  if(bullets.get(i).getY() >= Gdx.graphics.getHeight() + bullet.getHeight()) {
                      bullets.remove(i);
                  }
              }
      
              for(int i = missiles.size()-1; i >= 0; i--) {
                  missiles.get(i).translateY(missileSpeed);
              }
      
              for(int i = missiles.size()-1; i >= 0; i--) {
                  if(missiles.get(i).getY() <= 0) {
                      missiles.remove(i);
                  }
              }
      
              for(int i = missiles.size()-1; i >= 0; i--) {
                  for(int j = bullets.size()-1; j >= 0; j--) {
                      if(missiles.get(i).getBoundingRectangle().overlaps(bullets.get(j).getBoundingRectangle())) {
                          bullets.remove(j);
                          missiles.remove(i);
                          break;
                      }
                  }
              }
      
              for(int i = missiles.size()-1; i >= 0; i--) {
                  for(int j = houses.size()-1; j >= 0; j--) {
                      if(missiles.get(i).getBoundingRectangle().overlaps(houses.get(j).getBoundingRectangle())) {
                          houses.remove(j);
                          missiles.remove(i);
                          break;
                      }
                  }
              }
      
      

      我在从数组中删除一个项目后添加了break;,因为该索引将不再使用。

      【讨论】:

        【解决方案3】:

        所以我马上注意到并且会担心的一件事。当您遍历列表时,您会改变它们。你打电话给

        List&lt;E&gt;.remove(i);

        这被高度认为是一种不太安全的操作。

        如果你真的想在迭代时改变一个列表,标准且安全的方法是调用列表'

        `iterator()` 
        

        函数然后调用 remove() 在迭代器本身上。

        不是 100% 确定这是否是您最初问题的解决方案,但这很可能是给您带来麻烦的原因

        【讨论】:

        • 谢谢,我很快就会研究这个问题,我一直在学习计算机科学课程,但我现在正在努力坚持我所学的内容,谢谢你的建议!
        猜你喜欢
        • 1970-01-01
        • 2016-04-29
        • 2015-07-29
        • 1970-01-01
        • 2016-11-05
        • 2019-05-22
        • 2016-03-08
        • 2018-02-13
        相关资源
        最近更新 更多