# Spielprogrammierung mit bewegung und kollision



## jannig10 (28. Feb 2011)

Guten abend leute!

muss sagen, dass ich ziemlich am anfang bin in java und vieles mit kleinen tipps nicht einfach umsetzen kann..

Nun zum Spiel:

ich möchte drei Klassen erstellen. 1. klasse wird die Spielfäche sein, 2.Klasse die Spielfigur, die man per tastatur bewegen kann und die dritte klasse 
sollen die figuren sein, die sich selbst bewegen können und somit auch die gegner sind.

bis jetzt habe ich leider nicht viel hinbekommen.

ich habe soweit die 2. klasse fertig und teilweise die erste..

zu den Problemen:

1. ich weiß nicht wie ich die 2. und 3. klasse umschreiben soll, damit man sie mit hilfe der 1. klasse anzeigen kann.
2. ich weiß auch nicht, wie ich die "gegner" zum selbständigen bewegen bringen soll. 
3. kollission zwischen der spielfigur und dem gegner realisieren.

habe zwar hilfestellungen bekommen wie z.b linked list, for-schleife und timer zu erstellen aber ich habe zu wenig erfahrung um damit weiter zu kommen.

ich hoffe ihr habt lust und laune mir dabei zu helfen =)



```
package spiel;

import java.awt.*;

import javax.swing.*;


public class Spielfeld extends JFrame
{
	
	static JLabel straße2 = new JLabel(new ImageIcon(Spielfigur.class.getResource("straße2.gif")));

	public Spielfeld()
	{		
			
		JPanel Sf1 = new JPanel();
		
		Sf1.add(straße2);
		setContentPane(Sf1);
	}
	
	 
	
	
	
	public static void main(String[] args) 
	{
		
		Spielfeld Spielfeld = new Spielfeld();
			
		Spielfeld.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		Spielfeld.setBounds(100, 0, 800, 540);
		
		Spielfeld.setVisible(true);
		
	}
}
```


```
package spiel;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.*;

public class Spielfigur extends JFrame implements KeyListener 
{
	static JLabel fly = new JLabel(new ImageIcon(Spielfigur.class.getResource("fly.gif")));
	static JLabel straße2 = new JLabel(new ImageIcon(Spielfigur.class.getResource("straße2.gif")));
	
	public Spielfigur()
	{
		int width = 400;
		int height = 300;
		JPanel Spielfigur = new JPanel();
		Spielfigur.setBackground(Color.BLACK);
		
		fly.setPreferredSize(new Dimension(width , height));
						
		Spielfigur.add(fly);
		setContentPane(Spielfigur);
		
		addKeyListener(this);
		setFocusable(true);
	}
	

	
	public void keyPressed(KeyEvent e) 
	{
		switch (e.getKeyCode())
		{
		
		case KeyEvent.VK_RIGHT:
		fly.setLocation((int)fly.getLocation().getX() + 10, (int)fly.getLocation().getY());
			break;
			
		case KeyEvent.VK_LEFT:
			fly.setLocation((int)fly.getLocation().getX() -10, (int)fly.getLocation().getY());
				break;	
			
		case KeyEvent.VK_UP:
			fly.setLocation((int)fly.getLocation().getX() , (int)fly.getLocation().getY() -10);
				break;
		
		case KeyEvent.VK_DOWN:
			fly.setLocation((int)fly.getLocation().getX() , (int)fly.getLocation().getY() + 10);
				break;
		}
	}


	
	public void keyReleased(KeyEvent e) {
		if((fly.getLocation().getX() < WIDTH-170)) 
		{
			fly.setLocation(((int)fly.getLocation().getX() + 10), (int)fly.getLocation().getY());
		}
		
		if((fly.getLocation().getX() > WIDTH+565)) 
		{
			fly.setLocation(((int)fly.getLocation().getX() - 10), (int)fly.getLocation().getY());
		}
		
		if((fly.getLocation().getY() < HEIGHT-120)) 
		{
			fly.setLocation(((int)fly.getLocation().getX() ), (int)fly.getLocation().getY()+10);
		}
		
		if((fly.getLocation().getY() > HEIGHT+330)) 
		{
			fly.setLocation(((int)fly.getLocation().getX() ), (int)fly.getLocation().getY()-10);
		}
		
		
	}



	public void keyTyped(KeyEvent e) {
		// TODO Auto-generated method stub
		
	}
	
	
}
```


```
package spiel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import javax.swing.*;

public class GegnerFiguren extends Spielfigur
{	
	
	JLabel balloon = new JLabel(new ImageIcon(GegnerFiguren.class.getResource("balloon.gif")));
	 


		public  GegnerFiguren()
		{
			JPanel GegnerFiguren= new JPanel();
			GegnerFiguren.setBackground(Color.BLACK);
			
			balloon.setPreferredSize(new Dimension(400 , 100));
			
			
			GegnerFiguren.add(balloon);
			
			setContentPane(GegnerFiguren);
			
		}

				
		
}
```


----------



## Quaxli (28. Feb 2011)

Am Besten liest Du Dir mal ein paar Tutorials durch, bevor Du anfängst, wild drauf los zu programmieren. Dein Versuch oben hat schon mal eine Klasse zuviel, die von JFrame erbt.

Guck mal hier: http://www.java-forum.org/spiele-multimedia-programmierung/6529-tutorials.html


----------



## jannig10 (28. Feb 2011)

danke für die infos!

nun habe ich mir in einem link von dir, die pdf datei runtergeladen und hab langsam die beispiele abgeschrieben.

nur hab einpaar fehlermeldungen obwohl ich alles genau so hab wie im beispiel!

Fehlermeldungen:

Exception in thread "main" java.lang.IllegalArgumentException: input == null!
	at javax.imageio.ImageIO.read(ImageIO.java:1378)
	at spiel.Spielfeld.loadPics(Spielfeld.java:173)
	at spiel.Spielfeld.doInitializations(Spielfeld.java:63)
	at spiel.Spielfeld.<init>(Spielfeld.java:52)
	at spiel.Spielfeld.main(Spielfeld.java:39)


bitte um hilfe =)


----------



## xehpuk (28. Feb 2011)

Da ist die Bild-URL wohl falsch.


----------



## jannig10 (28. Feb 2011)

jap danke! 

musste noch den ordner angeben wo der pic ordner sich befindet.. 

danke!


----------



## jannig10 (2. Mrz 2011)

danke nochmal für die infos !

nun hab ich mich bisschen eingelesen.

hab auch soweit ein kleines spiel programmiert.

hab dennoch einpaar fragen!


```
public void paintComponent(Graphics g) 
	{		
		super.paintComponent(g);
		g.drawImage(background, 0, 0,this);
		g.setFont(new Font("Arial",Font.BOLD,20));
		
		g.setColor(Color.RED);
			
		g.drawString("CLICK [ENTER] TO START THE GAME",50,200);
		

		
		if(!isStarted())
		{
			
			return;
			
		}
		
		if (actors != null)
		{
			for(Drawable draw:actors)
			{
				draw.drawObjects(g);
				
			}
		}
	}
```


Also wie man da sehen kann, wird ein text im feld ausgegeben.
sobal man auf enter klickt, fängt das spiel an.

mein problem dabei ist es, dass der text "click enter.." nicht verschwindet sobald man auf enter klickt.
gibt es eine möglichkeit dieses als setvisible(false) zu setzen?


----------



## xehpuk (2. Mrz 2011)

Der Text soll ja nur gezeichnet werden, wenn das Spiel noch nicht gestartet wurde.

Weiter unten wird schon abgefragt, ob das Spiel gestartet wurde. Da kannst du dann den Text reinsetzen:

```
if(!isStarted())
        {
            g.setFont(new Font("Arial",Font.BOLD,20));
            g.setColor(Color.RED);
            g.drawString("CLICK [ENTER] TO START THE GAME",50,200);
            return;
        }
```
Den Font solltest du übrigens auslagern, damit er nicht jedes Mal neu geladen werden muss.


----------



## jannig10 (2. Mrz 2011)

vielen dank für die schnelle antwort!

eine weitere frage:

ich möchte oben rechts wenn das spiel gestartet worden ist die punkte anzeigen.
jedoch möchte ich, dass jede 30 sekunden 50 pkt gutgeschrieben werden.

allerding sollte die zeit oben stehen..

wie mach ich das?


----------



## Quaxli (3. Mrz 2011)

jannig10 hat gesagt.:


> ...
> 
> wie mach ich das?



In dem Du einen Integer für Deine Punkte in einen String umwandelst und da oben hinschreibst. Diese Aussage bringt Dir jetzt vermutlich nicht viel, oder? Nach Deiner Frage weiter oben bzgl. des Textes in paintComponent habe ich nämlich den Eindruck, daß Du erst mal noch an den Basics feilen solltest, bevor Du mit einer komplexeren Aufgabenstellung anfängst. Ein if-Bedingung sollte man innerhalb einer Methode schon so anwenden können, daß das gewünschte Ergebnis dabei heraus kommt.

Es bringt Dir wenig, wenn Du Dir hier alles vorkauen läßt. Und die hilfsbereiten Forenmitglieder, werden schnell die Lust verlieren, Dir zu helfen, wenn offensichtlich wird, das Du nicht erst mal selbst etwas ausprobierst.
Also, probier es erst mal selbst und wenn Du es dann nicht hinkriegst, poste Deinen Code, dann wird Dir auch geholfen.


----------



## jannig10 (4. Mrz 2011)

Also gut! hab deine anweisungen befolgt(selber ausprobieren) und hab das hinbekommen =)

nun gibt es ein weiteres problem, bei der ich der meinung bin, dass ich mir selber nicht helfen kann.

mein spiel startet ganz normal. sobald man dieses aber öffters spielt, hängt es auf einmal und man muss neu starten.

in meinem spiel geht es dadrum, dass eine fliege per tastatur bewegt wird und von den flammen gerettet wird.
sobal sich die flammen aneinander prallen, entsteht eine explosion (so soll es auch sein) und dann verschwinden diese.(hab das so gemacht, damit man ne chance hat zu entfliehen)

nun zum problem: 

sobald die flammen mit der figur aneinander prallen oder auch mit den eiflammen, kommen diese fehlermeldungen und mal läuft es denoch weiter mal hängt es.


```
Exception in thread "AWT-EventQueue-0" java.util.ConcurrentModificationException
	at java.util.Vector$Itr.checkForComodification(Vector.java:1119)
	at java.util.Vector$Itr.next(Vector.java:1096)
	at spiel.Spielfeld.paintComponent(Spielfeld.java:418)
	at javax.swing.JComponent.paint(JComponent.java:1029)
	at javax.swing.JComponent.paintToOffscreen(JComponent.java:5138)
	at javax.swing.BufferStrategyPaintManager.paint(BufferStrategyPaintManager.java:302)
	at javax.swing.RepaintManager.paint(RepaintManager.java:1145)
	at javax.swing.JComponent._paintImmediately(JComponent.java:5086)
	at javax.swing.JComponent.paintImmediately(JComponent.java:4896)
	at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:740)
	at javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:696)
	at javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:676)
	at javax.swing.RepaintManager.access$700(RepaintManager.java:57)
	at javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1550)
	at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:226)
	at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:647)
	at java.awt.EventQueue.access$000(EventQueue.java:96)
	at java.awt.EventQueue$1.run(EventQueue.java:608)
	at java.awt.EventQueue$1.run(EventQueue.java:606)
	at java.security.AccessController.doPrivileged(Native Method)
	at java.security.AccessControlContext$1.doIntersectionPrivilege(AccessControlContext.java:105)
	at java.awt.EventQueue.dispatchEvent(EventQueue.java:617)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:275)
	at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:200)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:190)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:185)
	at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:177)
	at java.awt.EventDispatchThread.run(EventDispatchThread.java:138)
```


----------



## Quaxli (4. Mrz 2011)

Ja, der Sch.... ;(

Eine 100% Lösung habe ich dafür noch nicht. Lies mal hier: http://www.java-forum.org/awt-swing...urrentmodificationexception-2.html#post661709

Da wurde schon mal drüber diskutiert.


----------



## jannig10 (4. Mrz 2011)

ok alles klar =)


----------



## jannig10 (5. Mrz 2011)

also durch diese fehlermeldung ist das spiel ja unerträglich!
man kann kaum spielen, denn es hängt!
gibt es denn keine verdammte lösung?

ihr könnt mein spiel ja testen

Index of /~ichir001/Games

und dann auf rette die fliege.jar


----------



## Marco13 (5. Mrz 2011)

Also hier auf meinem alten 1.4er mit GeForce2 (!) ist es unerträglich langsam und ruckelnd - das könnte aber auch am Sound liegen, die Soundkarte auf der Kiste hier spinnt irgendwie...

Die Sache mit der ConcurrentModificationException muss doch hinzukriegen sein...

@Quaxli: Bin nicht so auf dem Laufenden, gibt's von dem Tutorial eine "Und-das-kommt-am-Ende-dabei-raus.ZIP" die man direkt öffnen und Compilieren kann (und wo das Problem auftritt)?


----------



## Quaxli (7. Mrz 2011)

@Marco: Nö, gibt's nicht. Ich hatte diese Fehlermeldung auch seltersamerweise lange selbst nicht. Daher habe ich auch nicht so eine "Hier ist der Fehler".zip.

Bei mir ist das Spiel  übrigens gut gelaufen (neuer, 2 Wochen alter Büro-Laptop) - ohne Fehlermeldung. Ich habe aber nur 5 Runden gespielt. 
Die Fehlermeldung scheint auch nicht jeder zu haben. Oder beschwert sich bloß keiner? Das wäre natürlich unschön.  Ich werde immer wieder mal bei Fragen via PN angeschrieben, aber wg. dieser Fehlermledung fragt eigentlich keiner. :bahnhof:

@jannig10:
Üblicherweise tritt der Fehler aber früher auf, oder?
In dem Beispielen aus dem anderen Thread, der oben verlinkt ist, brachte das Vermeiden der for-each-Schleife eine deutliche Besserung. 
Kannst Du mir mal den Code schicken (ohne Sound und Bilder)?

Was mir sonst noch so aufgefallen ist:

- Durch die Flammen läuft ein "Balken". Ist das Absicht? Bei mir ist das üblicherweise ein Hinweis, daß ich die Bilder falsch eingelesen habe.

- Warum hat so ein kleines Spielchen 18 MB? Wie hast Du denn Deine Grafiken gespeichert?


----------



## Marco13 (7. Mrz 2011)

Ggf. hängt das auch davon ab, wie viele Cores man hat usw. Solche Probleme werden bei 4, 8 und mehr Kernen eher häufiger auftreten ... 
Ggf. mal einen "Stresstest" machen, wo in jedem Schritt pratkisch die komplette Liste ersetzt wird oder so...


----------



## Quaxli (7. Mrz 2011)

Das mit dem Stresstest ist eine gute Idee. Da hätte ich eigentlich selbst drauf kommen können... 
Ich hab' gerade mal ein kurzes Programm zusammen gefrickelt und es scheint, daß der "Bösewicht" die 2. Collection ist,  in der zu entfernende Objekte gesammelt werden.
Bevor die reingepackt wurde war alle gut.

Hier mal das Beispiel:


```
import javax.swing.*;
import java.awt.*;
import java.util.*;

public class Stress extends JPanel implements Runnable{

	private static final long	serialVersionUID	= 1L;
	JFrame frame;
	Vector<Rect> actors;
	
	
	public static void main(String[] args){
		new Stress();
	}
	
	//Konstruktor. Vector füllen,  Fenster basteln, etc.
	public Stress(){
		setPreferredSize(new Dimension(800,800));
		
		actors = new Vector<Rect>();
		for(int i=0;i<200;i++){
      addNewRect();
		}
		
		frame = new JFrame("Stresstest");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setLocation(100,100);
		frame.add(this);
		frame.pack();
		frame.setVisible(true);
		
		Thread th = new Thread(this);
		th.start();
		
	}

	public void run() {
		
		int count = 0;

		//GameLoop
		while(frame.isVisible()){
			
			//Move - alles bewegen
			for(Rect r:actors){
				r.move();
			}
			
			//Logic
			Vector<Rect> trash = new Vector<Rect>();  //Sammeln von zu entfernenden Objekten
			
			for(Rect r:actors){
				r.doLogic();
				//Wenn boolen = true, wird das Objekt zum entfernen vorgemerkt
				if(r.remove){
					trash.add(r);
					addNewRect();
				}
			}
			
			//alles raus, was weg soll
			actors.removeAll(trash);
			trash.clear();
			
			
			//counter - ab und an noch ein Objekt rein packen
			count++;
			if(count==10){
				count=0;
				addNewRect();
				System.out.println("noch eins");
			}
			
			
       try {
				Thread.sleep(10);
			} catch (InterruptedException e) {}			
			
			
			//Malen - irgendwann.....
			repaint();
			
		}
		
	}
	
	//neues Rechteck reinpacken
	private void addNewRect(){
		int x = (int)(Math.random()*800);
		int y = (int)(Math.random()*800);
		actors.add(new Rect(x,y,this));
	}

	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		for(Rect r:actors){
			r.paintRect(g);
		}
	}
	
	
	
}

class Rect extends Rectangle{
	
	private static final long	serialVersionUID	= 1L;
	
	int dx;
	int dy;
	Stress parent;
	boolean remove = false;
	
	public Rect(int x, int y, Stress s){
		super(x,y,10,10);
		parent = s;
		dx = (int) (Math.random()*5);
		dy = (int) (Math.random()*5);
		
		if(dx==0){
			dx=1;
		}
		if(dy==0){
			dy=1;
		}
	}
	
	public void move(){
	  x+= dx;
	  y+= dy;
	}
	
	public void doLogic(){
		if(x>parent.getWidth()){
			remove = true;
		}
		if(y>parent.getHeight()){
			remove = true;
		}
	}
	
	public void paintRect(Graphics g){
		Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2.setColor(Color.BLUE);
		g2.drawRect(x, y, width, height);
	}
	
}
```

Das sollte man ja lösen können. Ich probiere mal ein bißchen rum.


----------



## Quaxli (7. Mrz 2011)

So, es scheint eine einfache Lösung zu geben:

Statt mit einer for-each-Schleife iteriere ich "manuell" über den Vector. Für die move-Methode z. B. so.:


```
//Move - alles bewegen
			for(int i=0;i<actors.size();i++){
				Rect r = actors.elementAt(i);
				r.move();
			}
```

Damit ist die ConcurrentModificationException bei mir nicht mehr aufgetreten. Wäre nett, wenn das nochmal jemand auf anderen Rechnern überprüfen könnte.

Ich habe bei der Suche nach Hinweisen auch einen Thread im Forum von eRaaaa gefunden: http://www.java-forum.org/entwuerfe/109419-fail-fast-iterator-concurrentmodificationexception.html

Dort steht noch einmal schön verständlich, was man auch mit Hilfe von Google auf diversen Seiten findet. Das einzige was meiner Ansicht nach nicht paßt, ist dort im Thread der Lösungsvorschlag 1. Denn das ist ja genau das, was mein Testprogramm macht: Alle zu entfernenden Objekte in einer Collection merken und erst entfernen, nachdem man der aktuelle Iterator beendet wurde.

Da muß ich noch mal ein bißchen suchen. Evtl. greift hier Marcos Vermutung, daß die Anzahl der Kerne relevant ist? :bahnhof:
Mein Rechner hier hat 4 Kerne (Intel i5). Hat noch jemand einen Einkern-Rechner irgendwo laufen und kann das mal gegen prüfen? Ich hab' meinen leider vor einem halben Jahr weggeschmissen, nachdem ich zuhause mal wieder aufgerüstet habe.


----------



## Marco13 (7. Mrz 2011)

Eigentlich meinte ich den "Stresstest" möglichst ähnlich dem, wie es im Tutorial beschrieben wurde. 

An sich ist die Ursache für die ConcurrentModificationException ja klar: Eine Collection wird verändert, während drüberiteriert wird. Das "manuelle" Drüberiterieren bewirkt zwar, dass keine ConcurrentModificationException mehr geworfen wird, aber es besteht weiterhin das Problem, das durch die CME angedeutet werden sollte. (Die CME dient ja nur dem Zweck, möglichst früh zu sagen: "Oiii, da stimmt was nicht", statt dass später (noch) "undeterministische(re)s" Verhalten auftritt).

Das eigentliche Problem ist beim manuellen Drüberiterieren nur "verlagert". Wenn ein Thread diesen Code abarbeitet....

```
for(int i=0;i<actors.size();i++){   // Hier ist actors.size()==3, und irgendwann i==2

    // Hier funkt ein anderer Thread dazwischen, und entfernt etwas aus 'actors'
    // Jetzt ist actors.size()==2 und i==2
 
    Rect r = actors.elementAt(i); // Und dann kracht's hier
    r.move();
}
```
Das tritt natürlich "seltener" auf, als die CME (und vielleicht auch "gar nicht" (reproduzierbar)), aber es KANN auftreten.

Eigentlich sollte die CME verhindert werden, indem man das drüberiterieren in einen synchronized-Block packt (zusammen mit einer synchronized Collection, wie eben Vector)

```
synchronized (actors)
{
    for (Rect rect : actors)  { .... }
}
```
In anderen Threads zu diesem Thema hatte ich das auch schon erwähnt, dort wurde aber dann gesagt, dass es langsamer wird und ruckelt. Klar, das kann sein - dann kann man sich eben noch andere Sachen überlegen, wie die Liste zu Kopieren und über die Kopie zu iterieren, oder eine CopyOnWriteArrayList verwenden (was in diesem Fall aber beides potentiell ineffizient wäre). Ich werd' mal schauen, ob ich den "Stresstest" bei Gelegenheit näher ansehen kann...


----------



## Quaxli (7. Mrz 2011)

Das unten ist möglichst ähnlich.... 
Das grobe Konzept ist das Gleiche. Es sind ein paar Methoden weniger, es ist nicht schön programmiert, die doLogic- und Move-Methoden sind sehr simpel, ABER: das "Konstrukt" mit Vectoren, GameLoop und paintComponent, Thread, etc. ist das Gleiche.
Nach meinem Dafürhalten ausreichend, um den Fehler finden zu können.  Vor allem nachdem sofort eine ConcurrentModification geworfen wird. 

Ja, das mit dem synchronized hatten wir schon mal und haben es damals verworfen, weil es das Ganze ausbremst. Ich suche hier zugegebenermaßen noch nach einem goldenen Mittelweg, der mir noch nicht recht klar ist. Es handelt sich um ein Anfänger-Tutorial und ich würde es gern möglichst simpel halten, da ich davon ausgehe, daß es auch von Leuten gelesen wird, die gerade mal ein Fenster mit Swing gebastelt haben.
Ob das ein guter Ansatz ist, darüber läßt sich sicherlich streiten, ich bin da selbst unentschlossen. Ursprüngliche Absicht war ja mal, Spieleprogrammierung mit JLabels und JPanels zu verhindern. Es sollte bei einem möglichst geringen Umfang bleiben und nicht vom 100sten ins 1000ste führen. Andererseits IST es ein potentielle Fehlerquelle, die man den Lesern bewußt machen sollte.

Ich muß mir wohl mal wieder richtig Zeit für das Thema nehmen und nicht immer nur mal gucken, wenn jemand aufschreit. :autsch:


----------



## Quaxli (7. Mrz 2011)

Nachtrag:

Wie es scheint, könnte es eine einfache Lösung geben. Ich muß mir die nochmal ansehen, aber folgendes Konzept sieht bis jetzt gut aus:

1. Zum Iterieren über den Vector ausschließlich einen ListIterator verwenden.
2. Objekte hinzufügen/entfernen ebenfalls nur über ListIterator
3. Zum Zeichnen eine Kopie des Vectors mit clone() anlegen und diese verwenden.

Somit wäre zum Zeichnen eine Kopie des Vectors zum Zeitpunkt x da, über die man "in Ruhe" iterieren kann, während Änderungen am Vector nur im GameLoop vorgenommen werden und dort auch nur über die Methoden des Iterators. Das sieht soweit gut aus.

Falls jetzt jemandem ein dickes ABER einfällt, laßt es mich wissen.


----------



## Marco13 (7. Mrz 2011)

AAABER...  Vector ist schon synchronized, d.h. eigentlich sollte man die Änderungen auch direkt (ohne Iterator) machen können, sofern das Drüberiterieren von einem anderen Thread entweder innerhalb eines synchronized-blocks oder NUR über die Kopie gemacht wird. (Abgesehen davon erschließt sich mir (noch) nicht 100%ig, warum ein ListIterator da grundsätzlich helfen sollte). Es gibt schon einen Tradeoff: Wenn man sich vorstellt, dass der Vector erstmal 100000 Elemente enthält, dann wird ein clone() ggf. schon ziemlich teuer (und evtl. teurer als ein synchronized).


----------



## jannig10 (7. Mrz 2011)

also Jungs danke dass ihr eure Ideen hier geäußert habt!

also ich hab nicht viel verstanden, bin ich ganz ehrlich =)
ich dnke mal das problem taucht bei mir hier auf :


```
private void moveObjects()
	{
		for(Movable mov:actors)
		{
			mov.move(delta);
		}
```

wenn dort das problem sein sollte, wir würdet ihr das denn umschreiben?
macht dann mal bitte ein beispiel damit.


----------



## Quaxli (7. Mrz 2011)

Du nun wieder - ich hab's gewußt.  Welcher Anfänger will 100.000 Elemente bewegen. 
Soll ja kein Partikelsystem werden. 

Mal im Ernst, was mich zu dieser Lösung bewogen hat:

*Auszug aus der API zu ConcurrentModifikationException:*


> ...Note that this exception does not always indicate that an object has been concurrently modified by a different thread. If a single thread issues a sequence of method invocations that violates the contract of an object, the object may throw this exception. For example, if a thread modifies a collection directly while it is iterating over the collection with a fail-fast iterator, the iterator will throw this exception.....



Der letzte Satz hat mich bewogen, die Lösung mit den Iteratoren auszuprobieren: Keine direkte Modifikation mehr, sondern nur noch über Iterator. Insbesondere da die Exception bei mir immer innerhalb eines Threads geworfen wurde.

*azu noch folgender Satz aus der Beschreibung für Vector:*


> ...The Iterators returned by Vector's iterator and listIterator methods are fail-fast: if the Vector is structurally modified at any time after the Iterator is created, in any way except through the Iterator's own remove or add methods, the Iterator will throw a ConcurrentModificationException...



Ich vermute mal, daß for-each so zugreift (sicher bin ich nicht, ich hab keinen Hinweis gefunden) und deswegen immer wieder mal die Exception geworfen wurde, wenn man zusätzlich noch am Vector rumgefummelt hat. Insofern könnte der Verzicht auf for-each in der ersten Version schon das Problem gelöst haben, wenn Vector synchronized ist.
Die jetztige Version gefällt mir aber noch ein bißchen besser. 

Letzten Endes setzt sie das um, was eRaaaa mal gepostet hat (vgl. Lösungsmöglichkeit 3 und 4). Den gleichen Text habe ich auch nochmal auf englisch gefunden.

Im Folgenden mal das umgebaute Programm. Das hat auch noch mit 200.000 Rechtecken funktioniert. Die FPS waren runter auf um die 40 und geruckelt hat es auch ein bißchen.   (Der Code für fps ist übrigens nicht mehr enthalten). 
Auf Ressourcen-Verbrauch habe ich zugegebenermaßen nicht geachtet. 


```
import javax.swing.*;
import java.awt.*;
import java.util.*;

public class Stress extends JPanel implements Runnable{

	private static final long	serialVersionUID	= 1L;
	JFrame frame;
	Vector<Rect> actors;
	Vector<Rect> painter;
	
	public static void main(String[] args){
		new Stress();
	}
	
	//Konstruktor. Vector füllen,  Fenster basteln, etc.
	public Stress(){
		setPreferredSize(new Dimension(800,800));
		
		actors = new Vector<Rect>();
		for(int i=0;i<200;i++){
      addNewRect();
		}
		
		frame = new JFrame("Stresstest");
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setLocation(100,100);
		frame.add(this);
		frame.pack();
		frame.setVisible(true);
		
		Thread th = new Thread(this);
		th.start();
		
	}

	@SuppressWarnings("unchecked")
	public void run() {
		
		int count = 0;
		
		//GameLoop
		while(frame.isVisible()){
			
			//Jetzt iterieren über ListIterator - Veränderungen in diesem Thread nur darüber
			
			for(ListIterator<Rect> it = actors.listIterator();it.hasNext();){
				Rect r = it.next();
				r.move();
			}
			
			//Logic
			int n = 0; //Zähler, wieviel neue Objekte entfernt wurden und neu erzeugt werden sollen

			for(ListIterator<Rect> it = actors.listIterator();it.hasNext();){
				Rect r = it.next();
				r.doLogic();
				//Wenn boolen = true, wird das Objekt über den Iterator entfernt
				if(r.remove){
					it.remove();
					n++;
				}
			}
			
			//neue Objekte hinzufügen - auch in der Methode über einen Interator
			for(int i=0;i<n;i++){
				addNewRect();
			}
			
			//counter - ab und an noch ein Objekt rein packen
			count++;
			if(count==10){
        count=0;
				addNewRect();
			}
			
			
			//Clone - Eigenen Vector für's Zeichnen, der nicht verändert wird
			painter = (Vector<Rect>) actors.clone();
			
       try {
				Thread.sleep(10);
			} catch (InterruptedException e) {}			
			
			
			//Malen - irgendwann.....
			repaint();
			
		}
		
	}
	
	//neues Rechteck
	private void addNewRect(){
		//add ebenfalls über ListIterator
		ListIterator<Rect> it = actors.listIterator();
		int x = (int)(Math.random()*50);
		int y = (int)(Math.random()*50);
		it.add(new Rect(x,y,this));
	}

	@Override
	protected void paintComponent(Graphics g) {
		super.paintComponent(g);
		
		for(ListIterator<Rect> it = painter.listIterator();it.hasNext();){
			Rect r = it.next();
			r.paintRect(g);
		}
		
		g.setColor(Color.RED);
		g.drawString(Integer.toString(actors.size()), 600, 20);

	}
	
	
	
}

class Rect extends Rectangle{
	
	private static final long	serialVersionUID	= 1L;
	
	int dx;
	int dy;
	Stress parent;
	boolean remove = false;
	
	public Rect(int x, int y, Stress s){
		super(x,y,10,10);
		parent = s;
		dx = (int) (Math.random()*5);
		dy = (int) (Math.random()*5);
		
		if(dx==0){
			dx=1;
		}
		if(dy==0){
			dy=1;
		}
	}
	
	public void move(){
	  x+= dx;
	  y+= dy;
	}
	
	public void doLogic(){
		if(x>parent.getWidth()){
			remove = true;
		}
		if(y>parent.getHeight()){
			remove = true;
		}
	}
	
	public void paintRect(Graphics g){
		Graphics2D g2 = (Graphics2D) g;
		g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2.setColor(Color.BLUE);
		g2.drawRect(x, y, width, height);
	}
	
}
```


----------



## Quaxli (7. Mrz 2011)

jannig10 hat gesagt.:


> also Jungs danke dass ihr eure Ideen hier geäußert habt!
> 
> also ich hab nicht viel verstanden, bin ich ganz ehrlich =)
> ich dnke mal das problem taucht bei mir hier auf :
> ...



Das ist anhand einer einzigen Methode schlecht erklärt, da noch ein paar Änderungen mit rein spielen. 

Schau Dir die beiden Beispiele an (vorher/nachher). 
Beide Programme habe die Struktur des Tutorials, nur etwas einfacher, so daß z. B. an die move-Methode im Beispiel keine delta übergeben wird und die Berechnung sehr viel einfacher ist.

Dein Beispiel könnte z. B. so aussehen:


```
for(ListIterator<Sprite> it = actors.listIterator();it.hasNext();){
				Sprite r = it.next();
				r.move(delta);
			}
```

Aber es wurde auch beim Zeichnen und beim Löschen nicht mehr benötigter Objekte etwas geändert.


----------

