Jump to content
  • Advertisement
Sign in to follow this  
gamedude7

Strange error in Bullet Collision in Java

This topic is 2658 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hello, I've been working on this 2d game where there are 2 player objects which can shoot in any of the 4 cardinal directions and I've been trying to code for bullet collision. Right now my code for it is

public void bulletCollision(){
if(bullets.size()>0){
if(bullets2.size()>0){
for(int i = 0; i<bullets.size(); i++){
for(int w = 0; w<bullets2.size(); w++){
if(bullets.get(i).getBounds().intersects(bullets2.get(w).getBounds())){
bullets.remove(i);
bullets2.remove(w);
}
}
}
}
}
}


-"bullets" is a CopyOnWriteArrayList of bullets shot from player 1. "bullets2" contains bullets from player 2.
-getBounds() returns a rectangle of the same dimensions as the bullet.
- bulletCollision() is called in my paintComponent(Graphics g) method.

It works just fine except in one case. If player 1 shoots 5 bullets and 4 of them collide with 4 of player 2's bullets, when the last bullet is supposed to collide, this error pops up

Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 0
at java.util.concurrent.CopyOnWriteArrayList.get(Unknown Source)
at DrawnClass.bulletCollision(DrawnClass.java:91)
at DrawnClass.paintComponent(DrawnClass.java:77)
at DrawnClass.paint(DrawnClass.java:51)
at javax.swing.JComponent.paintToOffscreen(Unknown Source)
at javax.swing.BufferStrategyPaintManager.paint(Unknown Source)
at javax.swing.RepaintManager.paint(Unknown Source)
at javax.swing.JComponent._paintImmediately(Unknown Source)
at javax.swing.JComponent.paintImmediately(Unknown Source)
at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
at javax.swing.RepaintManager.seqPaintDirtyRegions(Unknown Source)
at javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(Unknown Source)
at java.awt.event.InvocationEvent.dispatch(Unknown Source)
at java.awt.EventQueue.dispatchEventImpl(Unknown Source)
at java.awt.EventQueue.access$000(Unknown Source)
at java.awt.EventQueue$1.run(Unknown Source)
at java.awt.EventQueue$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.AccessControlContext$1.doIntersectionPrivilege(Unknown Source)
at java.awt.EventQueue.dispatchEvent(Unknown Source)
at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
at java.awt.EventDispatchThread.run(Unknown Source)



- If player 2 has either 1 or 2 bullets out, it works fine. If there are 3 or more bullets out from player 2, when player 1's last bullet onscreen collides, it shows that error
- It doesn't happen if the roles are reversed and player 2 has only 1 bullet out.

I've been at this for about an hour and any help would be much appreciated

Share this post


Link to post
Share on other sites
Advertisement
Okay, first off your two if statements which check for size > 0 aren't needed - the for loop handles that for you.

Second, the problem is as follows:
Say P1 has 3 bullets out
Say p2 has 3 bullets out

Say P1's 3rd bullet is going to collide with P2's second bullet.

When the collision happens in your code you remove Bullet 3 from P1s list, and Bullet 2 from P2's list. But then you loop again and try to check to make sure bullet 3 (which you just removed) hasn't collided with bullet 3 from P2s list... And you get an array index out of bounds exception because you just tried to access P1s list at element 2 (the third bullet) and there is no third bullet anymore since you removed it.

Share this post


Link to post
Share on other sites
Okay thank you I get what you're saying so shouldn't

public void bulletCollision(){
int array1 = bullets.size();
int array2 = bullets2.size();
for(int i = 0; i<array1; i++){
for(int w = 0; w<array2; w++){
if(bullets.get(i).getBounds().intersects(bullets2.get(w).getBounds())){
bullets.remove(i);
bullets2.remove(w);
array1--;
array2--;
w=0;
i=0;
}
}
}
}



Fix the problem? Wouldn't it reset the loop accounting for the new change in size? It still gives me the same error

Share this post


Link to post
Share on other sites
Try this instead:
During your collision detection loop, set a flag on any bullets that report as colliding, but don't do anything else with them.
In a secondary loop that runs (in the same frame) after the collision check, run through all your bullet lists and pull out all projectiles that are flagged as having collided.

This keeps your index range safe for the collision check, and removes any problems with double-reporting on a collision.

Alternately/also, using an iterator would help. You can set your loop invariant to iterator.hasNext().

Share this post


Link to post
Share on other sites
Are you sure you should be using CopyOnWriteArrayLists here?

As BCullis says, you could use iterators.

The way to do this with integer indices is like the following psuedo code:

for(var i = 0 ; i < outerarray.size ; /* handled in loop body */)
{
var collision = false;

for(var j = 0 ; j < innerarray.size && !collision ; ++j)
{
if(collision(outerarray, innerarray[j]))
{
innerarray.remove(j);
}
}

if(collision)
{
outerarray.remove(i);
}
else
{
++i;
}
}

You could get a little fancier and implement the algorithm used in std::remove_if (from the C++ Standard Library). This is more efficient as it doesn't end up with lots of moving objects around in the array. The basic algorithm is simple

  • Record an additional index, the "write" pointer.
  • Loop over all the objects in the collection.
  • If you want to keep it, move it to the write pointer and increment the write pointer
  • After the loop, remove all the items at the back (up to the write pointer).

    This means that at the end of the loop all the useful objects are tightly packed at the front of the list, and the rest of the list is unimportant.

    It would work something like this:

    public class Help
    {
    public interface Predicate<T>
    {
    public boolean test(T object);
    }

    public static <T> void remove(List<T> list, Predicate<T> predicate)
    {
    int write = 0;

    for(int read = 0; read < list.size() ; ++read)
    {
    T object = list.get(read);

    if(predicate.test(object))
    {
    list.set(write, object);
    ++write;
    }
    }

    for(int i = list.size() - 1 ; i >= write ; --i)
    {
    list.remove(i);
    }
    }

    public static void main(String[] args)
    {
    List<String> animals = new ArrayList<String>();
    animals.add("Cat");
    animals.add("Dog");
    animals.add("Horse");
    animals.add("Donkey");
    animals.add("Pig");
    animals.add("Duck");
    animals.add("Goose");
    animals.add("Hen");

    System.out.print("Animals:");
    for(String animal : animals)
    {
    System.out.print(' ' + animal);
    }
    System.out.println();

    // Remove all long animal names
    remove(animals, new Predicate<String>() {
    @Override
    public boolean test(String name)
    {
    return name.length() <= 3;
    }
    });

    System.out.print("Animals:");
    for(String animal : animals)
    {
    System.out.print(' ' + animal);
    }
    System.out.println();
    }

    }

    Warning: code not tested.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!