Von FPS und FrameLimitern

F

fps

Gast
Hallo,

nachdem ich mich entschlossen habe, ein eigenes Spiel mit Java zu schreiben, bin ich ersteinmal hier ins Forum gekommen und habe mir ein Paar Threads durchgelesen und die Gedanken darin. Dabei habe ich sowas wie FPS und FrameLimiter aufgefangen.

Also ersteinmal ist es ja klar, dass ich ein JFrame habe und eine Klasse, die von JComponent abgeleitet ist und ein Runnable ist und praktisch mein "Spielpainter" realisiert. Aus vielen Threads und einigen Tutorials ergibt sich folgender Standardmäßiger Aufbau der run-Methode:
Java:
public void run() {
   long delta, last, fps = 0;
   while (!Thread.interrupted()) {
      delta = System.nanoTime()-last;
      last = System.nanoTime();
      fps = ((long) 1e9)/delta;
      doOtherStuff();

      repaint();
      try {
         Thread.sleep(10);
      } catch (Exception e) {}
   }
}

Das funktioniert auch, doch bewegt sich bei mir die FPS-Rate bei 83 FPS. Und das ist - meiner Meinung nach - zu hoch, denn einige Anspruchsvolle Spiele (auf meinem PC) haben eine Rate, die sich zwischen 30-50 FPS bewegt.

Nun beim Nachdenken über dieses Problem viel mir das "Thread.sleep(10)" ins Auge: die Sleepzeit ist konstant. Kann man nicht irgendwie eine sich anpassende Sleepzeit berechnen anhand einer konstanden Framerate? Ich denke, dass ist es, was ein FrameLimiter macht: er sorgt dafür, dass die Framerate an das System angepasst wird und sich "normal" verhält. Ist ein langsames System vorhanden, geht die FPS durch den Limiter in den Keller, da FrameSkipping betrieben werden muss, damit nicht alles abschmiert.

Also, was will ich nun hier? Es geht mir darum, meine Gedanken noch ein bisschen zu ordnen, sodass ich mir überlegen kann, wie ich meinen GamePainter nun richtig aufbaue, damit mir das Ding später nicht vor die Füße fliegt!

Es wäre also wirklich super nett, wenn Ihr ein Paar Gedanken schreiben würdet, die mich irgendwie weiterbringen! Vieleicht habt Ihr auch noch Links oder soetwas - immer gerne!!!

Mit bestem Gruß,
fps (alias Max)
 

ice-breaker

Top Contributor
Das funktioniert auch, doch bewegt sich bei mir die FPS-Rate bei 83 FPS. Und das ist - meiner Meinung nach - zu hoch, denn einige Anspruchsvolle Spiele (auf meinem PC) haben eine Rate, die sich zwischen 30-50 FPS bewegt.
richtig, anspruchsvolle Spiele, also 3D-Spiele ;)
Das hier sind ja nur 2d-Operationen mit weit weniger Komplexität.

Zudem ist repaint ein asynchroner Aufruf, bei
Code:
repaint()
wird also nicht alles neu gezeichnet, sondern nur als Job in die AWT EventQueue eingereiht. ServiceRepaint müsste für das was du simulieren willst, angebrachter sein.

Eigentlich sollte dich die Framerate kein Stück interessieren, nur wenn das Spiel nachher zu langsam läuft um zu Erkennen an welchen Stellen.
 
F

fps

Gast
Das mit dem 3D-Spielen stimmt ... wie leicht man doch den Wald vor lauter Bäumen nicht sehen kann. Es ist also "normal", dass bei 2D-Spielen die FPS-Anzahl höher ist, da weniger Berechnungen durchgeführt werden müssen - leuchtet ja ein!

Ich habe mal "ServiceRepaint" gegoogelt und bin nur auf Themen zu J2ME gestoßen. Es handelt sich hierbei um eine Methode, die die Neuzeichnung sofort ausführt, oder? (EDIT: also für ein JComponent scheint es das nicht zu geben - oder ich hab es übersehen)

Okay, umso besser, wenn ich kein FrameSkipping und FrameLimiter benötige (das benötige ich nicht, habe ich jetzt so verstanden, da es nur in "richtigen" 3D-Spielen zum Einsatz kommt). Aber angenommen, ich habe alles implementiert und es läuft an einer Stelle jetzt ganz langsam. Woran kann das grundsätzlich liegen (also ohne näheres zu wissen)?

Also, ich nehme aus der Antwort mit, dass der "typische" Aufbau okay ist und funktioniert.

Gruß,
fps (alias Max)
 

Empire Phoenix

Top Contributor
Du machst die logic im selben thread wie das painten?
Logic A mit fester rate, B anhand der rendertime anpassen

Java:
	@Override
    public void simpleUpdate(float tpf){
    	long start = System.currentTimeMillis();
    	logicupdate();
    	
		long end = System.currentTimeMillis();
		int timepertick = (int) (end-start);
		int sleeptime = NHGlobals.TickTime - timepertick;
		if(sleeptime > 0){
			try {
				Thread.sleep(NHGlobals.TickTime - timepertick);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		
    }
 
F

fps

Gast
Also die Beispielmethode, die ich gepostet habe war nur eine 5-Minuten-Zusammenbastelung. Ich hatte schon vor, drei Threads zu realisieren: JFrame, Painter und das Game (also die Logik inkl. der Objekte etc.).
Logic A mit fester rate, B anhand der rendertime anpassen
Irgendwie verstehe ich die Methode nicht (und auch was Du geschrieben hast) ...
Java:
Thread.sleep(NHGlobals.TickTime - timepertick);
kann man ersetzen durch (ist das selbe):
Java:
Thread.sleep(sleeptime);

Aber was macht nun die Methode? Sie sorgt dafür, dass der Thread solange schläft, wie es dauert, die Logik des Spiels zu "berechnen" also die Ausführung von "logicupdate();"?? Und wie bringt mich das weiter?

Gruß,
fps (alias Max)
 

Empire Phoenix

Top Contributor
Erstens isses einfach schnell zusammengeklatscht für dich und icht copy paste as is.

Und es sorgt dafür das logicupdate continuierlich gleich fot pro secunde augerufen wird. (wenn simpleupate einfach dauerhaft while(true){simpleupdate())
 

Steev

Bekanntes Mitglied
Wenn du so willst ist das ein equivalent zu doOtherStuff(), nur dass du halt noch das repaint besorgen musst.
 
F

fps

Gast
Und was ist der Parameter "tpf"? Der wird garnicht benötigt???

@"Erstens isses einfach schnell zusammengeklatscht für dich und icht copy paste as is."
Tut mir leid, aber ich bin immer stolz auf mich, wenn ich einen Fehler finde ... *freude* - Nein im ernst: es war nicht so gemeint, wie ich es gesagt habe ...

Kannst Du mir noch mal in einem Satz sagen, was die Methode genau macht (nicht nur technisch)?
 
F

fps

Gast
Ich habe mir mal die Java2D-Demo angeschaut, die mit dem JDK kommt (bei mir unter: "C:\Programme\Java\jdk1.6.0_16\demo\jfc\Java2D"). Warum läuft da die "BezierAnim" (2. Registerkarte, 2. Bild) mit 30 fps? Es handelt sich ja hierbei nicht um irgendeine besonders rechenintensive Animation? Dann könnte diese doch auch mit 30 fps laufen.
 
F

fps

Gast
Ich habe mich gerade mal durch den Quellcode gewühlt und sehe, dass die Anwendung (fast) genauso einfach aufgebaut ist, wie in meinem Code oben gepostet. Es wird eine Sleepzeit von 30 ms gewählt, daher ergeben sich halt nur 30 fps. Wenn man die Sleepzeit verringert (0), dann sind es auch wieder 70-80 fps. So, nun will ich wissen, nach welchem Kriterium man die Sleepzeit wählt? Wäre es nicht doch sinnvoll einen FrameLimiter einzubauen, wenn man sagt, ich will nahezu konstant 30 fps haben? Denn z.B. ein Film läuft ja mit rund 25 fps (PAL) ab. Dann würden 30 doch sinn machen, oder?
 

Empire Phoenix

Top Contributor
Ne tpf ist ein rest von der verwedeteten Engine,

Also ich berechne logic, gucke wie lange das gebraucht hat und warte dann noch solange bis ich eine vorgegeben zeit verbracht habe (die NHGobals.Ticktime) Also sage ich damti die logic soll nur alle x milisecundenn laufen. Das kann man dann entweder mit Thread.sleep machen (pausiert dann die "Anwendung/Thread") oder alternativ baut man sich eine Variable nextlogicupdate und guckt nach ob es bereits zeit ist für die nächste logic (rendert dann so schnell es geht aber halt mehrfach das selbe, hier könnte man jetzt eine interpolation einbauen, (physic wird nicht neuberechnet, man approximiert nur anhand der geschwindigkeit eines objectes wo es ungefähr sein müsste, so als beispiel(ich sollte mir echt abgewöhnen verklammerte Sätze zu schreiben(lol^^))))
 

Steev

Bekanntes Mitglied
Die Sache ist eigendlich die: Normalerweise kann man sich recht gut ausrechnen wieviele FPS man haben will und dann eine entsprechende Sleep-Zeit auswählen. Wenn man das Programm dann allerdings auf einem anderen Rechner durchführt, so kann es sein, dass die gewünschte Framerate dort garnicht erreicht wird, weil der Rechner viel besser, oder viel schlechter ist, als der eigene Rechner.
Also geht man einfach her und misst die Zeit, wie lange das Durchführen des Repaints bzw. der Logik dauert. Anhand dieser Dauer kann man sich ausrechnen, wie oft man bei dieser Zeit pro Sekunde neuzeichnen kann. Wenn man auf einen höheren Wert kommt, als man eigendlich will - zum Beispiel 75 FPS anstatt 30 FPS - so wartet man dann einfach so lange, dass man trotz der schnelleren Ausführungszeit auf seine 30 FPS kommt.
Für nichts anderes ist ein FrameLimiter da.

Angenommen man hat aber einen langsameren Zielrechner, der nicht die eingestellte FPS-Zahl schafft, so bekommt man immer ein Sleep von 0 Sekunden und die Prozessorauslastung schnellt auf 100%. Um das zu vermeiden verwendet man dann in der Regel noch das FrameSkipping-Verfahren, wo man sich dann ausrechnet, wie viele Repaints der Rechner bei der aktuellen Renderzeit am besten schaffen könnte. Also wie viele Bilder man überspringen muss, um wieder auf die zum Beispiel 30 FPS zu kommen.

Gruß
Steev
 
F

fps

Gast
Also, ich habe mal etwas geschrieben. Aber es tritt immer wieder eine Exception auf (es dauert immer ein Paar Sekunden):
Code:
Exception in thread "AWT-EventQueue-0" java.util.ConcurrentModificationException
	at java.util.AbstractList$Itr.checkForComodification(Unknown Source)
	at java.util.AbstractList$Itr.next(Unknown Source)
	at GameDemoPanel.draw(GameDemoPanel.java:65)
	at GameDemoPanel.paintComponent(GameDemoPanel.java:40)
	at javax.swing.JComponent.paint(Unknown Source)
	at javax.swing.JComponent.paintChildren(Unknown Source)
	at javax.swing.JComponent.paint(Unknown Source)
	at javax.swing.JComponent.paintToOffscreen(Unknown Source)
	at javax.swing.BufferStrategyPaintManager.paint(Unknown Source)
	at javax.swing.RepaintManager.paint(Unknown Source)
	at javax.swing.JComponent._paintImmediately(Unknown Source)
	at javax.swing.JComponent.paintImmediately(Unknown Source)
	at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
	at javax.swing.RepaintManager.paintDirtyRegions(Unknown Source)
	at javax.swing.RepaintManager.seqPaintDirtyRegions(Unknown Source)
	at javax.swing.SystemEventQueueUtilities$ComponentWorkRequest.run(Unknown Source)
	at java.awt.event.InvocationEvent.dispatch(Unknown Source)
	at java.awt.EventQueue.dispatchEvent(Unknown Source)
	at java.awt.EventDispatchThread.pumpOneEventForFilters(Unknown Source)
	at java.awt.EventDispatchThread.pumpEventsForFilter(Unknown Source)
	at java.awt.EventDispatchThread.pumpEventsForHierarchy(Unknown Source)
	at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.awt.EventDispatchThread.pumpEvents(Unknown Source)
	at java.awt.EventDispatchThread.run(Unknown Source)

Was mache ich falsch?

Java:
public class GameDemo {

	private JFrame window;
	private GameDemoPanel gdp;
	
	public GameDemo() {
		window = new JFrame("GameDemo mit Java2D");
		gdp = new GameDemoPanel();
		
		window.setLayout(new BorderLayout());
		window.add(gdp, BorderLayout.CENTER);
	}
	
	public void start() {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				window.pack();
				gdp.start();
				window.setVisible(true);				
			}
		});
	}
	
	public void stop() {
		gdp.end();
		window.dispose();
	}
	
	public static void main(String[] args) {
		GameDemo g = new GameDemo();
		g.start();
	}
	
}
Java:
public class GameDemoPanel extends JComponent implements Runnable {

	private static final long serialVersionUID = 1L;
	protected int sleepAmount = 30;
	
	private Thread thisThread;
	private long fps;
	
	public ArrayList<Point> stars;
	private HeapMon heap;
	
	public GameDemoPanel() {
		setSize(new Dimension(600, 450));
		setPreferredSize(new Dimension(600, 450));
		
		this.stars = new ArrayList<Point>();
		this.heap = new HeapMon();
	}
	
	public void start() {
		thisThread = new Thread(this);
		thisThread.start();
	}
	
	public void end() {
		thisThread.interrupt();
	}
	
	protected void paintComponent(Graphics g) {
		this.draw((Graphics2D) g, getWidth(), getHeight());
	}
	
	public void run() {
		long last = System.nanoTime(), delta = 0;
		
		while (!thisThread.isInterrupted()) {
		      delta = System.nanoTime()-last;
		      last = System.nanoTime();
		      fps = ((long) 1e9)/delta;
		 
		      doLogic();
		      repaint();
		      try {
		         Thread.sleep(sleepAmount);
		      } catch (Exception e) {}
		}
	}


	public void draw(Graphics2D g, int w, int h) {
		g.setColor(Color.black);
		g.fillRect(0, 0, w, h);
		
		g.setColor(Color.yellow);
		for (Point p : this.stars) {
			g.fillArc(p.x-2, p.y-2, 4, 4, 0, 360);
		}
		
		g.setColor(Color.green);
		g.drawString(Long.toString(fps) + " fps", 10, 20);
		this.heap.draw(g, w-32-5, 5);
	}
	
	private void doLogic() {
		synchronized (stars) {
			if (this.stars.size() > 1000)
				this.stars.clear();
			
			Point p = new Point(Math.round((float) Math.random()*600.0f),
					Math.round((float) Math.random()*450.0f));
			this.stars.add(p);	
		}
	}
	
	public class HeapMon implements Runnable {
		
		private int[] history;
		private int hcnt;
		
		public HeapMon() {
			history = new int[32];
			hcnt = 0;
			new Thread(this).start();
		}
		
		public void run() {
			while (!Thread.interrupted()) {
				update();
				try {
					Thread.sleep(999);
				} catch (Exception e) { }
			}
		}
		
		public void update() {
			long totalMemory = Runtime.getRuntime().totalMemory();
			long freeMemory = Runtime.getRuntime().freeMemory();
			
			int used = Math.round(((float) (totalMemory-freeMemory)/totalMemory)*100.0f);
			
			history[hcnt] = used;
			hcnt++;
			
			if (hcnt >= history.length)
				hcnt = 0;
		}

		public void draw(Graphics2D g, int x0, int y0) {
			
			g.setColor(new Color(0, 80, 0));
			g.fillRect(x0, y0, 32, 32);
			
			
			for (int i = 0; i < history.length; i++) {
				int u = history[i];
				
				if (u > 0) {
					g.setColor(Color.green);
					
					int h = 32-Math.round(32.0f*(((float) u)/((float) 100.f)));
					g.drawLine(x0+i, y0+h, x0+i, y0+32-1);
				}
			}
		}
		
	}
	
}
 

Steev

Bekanntes Mitglied
Und genau hier liegt das Problem:

[Java]for (Point p : this.stars) {[/Java]

Verwende an dieser Stelle folgendes:

[Java]Point p = null;
for (int i = 0; i < stars.size(); i++) {
p = stars.get(i);
// ...
}[/Java]

Dann sollte die Exception nicht mehr auftreten. Deine Iterationsart ist bei Threads und ohne Synchronisierung immer etwas "kritisch" zu verwenden. Vor allem, wenn sich an der Anzahl der Listenelemente zur Laufzeit verändert.

Gruß
Steev
 

Empire Phoenix

Top Contributor
Und genau hier liegt das Problem:

[Java]for (Point p : this.stars) {[/Java]

Verwende an dieser Stelle folgendes:

[Java]Point p = null;
for (int i = 0; i < stars.size(); i++) {
p = stars.get(i);
// ...
}[/Java]

Dann sollte die Exception nicht mehr auftreten. Deine Iterationsart ist bei Threads und ohne Synchronisierung immer etwas "kritisch" zu verwenden. Vor allem, wenn sich an der Anzahl der Listenelemente zur Laufzeit verändert.

Gruß
Steev


Oder man Synchronisert es einfach richtig, zb nimm einen Vector statt einer ArrayList, der ist Synchronisiert und gut is.
 

Neue Themen


Oben