2D-Grafik Minispiel mit Swing ruckelt

Tjong

Mitglied
Hallo erstmal 🙂
Ich versuche mich jetzt seit langem mal wieder mit Java und habe mir als Motivation mal vorgenommen ein kleines Minispiel zu schreiben, in dem man halt rumlaufen kann und so 😉
Das ganze soll grafisch ziemlich unaufwendig sein, also nur praktisch mit Rectangles und so zumindest vorerst gezeichnet werden...
Dazu habe ich wie schon oft beschrieben ein Panel erstellt und PaintComponent überschrieben usw., mein Problem ist jetzt dass es zwar auch eigentlich schön funktioniert, allerdings ruckelt das ganze etwas unschön, es kann aber eigentlich kaum an meiner Hardware liegen, denn auf der läuft unter Windows selbst GTA IV ohne solche Framedrops wie hier :/
Ich habe auch schon versucht am Timerintervall zu drehen der "repaint();" aufruft, das ändert gar nichts und auch der Timer der den Kreis bewegt (welcher die Spielfigur darstellt) kann so schnell gestellt sein wie es geht und es ruckelt trotzdem...
Das hier ist übrigends der Code der dafür relevant sein sollte:
Java:
    public GraphicOutput ()
    {
        gMainWindow = new JFrame("Spiel");
        gMainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        gMainWindow.setPreferredSize(new Dimension(SCREENWIDTH, SCREENHEIGHT));
        gMainPanel = new JPanel() {
            private static final long serialVersionUID = 1L;
            public void paintComponent(Graphics g)
            {
                super.paintComponent(g);
                gMainGraphics = (Graphics2D) g;
                updateScreen();
            }
        };
        gMainWindow.setContentPane(gMainPanel);
	gMainWindow.pack();
	gMainWindow.setVisible(true);

	Timer timer = new Timer ( ) ;
	//timer.scheduleAtFixedRate(new RefreshScreen(), 0, (int)(1000/Settings.FRAMES_PER_SECOND));
	timer.schedule(new RefreshScreen(), 0, (int)(1000/Settings.FRAMES_PER_SECOND));
	}

	public void updateScreen ()
	{
        gMainGraphics.fillOval(Math.round(gMainPlayer.Position[0]+SCREEN_CENTER_X-10), 
                                Math.round(gMainPlayer.Position[1]+SCREEN_CENTER_Y-10), 20, 20);
	}

	class RefreshScreen extends TimerTask {
		public void run() {
	    	gMainPanel.repaint();
	    }
	}
Ist vllt. Swing gar nicht für sowas ausgelegt? Also für ca. 30 mal neuzeichnen pro Sekunde o.ä.? Wenn ja, was gibt es dann für bessere Alternativen dafür? oO
Ich hoffe mal jemand weiß Rat :S

MfG
Tjong
 
ja Settings.FRAMES_PER_SECOND ist ein int und hat im Moment den Wert 20 was also 50 FPS entspricht wenn ich mich hier nicht verrechne und mal davon abgesehen hab ich nen 4 Kern Prozessor mit jeweils 3.33 Ghz, der java Prozess zeigt in der Systemüberwachung (nutze Ubuntu) meistens 0 höchstens 4 % Auslastung an, also überfordert würd ich das mal nicht nennen... oO

So dramatisch falsch scheint daran nichts zu sein. Vielleicht würde man bei einem KSKB einen Fehler sehen.

Wie erstellt man denn genau so ein KSKB?
 
ja Settings.FRAMES_PER_SECOND ist ein int [....]
hatte einen Denkfehler drinnen, deswegen hab ich den Quatsch den ich geschrieben hab auch direkt wieder weggeditiert^^

Ein KSKB ist einfach nur ein Stück Code den wir genauso wie er ist rauskopieren, compilen und ausführen können. Damit kann man das Problem viel leichter nachvollziehen.
 
1000/20 bedeutet, dass er alle 50 Millisekunden neu zeichnet, und das sind 1000/50 = 20 FPS.
Benutzt du mehr, als einen Thread? Wenn nicht, ist es egal, wie viele Cores/CPUs du hast.

Ein KSKB (Kurzes selbständig compilierbares Beispiel) ist einfach eine sehr gekürzte Version deines Programms, die das wesendliche Problem zeigt.
 
Tschuldigung falls ich mich gerade doof anstelle :/ aber heißt das jetzt dass ich Teile des SourceCodes von dem Programm zeigen soll oder das ich mal eine .jar kompilieren soll um zu zeigen wie sich das ruckeln äußert? Oder beides? ^^
 
Keine JAR. Nur ein Stück quellcode, mit einer main() drin, und ohne irgendwelche externen (nicht-standard-Java-) Bibliotheken, so dass jeder es mit Copy&Paste schnell rauskopieren, direkt und ohne Fehlermeldungen compilieren und starten kann. (Manche Leute beantworten hier 20 oder mehr Fragen dieser Art am Tag - wenn ich für jedes "public static void main(String args[])" das ich bisher in meinem Leben geschrieben habe, einen €-cent bekommen würde ... :reflect: 😉 )
 
So hier ist die Klasse, hat aber etwas gedauert das ganze zu modifizieren, ich hoffe mal das geht so 😉

Achja mir ist gerade was aufgefallen... Das Fenster fängt immer nur an zu "ruckeln" wenn ich den Mauszeiger nicht bewege, bzw. wenn ich sehr hektisch mit dem Mauszeiger über dem Fenster hin und her hampel, dann läuft das ganze flüssig wie sonst nix, kann es deshalb vllt. daran liegen, dass das sich wirklich nur "gedrosselt" erneuert und wenn ich mit dem Mauszeiger drüber geh erzwing ich ne Erneuerung?

Java:
package mainPackage;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.JFrame;
import javax.swing.JPanel;

public class GraphicOutputSimple {

	
	public static void main(String[] args) throws IOException 
	{
		new GraphicOutputSimple();

	}

	int	
		SCREENWIDTH  = 	Toolkit.getDefaultToolkit().getScreenSize().width,
		SCREENHEIGHT = 	Toolkit.getDefaultToolkit().getScreenSize().height,
		SCREEN_CENTER_X = Math.round(SCREENWIDTH/2),
		SCREEN_CENTER_Y = Math.round(SCREENHEIGHT/2),
		PLAYER_ICON_SIZE = 20;

	public JFrame
		gMainWindow;
	public JPanel
		gMainPanel;
	public Graphics2D
		gMainGraphics; 
	
	// das hier ist extra eingefügt damit kein Input erfolgen muss
	public boolean 
		movingAlong = false;
	// das hier ist extra eingefügt damit kein Input erfolgen muss
	
	public float []
		gMainPlayer;
	
	public GraphicOutputSimple () throws IOException
	{
		gMainPlayer = new float[2];
		gMainPlayer[0] = 0;
		gMainPlayer[1] = 0;
		
		// creating main window
		gMainWindow = new JFrame("Game");
		gMainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		gMainWindow.setPreferredSize(new Dimension(SCREENWIDTH, SCREENHEIGHT));
		
		gMainPanel = new JPanel() {
			private static final long serialVersionUID = 1L;

			public void paintComponent(Graphics g)
			{
				super.paintComponent(g);
				gMainGraphics = (Graphics2D) g;
				
				// das hier ist extra eingefügt damit kein Input erfolgen muss
				gMainPlayer[1] += movingAlong ? 2.0 : -2.0;			
				if (gMainPlayer[1] < -800)
					movingAlong = true;
				else if (gMainPlayer[1] > -400)
					movingAlong = false;
				// das hier ist extra eingefügt damit kein Input erfolgen muss
				
				// draw elements
				updateScreen();
			}
		};
		gMainWindow.setContentPane(gMainPanel);	
		gMainWindow.pack();
		gMainWindow.setVisible(true);
		
		// replace Screensizes with the window sizes
		Dimension 
			realSize = gMainWindow.getSize();
		SCREENWIDTH = realSize.width;
		SCREEN_CENTER_X = Math.round(SCREENWIDTH/2);
		SCREENHEIGHT = realSize.height;
		SCREEN_CENTER_Y = Math.round(SCREENHEIGHT/2);

		// start timer
		Timer timer = new Timer ( ) ;
		//timer.scheduleAtFixedRate(new RefreshScreen(), 0, 20);
		timer.schedule(new RefreshScreen(), 0, (int)(20));
	}

	/*-------------------------------------------*-
	  ===========================================
	 					Callbacks
	  ===========================================
	-*-------------------------------------------*/
	
	/* void updateScreen ( )
	 <= gMainPanel.repaint
	 => draws every object */
	Rectangle testobject = new Rectangle();
	float testfloatobject [] = { 300-Math.round(400/2), 300-Math.round(400/2) };
	//LandscapeObject testobject = new LandscapeObject(300, 300, 400, 400, 0.0f);
	public void updateScreen ()
	{
		testobject.setRect(300-Math.round(400/2), 300-Math.round(400/2), 400, 400);
		updateObjectToPlayerPosition(testobject);
		gMainGraphics.setColor(new Color(100, 100, 100));
		gMainGraphics.fill(testobject);
		
		// draw Player symbol
		gMainGraphics.setColor(new Color(0, 255, 100));
		gMainGraphics.fillOval(SCREEN_CENTER_X-(PLAYER_ICON_SIZE/2), 
								SCREEN_CENTER_Y-(PLAYER_ICON_SIZE/2),
								PLAYER_ICON_SIZE, PLAYER_ICON_SIZE);
		gMainGraphics.setColor(new Color(0, 0, 0));
		gMainGraphics.drawOval(SCREEN_CENTER_X-(PLAYER_ICON_SIZE/2), 
								SCREEN_CENTER_Y-(PLAYER_ICON_SIZE/2),
								PLAYER_ICON_SIZE, PLAYER_ICON_SIZE);
	}


	public void updateObjectToPlayerPosition (Rectangle theObject)
	{
		theObject.x = (int) (testfloatobject[0] - gMainPlayer[0]) + SCREEN_CENTER_X;
		theObject.y = (int) (testfloatobject[1] - gMainPlayer[1]) - SCREEN_CENTER_Y;
		//System.out.println("x: " + theObject.graphicObject.x + " y: " + theObject.graphicObject.y);
	}

	class RefreshScreen extends TimerTask {
		public void run() {
			gMainPanel.repaint();
	    }
	}

}
 
Bei mir ruckelt da garnichts, egal was ich mit der Maus mache.
Könnte bei der Größe des Fenster aber daran liegen, dass er einfach zu viel neu zu zeichnen hat. Du könntest einfach repaint(x, y, width, height) oder repaint(Rectangle) benutzen, damit der Bereich, wo sich nichts geändert hat auch nicht erneuert wird.
 
Hm kann sein dass es an Linux liegt oder so.
Ich habs jetzt aber gefixt bekommen, ich rufe einfach zusätzlich zu gMainPanel.repaint();
noch
gMainWindow.pack();
im Timer auf und dann aktualisiert der das auch vernünftig 🙂
Trotzdem vielen Dank für die Hilfe 🙂
 
Neee... pack sollte man da nicht aufrufen.

Anmerkungen: Man sollte nicht in paintComponent irgendwas anderes machen als zu Zeichnen. Bei dir wird da auch die Bewegung ausgeführt, d.h. wenn du das Fenster während der Bewegung mal schnell mit der Maus größer und kleiner ziehst, wird die Bwegung schneller.

Mit dem "Graphics" objekt solltest du aufpassen. Rein technisch ist das zwar im Moment "OK", aber man sollte eigentlich kein Graphics-Objekt irgendwo zwischenspeichern. Das kann zu Problemen führen, wenn man es irgendwo zwischendurch (nicht von paintComponent aus) verwendet.

Vielleicht wäre ein eigener Thread (statt eines Timers) da günstiger.... der könnte dann ggf. auch die Bewegung machen, die jetzt in paintComponent gemacht wird, und er würde unabhängig vom EDT laufen. Nur ein Tipp.
 
Neee... pack sollte man da nicht aufrufen.
Ich weiß dass man das nicht ausrufen sollte, aber es fixt bei mir nunmal das Problem, dass es ruckelt und ich lass es dann erstmal so, solange es keine Probleme macht.

Anmerkungen: Man sollte nicht in paintComponent irgendwas anderes machen als zu Zeichnen. Bei dir wird da auch die Bewegung ausgeführt, d.h. wenn du das Fenster während der Bewegung mal schnell mit der Maus größer und kleiner ziehst, wird die Bewegung schneller.
Wie gesagt ich hab das ganze hier sehr sehr vereinfacht damit es nur eine Klasse ist die dann auch kompilierbar ist.
"// das hier ist extra eingefügt damit kein Input erfolgen muss"
Hab ich ja auch extra eingefügt damit man sieht, dass es normalerweise nicht da ist 😉 eigentlich ist das ganze schon relativ komplex mit Keyboard-Input der Bewegungsabläufe auslöst, welche dann wiederum die Spieler Position seperat manipulieren...

Mit dem "Graphics" objekt solltest du aufpassen. Rein technisch ist das zwar im Moment "OK", aber man sollte eigentlich kein Graphics-Objekt irgendwo zwischenspeichern. Das kann zu Problemen führen, wenn man es irgendwo zwischendurch (nicht von paintComponent aus) verwendet.
Hm ok ich habs jetzt einfach mal als Parameterübergabe an "updateScreen" gemacht oder ist das auch schlecht? oO

Vielleicht wäre ein eigener Thread (statt eines Timers) da günstiger.... der könnte dann ggf. auch die Bewegung machen, die jetzt in paintComponent gemacht wird, und er würde unabhängig vom EDT laufen. Nur ein Tipp.
Ja ich werd mich vllt. mal damit auseinander setzen wie man einen eigenen Thread macht, hab bisher größtenteils nur Schulkentnisse über Java und naja wie ich sehe ist das gerade mal umgerechnet ein Baum auf der Oberfläche der Insel die noch einen riesigen Unterwasserteil hat 😉
 
Ich weiß dass man das nicht ausrufen sollte, aber es fixt bei mir nunmal das Problem, dass es ruckelt und ich lass es dann erstmal so, solange es keine Probleme macht.

Ein bekanntes Zitat, von jemandem, von dem man behaupten kann, dass er mehr als "rudimentäre Anfängerkenntnisse" im Programmieren hat, lautet: "Premature optimization is the root of all evil". Ich bin mal so anmaßend, zu behaupten, dass das nicht stimmt. Premature Optimization sorgt of für Schierigkeiten und Unschönheiten und so. Aber so richtig abgefuckter trash entsteht meiner Erfahrung nach durch etwas anderes: Nämlich durch den Versuch, Fehler zu beheben, deren Ursache man nicht richtig analysiert und verstanden hat.

Hm ok ich habs jetzt einfach mal als Parameterübergabe an "updateScreen" gemacht oder ist das auch schlecht? oO
Nein, das ist OK. Jeder, der jetzt "updateScreen" aufrufen will, muss sich im klaren sein, welches Graphics-Objekt dort übergeben werden muss. Man kann es nicht mehr "einfach so" (zu einem falschen Zeitpunkt, d.h. nicht von paintComponent aus) aufrufen. (Und wenn doch, dann kann man dort ein Graphics von einem BufferedImage übergeben, und den ganzen Schmodder in ein Bild reinmalen. Kann ja auch manchmal praktisch sein 🙂 )
 
Ein bekanntes Zitat, von jemandem, von dem man behaupten kann, dass er mehr als "rudimentäre Anfängerkenntnisse" im Programmieren hat, lautet: "Premature optimization is the root of all evil". Ich bin mal so anmaßend, zu behaupten, dass das nicht stimmt. Premature Optimization sorgt of für Schierigkeiten und Unschönheiten und so. Aber so richtig abgefuckter trash entsteht meiner Erfahrung nach durch etwas anderes: Nämlich durch den Versuch, Fehler zu beheben, deren Ursache man nicht richtig analysiert und verstanden hat.
ok 😀
naja also ich habs ja möchte ich mal behaupten einigermaßen analysiert und bin halt zu dem Schluss gekommen, dass es eigentlich flüssig gezeichnet wird aber das Fenster halt sehr gemütlich ist was das updaten angeht, vllt. liegts ja auch einfach irgendwie an Linux da ja bei Volvagia nichts zu ruckeln schien...
Ich hab ja auch gesucht ob es vllt. eine Methode gibt die ein Fenster/Panel/Graphics-Objekt dazu zwingt sich zu aktualisieren wenn man repaint(); aufruft, hab ich halt nur nicht gefunden...
Und dann hab ich mir gedacht, dass man es halt irgendwie zwangsaktualisieren muss mit einem anderen Befehl und das ging dann halt mit pack() wobei ich es gerade zu "setVisible(true)" geändert habe, was den gleichen Effekt hat 😉
 

Zurück
Oben