Search code examples
javaswingarraylistpanelgame-development

Space Invaders generate initial Asteroids Java Failure


I have written a Space Invaders-like game, which has a rocketShip and some Asteroid objects falling, and the objective of the game is to survive and shoot as many Asteroids as possible.

I am having some trouble generating the Asteroids when the game starts. My thought process was, that in the GamePanel constructor, before starting an ActionListener Timer, I generate a fixed Globals.NUM_OF_ASTEROIDS number of Asteroids, and whenever one gets destroyed in the updateGameState method, a new one gets added to the ArrayList of asteroids.

I have tried a simple for loop to generate all asteroids, and then starting a timer. I have also tried generating the asteroids in the timer itself. Both solutions produce a bunch of errors:

Exception in thread "AWT-EventQueue-0" java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
    at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
    at GamePanel.updateGameState(GamePanel.java:65)
    at GamePanel$1.actionPerformed(GamePanel.java:42)
    at java.desktop/javax.swing.Timer.fireActionPerformed(Timer.java:311)
    at java.desktop/javax.swing.Timer$DoPostEvent.run(Timer.java:243)
    at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
    at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:771)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:722)
    at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:716)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
    at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:741)
    at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
    at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
    at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
    at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

I am also going to attach the source code to my GamePanel class. Please note, that this still is an unfinished project. I have boxed in the troubling section, which can be found in the constructor.

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JPanel;
import javax.swing.Timer;

public class GamePanel extends JPanel implements KeyListener {
    private static final long serialVersionUID = -1036125108881682872L;
    private List<Asteroid> asteroids;
    private List<Projectile> projectiles;
    private RocketShip rocketShip;
    private Timer timer;

    private int end;    //0 - the game is still in progress
                        //1 - the game has ended
    
    public GamePanel() {
        setBackground(Color.BLACK);
        asteroids = new ArrayList<>();
        projectiles = new ArrayList<>();
        rocketShip = new RocketShip(Globals.WINDOW_WIDTH / 2, Globals.SHIP_HEIGHT);

        this.end = 0;
        
        addKeyListener(this);
        setFocusable(true);
        
        /////////////////////////////////////////////////////////
        //this loop is causing the errors.                  /////
        for(int i = 0; i < Globals.NUM_OF_ASTEROIDS; i++) { /////
            asteroids.add(new Asteroid());                  /////
        }                                                   /////
        /////////////////////////////////////////////////////////
        
        timer = new Timer(10, new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                updateGameState("");
                repaint();
            }
        });
        timer.start();
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        rocketShip.paintComponent(g);
        for (Asteroid asteroid : asteroids) {
            asteroid.paintComponent(g);
        }
        for (Projectile projectile : projectiles) {
            projectile.paintComponent(g);
        }
    }

    
    
    public void updateGameState(String direction) {
        rocketShip.move(direction);
        for (Asteroid asteroid : asteroids) {
            asteroid.move();
            if (asteroid.isOutOfBounds()) {
                asteroids.remove(asteroid);
                asteroids.add(new Asteroid());
            }
        }
        for (Projectile projectile : projectiles) {
            projectile.move();
            // projectile is out of bounds
            if (projectile.getX() - (projectile.getHeight() / 2) > Globals.WINDOW_HEIGHT) {
                projectiles.remove(projectile);
            }
        }
        // check for collisions
        //asteoids with projectiles
        for (Asteroid asteroid : asteroids) {
            for (Projectile projectile : projectiles) {
                if (projectile.getX() + projectile.getWidth() > asteroid.getX()
                        && projectile.getX() < asteroid.getX() + asteroid.getWidth()
                        && projectile.getY() + projectile.getHeight() > asteroid.getY()
                        && projectile.getY() < asteroid.getY() + asteroid.getHeight()) {
                    asteroids.remove(asteroid);
                    projectiles.remove(projectile);
                    //add a new asteroid
                    asteroids.add(new Asteroid());
                    //score increase
                }
            }
        }

        // asteroids with rocketship
        for (Asteroid asteroid : asteroids) {
            if (rocketShip.getX() + rocketShip.getWidth() > asteroid.getX()
                    && rocketShip.getX() < asteroid.getX() + asteroid.getWidth()
                    && rocketShip.getY() + rocketShip.getHeight() > asteroid.getY()
                    && rocketShip.getY() < asteroid.getY() + asteroid.getHeight()) {
                // rocketShip and asteroid have collided
                asteroids.remove(asteroid);
                // game over logic
                this.end = 1;
                timer.stop();
            }
        }

    }

    @Override
    public void keyTyped(KeyEvent e) {
        if (e.getKeyChar() == ' ') {
            Projectile projectile = new Projectile(rocketShip.getX() + rocketShip.getWidth() / 2, rocketShip.getY());
            projectiles.add(projectile);
        }
    }

    @Override
    public void keyPressed(KeyEvent e) {
        // TODO Auto-generated method stub
        int keyCode = e.getKeyCode();
        
        if(keyCode == KeyEvent.VK_RIGHT) {
            updateGameState("right");
        }
        if(keyCode == KeyEvent.VK_LEFT) {
            updateGameState("left");
        }
    }

    @Override
    public void keyReleased(KeyEvent e) {
    }

}

edit: If I remove the said section, the game will run without a problem, the moving and shooting mechanism is working correctly, but there are no asteroids spawned.

Any help is vastly appreciated! Thank you for spending your time and reading my query. <3


Solution

  • Changing the asteroid checking for loop, to a default for(int i = 0; i < asteroids.size(); i++) has fixed it.

    I have also moved the generation of new asteroids into the updateGameState method.

    import java.awt.Color;
    import java.io.*;
    import java.awt.Graphics;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.event.KeyEvent;
    import java.awt.event.KeyListener;
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.List;
    
    import javax.sound.sampled.AudioInputStream;
    import javax.sound.sampled.AudioSystem;
    import javax.sound.sampled.Clip;
    import javax.sound.sampled.LineUnavailableException;
    import javax.sound.sampled.UnsupportedAudioFileException;
    import javax.swing.JPanel;
    import javax.swing.Timer;
    
    public class GamePanel extends JPanel implements KeyListener {
        private static final long serialVersionUID = -1036125108881682872L;
        private List<Asteroid> asteroids;
        private List<Projectile> projectiles;
        private RocketShip rocketShip;
        private Timer timer;
    
        private int end; // 0 - the game is still in progress
                            // 1 - the game has ended
    
        public GamePanel() {
            setBackground(Color.BLACK);
    
            asteroids = new ArrayList<>();
            projectiles = new ArrayList<>();
            rocketShip = new RocketShip(Globals.WINDOW_WIDTH / 2, Globals.SHIP_HEIGHT);
    
            this.end = 0;
    
            addKeyListener(this);
            setFocusable(true);
    
            timer = new Timer(10, new ActionListener() {
    
                @Override
                public void actionPerformed(ActionEvent e) {
                    updateGameState("");
                    repaint();
                }
            });
            timer.start();
        }
    
        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            rocketShip.paintComponent(g);
            for (Asteroid asteroid : asteroids) {
                asteroid.paintComponent(g);
            }
            for (Projectile projectile : projectiles) {
                projectile.paintComponent(g);
            }
            // start and end screen, also score
        }
    
        public void updateGameState(String direction) {
            rocketShip.move(direction);
            rocketShip.move(direction);
            while (asteroids.size() < Globals.NUM_OF_ASTEROIDS) {
                asteroids.add(new Asteroid());
                //System.out.println("asteroid added\n");
            }
    
            for (int i = 0; i < asteroids.size(); i++) {
                Asteroid asteroid = asteroids.get(i);
                asteroid.move();
                if (asteroid.isOutOfBounds()) {
                    asteroids.remove(i);
                    asteroids.add(new Asteroid());
                    i--;
                }
            }
    
            for (Projectile projectile : projectiles) {
                projectile.move();
                // projectile is out of bounds
                if (projectile.getX() - (projectile.getHeight() / 2) > Globals.WINDOW_HEIGHT) {
                    projectiles.remove(projectile);
                }
            }
            // check for collisions
            // asteoids with projectiles
            for (Asteroid asteroid : asteroids) {
                for (Projectile projectile : projectiles) {
                    if (projectile.getX() + projectile.getWidth() > asteroid.getX()
                            && projectile.getX() < asteroid.getX() + asteroid.getWidth()
                            && projectile.getY() + projectile.getHeight() > asteroid.getY()
                            && projectile.getY() < asteroid.getY() + asteroid.getHeight()) {
                        playAsteroidDestroyedSound();
                        asteroids.remove(asteroid);
                        projectiles.remove(projectile);
                        // add a new asteroid
                        asteroids.add(new Asteroid());
                        // score increase
                    }
                }
            }
    
            // asteroids with rocketship
            for (Asteroid asteroid : asteroids) {
                if (rocketShip.getX() + rocketShip.getWidth() > asteroid.getX()
                        && rocketShip.getX() < asteroid.getX() + asteroid.getWidth()
                        && rocketShip.getY() + rocketShip.getHeight() > asteroid.getY()
                        && rocketShip.getY() < asteroid.getY() + asteroid.getHeight()) {
                    // rocketShip and asteroid have collided
                    asteroids.remove(asteroid);
                    // game over logic
                    this.end = 1;
                    timer.stop();
                }
            }
    
        }
    
        public void playShootingSound() {
            try {
                AudioInputStream in = AudioSystem.getAudioInputStream(new File("audio/shooting.wav"));
                Clip clip = AudioSystem.getClip();
                clip.open(in);
                clip.start();
            } catch (UnsupportedAudioFileException | LineUnavailableException | IOException e) {
                System.out.println("ERROR: playShootingSound");
            }
        }
    
        public void playAsteroidDestroyedSound() {
            try {
                AudioInputStream in = AudioSystem.getAudioInputStream(new File("audio/asterDestroid.wav"));
                Clip clip = AudioSystem.getClip();
                clip.open(in);
                clip.start();
            } catch (UnsupportedAudioFileException | LineUnavailableException | IOException e) {
                System.out.println("ERROR: playAsterDestroSound");
            }
        }
    
        @Override
        public void keyTyped(KeyEvent e) {
            if (e.getKeyChar() == ' ') {
                playShootingSound();
                Projectile projectile = new Projectile(rocketShip.getX() + rocketShip.getWidth() / 2, rocketShip.getY());
                projectiles.add(projectile);
            }
        }
    
        @Override
        public void keyPressed(KeyEvent e) {
            // TODO Auto-generated method stub
            int keyCode = e.getKeyCode();
    
            if (keyCode == KeyEvent.VK_RIGHT) {
                updateGameState("right");
            }
            if (keyCode == KeyEvent.VK_LEFT) {
                updateGameState("left");
            }
        }
    
        @Override
        public void keyReleased(KeyEvent e) {
        }
    
    }