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
CodePudding user response:
This is the problem: you iterate a list and modify it at the same time..
for (Asteroid asteroid : asteroids) {
asteroid.move();
if (asteroid.isOutOfBounds()) {
asteroids.remove(asteroid);
See here: In Java, can you modify a List while iterating through it?
CodePudding user response:
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) {
}
}