Hallo!
Ich habe den ersten Teil von Quaxlis "Tutorial für Java Spiele" durchgearbeitet und das spiel funktioniert wie beschrieben. Doch erscheint während dem Spiel oft die Fehlermeldung Exception in thread "Thread-5" java.util.ConcurrentModificationException.
Soweit so gut, denn im Nachtrag wird auf dieses Problem eingegangen und eine Lösung nachgereicht.
Allerdings verstehe ich nicht so recht was ich genau ändern muss. Wenn ich die in der doLogic()-Methode dieForeach Schleife auskommentiere und und ersetze, dann wird der helikopter nicht ausgeblendet und die explosionsanimation loopt ständig.
hier der Lösungsvorschlag des Tutorials;
JavaConcurrentModificationException
Von Lesern dieses Tutorials wurde mir immer wieder berichtet, daß ihr Programm
eine JavaConcurrentModificationException wirft. Seltsamerweise tut das mein hier
gezeigtes Programm nicht, während ich den gleichen Effekt bei Programmen
beobachten kann, die mir zugesendet wurden.
Obwohl ich einige Zeit investiert habe, ist es mir bisher nicht gelungen den genauen
Unterschied heraus zu finden.
Nichtsdestotrotz möchte ich hier aber eine Lösung für das Problem nachreichen:
Der Fehler wird geworfen, wenn mehrere Threads (GameLoop- & „Fenster“-Thread)
gleichzeitig auf den Vector zugreifen bzw. wenn einer der beiden Threads etwas am
Vector ändert, während der andere auf die Objekte darin zugreift bwz. darüber
iteriert.
Zwar ist die Klasse Vector lt. API eigentlich thread-safe, das foreach-Konstrukt ist es
aber offensichtlich nicht.
Die Lösung des Problems besteht daher darin, die Art und Weise, mit der über den
Vector iteriert wird zu ändern.
Daher sollten alle Zugriffe, die über den Vector „nudeln“ wie folgt geändert werden:
Zeile 153 – 155: Die foreach-Schleife wird auskommentiert.
Zeile 157 – 159: Stattdessen iterieren wir selbst über die Objekte des Vectors. Das
Prinzip ist gleich geblieben, der Methodenaufruf prinzipiell auch, wir rufen diese nur
ein bisschen anders auf.
Mit dieser Modifikation, angewendet auf alle foreach-Schleifen, sollte das Problem
dann erledigt sein. (Sobald ich herausbekomme, warum dies in meiner Version, bei
mir nicht geschieht, reiche ich die Lösung nach).
Ich habe den ersten Teil von Quaxlis "Tutorial für Java Spiele" durchgearbeitet und das spiel funktioniert wie beschrieben. Doch erscheint während dem Spiel oft die Fehlermeldung Exception in thread "Thread-5" java.util.ConcurrentModificationException.
Soweit so gut, denn im Nachtrag wird auf dieses Problem eingegangen und eine Lösung nachgereicht.
Allerdings verstehe ich nicht so recht was ich genau ändern muss. Wenn ich die in der doLogic()-Methode dieForeach Schleife auskommentiere und und ersetze, dann wird der helikopter nicht ausgeblendet und die explosionsanimation loopt ständig.
hier der Lösungsvorschlag des Tutorials;
JavaConcurrentModificationException
Von Lesern dieses Tutorials wurde mir immer wieder berichtet, daß ihr Programm
eine JavaConcurrentModificationException wirft. Seltsamerweise tut das mein hier
gezeigtes Programm nicht, während ich den gleichen Effekt bei Programmen
beobachten kann, die mir zugesendet wurden.
Obwohl ich einige Zeit investiert habe, ist es mir bisher nicht gelungen den genauen
Unterschied heraus zu finden.
Nichtsdestotrotz möchte ich hier aber eine Lösung für das Problem nachreichen:
Der Fehler wird geworfen, wenn mehrere Threads (GameLoop- & „Fenster“-Thread)
gleichzeitig auf den Vector zugreifen bzw. wenn einer der beiden Threads etwas am
Vector ändert, während der andere auf die Objekte darin zugreift bwz. darüber
iteriert.
Zwar ist die Klasse Vector lt. API eigentlich thread-safe, das foreach-Konstrukt ist es
aber offensichtlich nicht.
Die Lösung des Problems besteht daher darin, die Art und Weise, mit der über den
Vector iteriert wird zu ändern.
Daher sollten alle Zugriffe, die über den Vector „nudeln“ wie folgt geändert werden:
Zeile 153 – 155: Die foreach-Schleife wird auskommentiert.
Zeile 157 – 159: Stattdessen iterieren wir selbst über die Objekte des Vectors. Das
Prinzip ist gleich geblieben, der Methodenaufruf prinzipiell auch, wir rufen diese nur
ein bisschen anders auf.
Mit dieser Modifikation, angewendet auf alle foreach-Schleifen, sollte das Problem
dann erledigt sein. (Sobald ich herausbekomme, warum dies in meiner Version, bei
mir nicht geschieht, reiche ich die Lösung nach).
Java:
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.Vector;
import javax.imageio.ImageIO;
import javax.swing.*;
public class GamePanel extends JPanel implements Runnable, KeyListener, ActionListener{
private static final long serialVersionUID = 1L;
boolean game_running = true;
boolean started = false;
boolean once = false;
long delta = 0;
long last = 0;
long fps = 0;
long gameover = 0;
long timepass;
int level = 1;
int rdel = 1500;
int x = 1;
Heli copter;
Vector<Sprite> actors;
boolean up = false;
boolean down = false;
boolean left = false;
boolean right = false;
int speed = 200;
Timer timer;
Timer rtime;
BufferedImage[] rocket;
BufferedImage background;
BufferedImage[] explosion;
SoundLib slib;
public static void main(String[] args){
new GamePanel(1000,600);
}
public GamePanel(int w, int h)
{
this.setPreferredSize(new Dimension(w, h));
this.setBackground(Color.DARK_GRAY);
JFrame frame = new JFrame("GameDemo");
frame.setLocation(100,100);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.addKeyListener(this);
frame.add(this);
frame.pack();
frame.setVisible(true);
doInitializations();
}
private void doInitializations(){
BufferedImage[] heli = this.loadPics("pics/heli.png", 4);
rocket = loadPics("pics/rocket.gif",8);
background = loadPics("pics/background.jpg",1)[0];
explosion = loadPics("Pics/explosion.gif",5);
slib = new SoundLib();
slib.loadSound("heli","sound/heli.wav");
slib.loadSound("bumm","sound/boom.wav");
slib.loadSound("rocket", "sound/rocket_start.wav");
last = System.nanoTime();
gameover = 0;
actors = new Vector<Sprite>();
copter = new Heli(heli,400,300,100,this);
actors.add(copter);
if(isStarted()){
slib.loopSound("heli");
}
createClouds();
timer = new Timer(3000,this);
timer.start();
rtime = new Timer(10000,this);
rtime.start();
if(!once){
once = true;
Thread t = new Thread(this);
t.start();
}
}
private void createClouds(){
BufferedImage[] ci = this.loadPics("pics/cloudr.png", 1);
for(int y=10;y<getHeight();y+=50){
int x = (int)(Math.random()*getWidth());
Cloud cloud = new Cloud(ci,x,y,1000,this);
actors.add(cloud);
}
}
public void createExplosion(int x, int y){
Explosion ex = new Explosion(explosion,x,y,100,this);
actors.add(ex);
slib.playSound("bumm");
}
public void run(){
while(game_running){
computeDelta();
if(isStarted()){
checkKeys();
doLogic();
moveObjects();
}
repaint ();
try {
Thread.sleep(10);
}catch (InterruptedException e) {}
}
}
private void checkKeys(){
if(up){
copter.setVerticalSpeed(-speed);
}
if(down){
copter.setVerticalSpeed(speed);
}
if(right){
copter.setHorizontalSpeed(speed);
}
if(left){
copter.setHorizontalSpeed(-speed);
}
if(!up&&!down){
copter.setVerticalSpeed(0);
}
if(!left&&!right){
copter.setHorizontalSpeed(0);
}
}
private void doLogic(){
Vector<Sprite> trash = new Vector<Sprite>();
/*for(Movable mov:actors){
mov.doLogic(delta);
Sprite check = (Sprite)mov;
if(check.remove){
trash.add(check);
}
}
*/
for(int i = 0;i < actors.size();i++){
actors.get(i).doLogic(delta);
Sprite check = (Sprite)actors.get(i);
if(check.remove){
trash.add(check);
}
for(int n = i+1; n<actors.size();n++){
Sprite s1 = actors.elementAt(i);
Sprite s2 = actors.elementAt(n);
s1.collidedWith(s2);
}
}
if(trash.size()>0){
for(Sprite s: trash){
trash.clear();
}
}
if(gameover>0){
if(System.currentTimeMillis()-gameover>3000){
stopGame();
}
}
}
private void stopGame(){
timer.stop();
rtime.stop();
slib.stopLoopingSound();
setStarted(false);
}
private void moveObjects(){
for(Movable mov:actors){
mov.move(delta);
}
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(background, 0, 0, this);
g.setColor(Color.white);
g.setFont( new Font( "Trebuchet MS", Font.BOLD, 12 ) );
g.drawString("FPS: " + Long.toString(fps), 20, 10);
g.drawString("Level " + level, 750, 10);
if(!isStarted()){
return;
}
if(actors !=null){
for(Drawable draw:actors){
draw.drawObjects(g);
}
}
}
private void computeDelta(){
delta = System.nanoTime() - last;
last = System.nanoTime();
fps = ((long) 1e9)/delta;
}
private BufferedImage[] loadPics(String path, int pics){
BufferedImage[] anim = new BufferedImage[pics];
BufferedImage source = null;
URL pic_url = getClass().getClassLoader().getResource(path);
try{
source = ImageIO.read(pic_url);
} catch (IOException e) {}
for(int x=0;x<pics;x++){
anim[x] = source.getSubimage(x*source.getWidth()/pics, 0,
source.getWidth()/pics, source.getHeight());
}
return anim;
}
public boolean isStarted() {
return started;
}
public void setStarted(boolean started) {
this.started = started;
}
private void createRocket(){
int x = 0;
int y = (int)(Math.random()*getHeight());
int hori = (int)(Math.random()*2);
if(hori==0){
x = -30;
}else{
x = getWidth()+30;
}
Rocket rock = new Rocket(rocket,x,y,100,this);
if(x<0){
rock.setHorizontalSpeed(300);
}else{
rock.setHorizontalSpeed(-300);
}
actors.add(rock);
//slib.playSound("rocket");
}
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_UP){
up = true;
}
if(e.getKeyCode()==KeyEvent.VK_DOWN){
down = true;
}
if(e.getKeyCode()==KeyEvent.VK_LEFT){
left = true;
}
if(e.getKeyCode()==KeyEvent.VK_RIGHT){
right = true;
}
}
public void keyTyped(KeyEvent e) {
}
public void keyReleased(KeyEvent e) {
if(e.getKeyCode()==KeyEvent.VK_UP){
up = false;
}
if(e.getKeyCode()==KeyEvent.VK_DOWN){
down = false;
}
if(e.getKeyCode()==KeyEvent.VK_LEFT){
left = false;
}
if(e.getKeyCode()==KeyEvent.VK_RIGHT){
right = false;
}
if(e.getKeyCode()==KeyEvent.VK_ENTER){
if(!isStarted()){
doInitializations();
setStarted(true);
level = 1;
}
}
if(e.getKeyCode()==KeyEvent.VK_ESCAPE){
if(isStarted()){
stopGame();
}else{
setStarted(false);
System.exit(0);
}
}
}
public void actionPerformed(ActionEvent e){
if(isStarted() && e.getSource().equals(timer)){
createRocket();
}
if(isStarted() && e.getSource().equals(rtime)){
level++;
timer.setDelay(3000/level);
}
}
}