Search code examples
javamultithreadingswingcollision-detection

Game collision detection between many objects?


I'm trying to understand how to implement collision detection between multiple object. My project detects the collision between the objects but it crashes immediately after.

This is my main class with the JFrame and Main Loop:

public class Window {

public static void main(String[] args){
    GamePanel gamepanel = new GamePanel();
    JFrame f = new JFrame("Multiple Collision Detection");
    f.setSize(400, 400);
    f.add(gamepanel);
    f.setVisible(true);
    f.setResizable(false);
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.setLocationRelativeTo(null);

    while(true){
        gamepanel.repaint();
        gamepanel.update();
        try{
            Thread.sleep(10);
        }catch(Exception e){
            System.out.println("Main Loop Error");
        }
    }
}

}

Then i have two classes, one for the player and one for the enemy:

PLAYER:

public class Player {

int x = 175, y = 175, w = 50, h = 50, dx = 0, dy = 0;
Rectangle rect;

public void paint(Graphics g) {
    rect = new Rectangle(x, y, w, h);
    g.setColor(Color.black);
    g.fillRect(rect.x, rect.y, rect.width, rect.height);
    g.setColor(Color.CYAN);
    g.drawRect(x, y, w, h);
}

public void setDx(int dx) {
    this.dx = dx;
}

public void setDy(int dy) {
    this.dy = dy;
}

public void move() {
    x += dx;
    y += dy;
}

public void update() {
    move();
}

}

ENEMY:

public class Enemy {

int x, y, w = 35, h = 35;

Rectangle rect;

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

public void paint(Graphics g) {
    rect = new Rectangle(x, y, w, h);
    g.setColor(Color.red);
    g.fillRect(rect.x, rect.y, rect.width, rect.height);
}

public void update() {

}

}

I've the EnemyManager class that draws more than one enemy (in this case 3) through a List:

public class EnemyManager {

Player player = new Player();
Rectangle playerrect;
Rectangle enemyrect;
List<Enemy> enemies = new ArrayList<Enemy>();

public void paint(Graphics g) {

    enemies.add(new Enemy(20, 20));
    enemies.add(new Enemy(320, 20));
    enemies.add(new Enemy(20, 320));

    for (Enemy e : enemies) {
        e.paint(g);
    }
}

public void update() {

}

}

Finally i've the GamePanel class that draws the Graphics from the other classes (Player and EnemyManager):

public class GamePanel extends JPanel implements KeyListener{

Player player = new Player();
EnemyManager enemymanager = new EnemyManager();

public void paint(Graphics g){
    g.setColor(Color.LIGHT_GRAY);
    g.fillRect(0, 0, 400, 400);
    player.paint(g);
    enemymanager.paint(g);
}

public void checkPlayerEnemyCollision(){
    for(Enemy e : enemymanager.enemies){
        if(e.rect.intersects(player.rect)){
            System.out.println("Collision");
        }
    }
}

public void update(){
    addKeyListener(this);
    setFocusable(true);
    player.update();
    enemymanager.update();
    checkPlayerEnemyCollision();
}


@Override
public void keyPressed(KeyEvent e) {
    if(e.getKeyCode() == KeyEvent.VK_W){
        player.setDy(-2);
    }
    if(e.getKeyCode() == KeyEvent.VK_S){
        player.setDy(2);
    }
    if(e.getKeyCode() == KeyEvent.VK_A){
        player.setDx(-2);
    }
    if(e.getKeyCode() == KeyEvent.VK_D){
        player.setDx(2);
    }
    if(e.getKeyCode() == KeyEvent.VK_ESCAPE){
        System.exit(0);
    }

}

@Override
public void keyReleased(KeyEvent e) {
    if(e.getKeyCode() == KeyEvent.VK_W){
        player.setDy(0);
    }
    if(e.getKeyCode() == KeyEvent.VK_S){
        player.setDy(0);
    }
    if(e.getKeyCode() == KeyEvent.VK_A){
        player.setDx(0);
    }
    if(e.getKeyCode() == KeyEvent.VK_D){
        player.setDx(0);
    }
}
}

When the game detects a collision between the player and an enemy, it prints "Collision" to the console but after that it crashes with this error:

Exception in thread "main" java.util.ConcurrentModificationException
at java.util.ArrayList$Itr.checkForComodification(Unknown Source)
at java.util.ArrayList$Itr.next(Unknown Source)
at GamePanel.checkPlayerEnemyCollision(GamePanel.java:22)
at GamePanel.update(GamePanel.java:34)
at Window.main(Window.java:20)

Anyone knows what is the problem and maybe how to solve it?

Thanks in advance.


Solution

  • paint is called from the event dispatcher thread. You modify the list in this method

    event dispatcher thread
    -> GamePanel.paint
    -> EnemyManager.paint
    -> enemies.add
    

    However you use a iterator of the list through the "foreach" loop in GamePanel.checkPlayerEnemyCollision:

    for(Enemy e : enemymanager.enemies)
    

    But the iterator of ArrayList fails, if you modify the list after the iterator is created with the exception you got.

    Since the list modification and the iterator can are used from different threads they can easyly interfere, e.g.:

    1. main thread creates iterator
    2. main thread reads a few elements
    3. event dispatcher thread calls paint
    4. event dispatcher thread adds Enemy to list
    5. main thread calls tries to get a element from the iterator -> exception

    Your design is a bit flawed:

    You have little control, when paint is called and as the method name suggests it's used for painting. Don't change the data in this method, just draw it.

    Redesign your program keeping that in mind (maybe read a tutorial first, e.g. this one).

    You will still need to read the Enemy list from a different thread than the one that modifies the list. Use the get method of the list instead of the iterator and keep in mind, that the list size can change so you will need to synchronize things a bit. This could be done effectively, if the threads "reserve" a specific index range that they are allowed to access.