Animation in Swing (paintComponent())

takidoso

Erfahrenes Mitglied
Hallo und Halli,
nach endlichen Jahren habe ich mich mal wieder an eine angefangene Spielanwendung gewagt.
Es ist so eine Art Risikospiel mit selbsterstellten Landkarten.
dabei werden die verschiedenen Länder mit Poligonen dargestellt. Angriffe werden mittels Pfeile von einem zum anderen Land dargestellt.
Nun möchte wenn die verschiedenen Angriffe ausgeführt werden die beteiligten Länder etwas animieren. Am einfachsten vielleicht mit Farbwechseln.
Die Vorgehensweise ist grundsätzlich ja so dass man alles was zu zeichnen ist in die überschriebene paintComponent() routine packt. Aber irgendwie fehlt mir da nun der Ansatz dort eine art farbenfrohe Blinkerei unterzubringen.
Zuerst dachte ich ok man braucht einen Thread der die Farbwechsel der Schlachtbeteiligten Länder duchläuft.
In die PaintComponentroutine eingebaut ist das aber irgendwie so eine Art immerwieder neue Threads startende Endlosschleife.
wenn ich das ganze nur so als schleife reinbaue ohne threads bemerkt man fast nix.

Wie müsste man das gescheit anstellen?

grobes Prinzip z.Z.
Java:
...
  	public void paintComponent(Graphics g)
	{
		super.paintComponent(g);  // hier werden allemöglich dinge gemalt 
		paintPlayerOrders((Graphics2D)g);  // hier werden die Befehle des aktuellen Spielers als Pfeile dargestellt
		for (Country[] c : m_battleList)     // hier dachte ich könnte man die Schlachten falls welche stattfinden malen
		{
			paintBattle((Graphics2D)g, c[0], c[1]);
		}
	}...

	protected void paintBattle(final Graphics2D g, final Country offense, final Country defense)
	{
		/*
		new Thread(new Runnable()
		{
			public void run()
			{
		*/
				try
				{
					for (int i=0; i<4; i++)
					{
						Polygon polygon     = new Polygon();

						boolean blinkOn = true;

						if (blinkOn)
						{
							Point2D[] points    = offense.getPolygon().getRawPoints();
							for (int j=0; j<points.length; j++)
							{
								int x = (int) (points[j].getX() * m_skalaFaktor);
								int y = (int) (points[j].getY() * m_skalaFaktor);
								polygon.addPoint(x,y);
							}

							g.setColor(offense.getOwner().getColor());
							g.fillPolygon(polygon);
							g.setColor(Color.BLACK);
							drawCountryLink(g,offense, defense);
						}
						else
						{
							Point2D[] points    = defense.getPolygon().getRawPoints();
							for (int j=0; j<points.length; j++)
							{
								int x = (int) (points[j].getX() * m_skalaFaktor);
								int y = (int) (points[j].getY() * m_skalaFaktor);
								polygon.addPoint(x,y);
							}
					
							g.setColor(offense.getOwner().getColor());
							g.fillPolygon(polygon);
							g.setColor(Color.RED);
							drawCountryLink(g,offense, defense);
						}
						blinkOn = !blinkOn;
						
						repaint();
						Thread.sleep(500);
					}
				}
				catch (Exception ex)
				{
					MExceptionHandler.handleException(ex);
				}
/*				
			}
		}).start();
*/
	}

Habe dass wie gesagt mit und ohne Thread verscuht, dabei stellte sich heraus, dass repaint offenbar tatsächlich munter paintComponent auszulösen scheint.

Würde heir eine inwokeAndWati oder inwvokeLater Sinn machen?
irgendwie fehlt mir da aber die Vorstellung wie das dann läuft .... :-/

Hat jemand vielleicht einen Hinweis oder besseren Ansatz?

mit neujahrentgegenstrahlenden Grüßen

Takidoso
 

Fabio Hellmann

Erfahrenes Mitglied
Hi,
also grundlegend würde ich sagen, dass bei einem Spiel immer ein Thread benötigt wird, der das Spiel neu zeichnet und neu berechnet.

In deinen Paint-Methoden musst du dann das reinschreiben, was gezeichnet werden soll, in welchem Fall. Sprich:
Java:
protected void paintBattle(Graphics2D g, Country country) {
   if(country.isAttacked()) {
      // Hier schreibst du rein, was gemalt werden soll, wenn das Land angegriffen wird
   } else {
      // Hier schreibst du dann den "Default" rein
   }
}

Gruß

Fabio
 

genodeftest

Erfahrenes Mitglied
Das Problem ist, dass du die Animation nicht vollständig im Java-AWT-Event-Thread ablaufen lassen kannst, weil du damit das Zeichnen des Rests der Anwendung blockierst. Deswegen solltest du die Animation über Flags steuern, die über einen Counter (Attribut der Klasse) oder einen Timer gesetzt werden. Zeichnen solltes tu auf gar keinen Fall in einem anderen Thread oder Runnable durchführen!

Am besten ist, du definierst eine gewisse Abfolge und setzt dazu ein Flag, das bei jeder Zeichnung (d.h. bei jedem Aufruf von paintComponent() ) ein mal erhöht wird, damit du weißt, bei welchem Schritt du bist. Dann brauchst du noch einen Timer, der mindestens 25 mal pro Sekunde repaint() initiierst (damit du 25 FPS bekommst und die Animation damit flüssig erscheint).
 

takidoso

Erfahrenes Mitglied
Hallo genodeftest ,
Das ist ein interessanter Ansatz.
Also man hat einen Thread, der repaint ansteuert und damit wird paintComponent getriggert.
paintComponent selbst hat eine algorithmik, die nach jedem antriggern praktisch ein "Bild" weiter geht.

Mal schauen wie ich das bei mir umsetzen kann. Aber danke vielmals für die Grundidee.

edit:
Jo hat generell geklappt bei mir danke nochmals für den Gedankenanstoß :)