TileMap ruckelt

Status
Nicht offen für weitere Antworten.

Quaxli

Top Contributor
Ich habe ein Problem beim Scrollen einer Tile-Map und könnte ein paar Denkanstöße gebrauchen....

Momentan habe ich ein sehr rudimentäres Programm, bei dem ich über eine Tile-Map scrolle. Mehr als anzeigen wird nicht gemacht. Es wird nichts hinzugefügt, keine Kollisione berücksicht, etc.. Trotzdem ruckelt das Teil. Was mir aufgefallen ist: Ich bewege die Tiles in Abhängigkeit von der Zeit, die der letzte Durchlauf der GameLoop benötigt und dort sind durchaus größere Schwankungen drin:

Beispiel (Zeitermitllung mit System.nano())

15519011
15624612
15661488
15868497
13840027 <---
17437971 <---
17158046
14283100
15872688
15580752

Aber solche Schwankungen sollten durch die Berechnung der Bewegung in Abhängigkeit vom Schleifendurchlauf ja kompensiert werden. Die Tiles werden über ein Rectangle2D.Double positioniert, daran sollte es auch nicht liegen.

Ich habe auch schon auf actives Rendern und Vollbild umgestellt, in der Hoffnung, daß das ein Ergebnis zeigt, aber das hat leider auch nichts gebracht. :(

Größere Codemengen will ich hier erst mal nicht reinpacken. Ich skizziere mal kurz den Programm-Aufbau

- Die Grafiken der Tiles werden vorab in eine Verwalter-Klasse geladen und numeriert.
- Die Tiles selber werden in einem Vector gespeichert und enthalten als Referenz auf die Grafikdatei die Nummer aus der Verwalterklasse
- beim Zeichnen wird geprüft, ob die Position des Tiles sich mit der virtuellen Bildschirmposition überdeckt. Wenn ja, wird es gezeichnet, dazu wird das Image aus der Verwalter-Klasse genommen.
- zur Simulation der Bewegung werden nicht die Tiles bewegt, sondern das Rechteck (Double), das die virtuelle Bildschirmposition repräsentiert.

Wäre schön, wenn jemand eine Idee hat.
 

Evolver

Bekanntes Mitglied
Eine Idee hätte ich, habe aber offen gestanden keine Ahnung, ob sie irgendetwas bring oder nicht vielleicht sogar mehr Aufwand bedeutet, als das ursprüngliche Vorgehen. Anstelle bei jedem Zeichnen alle sichbaren Teilstücke der Map einzeln zu zeichnen, könntest du den sichtbaren Teil (oder etwas darüber hinaus) vorrendern. Du müsstest dann nicht in jedem Zeichenschritt die Map neu Zusammensetzen, sondern je nach Bewegungsgeschwindigkeit dies z.B. nur sekündlich tun. Da die Map ja der zusammengesetze Kartenausschnitt ja auch gewissermaßen den "Hintergrund" darstellt, müsste dort auch keine Transparenz beachtet werden.
Ich habe selbst soetwas noch nicht gemacht, habe also keine Ahnung, ob das was bringt. Aber es ist immerhin eine Idee.
 

Quaxli

Top Contributor
Hi, danke für den Tipp. Ich glaube aber nicht, daß das etwas bringt. Ich habe versucht, die Karte erst in ein Image zu zeichnen und dadurch ist mir die Framerate zusammengebrochen und das Ruckel war noch heftiger. Entsprechend würde bei Deinem Vorschlag das Ganze zwar in größeren Abständen passieren, aber vermutlich trotzdem eine Verzögerung bedeuten.
 

Evolver

Bekanntes Mitglied
Vielleicht sollte diese Funktion dann in einen eigene Thread ausgelagert werden. Aber war ja auch nur so eine Idee.

Vier Fragen habe ich:
1. In welcher Auflösung (bzw. beim Fenster die Framegröße) läuft denn dein Programm?
2. Wieviele Tiles hast du denn pro sichtbaren Ausschnitt?
3. Sind alle Tiles mit Alpha-Wert?
4. Du arbeitets doch bestimmt mit activeRendering? Nutzt du da schon VolatileImage als Bilschirmpuffer?
 

Quaxli

Top Contributor
1. 800 x 600
2. ca. 200 - ändert sich u. U. wenn sich der sichtbare Bereich verschiebt. Tilegröße 50 x 50
3. nein
4. active Rendering: ja - VolatileImage: nein
 

Quaxli

Top Contributor
Nö. Bisher hatte ich noch keine rechte Notwendigkeit dafür. Ich werde das mal einbauen.
 

Quaxli

Top Contributor
So, jetzt hab' ich VolatileImages reingebaut und es ruckelt immer noch wie die S... . Entweder mache ich was grundlegend falsch oder es liegt am Rechner. Ich habe mal eine "kleines" Testprogramm gebastelt, das BufferStrategy und VolatileImages verwendet und selbst dieses einfache Programm ruckelt ohne Ende.
Es wäre nett, wenn Ihr mal da drüber gucken könntet, ob ich was grundlegend falsch gemacht habe.

Bedingt durch die Verwendung von VolatileImages und BufferStrategy ist das Ganze halt etwas umfangreicher und die Methode zum Erstellen der Images könnte auch kürzer sein, aber ich denke es hält sich noch in Grenzen.

Danke schon mal im Voraus. :wink:

Code:
package test;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.image.*;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;

public class TestPanel extends Canvas implements KeyListener{

	private static final long	serialVersionUID	= 1L;

	protected long delta = 0;
	protected long last = 0;
	protected long fps = 0;
	
	BufferStrategy strategy;

	VolatileImage vimg;
	GraphicsEnvironment ge;
	GraphicsConfiguration gc;
	
	int x = 0;
	
	public static void main(String[] args) {
		new TestPanel(800,600);
	}
	
	//Konstruktor
	public TestPanel(int w, int h){
		
		//GraphicsConfiguration für VolatileImage
		ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
		gc = ge.getDefaultScreenDevice().getDefaultConfiguration();
		
		//Fenster bauen und Canvas reinpacken
		this.setPreferredSize(new Dimension(w,h));
		addKeyListener(this);
		
		Frame frame = new Frame("GameDemo");
		frame.setLocation(100,100);
		//frame.setUndecorated(true);
		frame.add(this);
		frame.pack();
		frame.enableInputMethods(false);
		frame.setVisible(true);
		
		//BufferStrategy initieren
    createBufferStrategy(2);
    strategy = getBufferStrategy();
    setIgnoreRepaint(true);

    //Volatile Image laden
    vimg = loadPics("pics/car.gif",1,1)[0];  // [0] <<< nur das erste Bild des zurückgegebenen Arrays
    
		run();
	}

	public void run() {
		
		while(true){
			
			computeDelta();         //Delta des letzten Durchlaufs
			x += 200*(delta/1e9);   //Geschwindigkeit 200 in Abhängk. vom letzten Durchlauf
			if(x>getWidth()){       // Rausgelaufen -> vorne anfangen
				x = 0;
			}
			render();			          //malen
			
		  try {
				Thread.sleep(10);     //Päuschen
			} catch (InterruptedException e) {}
			
		}
		
	}
	
	public void render(){
	  Graphics g =  strategy.getDrawGraphics();  //Backbuffer der BufferStrategy holen
	  g.setColor(Color.black);
	  g.clearRect(0, 0, getWidth(), getHeight()); //löschen
 	  paintAll(g);                                //alles reinpinseln
 	  g.setColor(Color.red);
	  g.drawString(Long.toString(fps), 50, 50);   //FPS
		g.dispose();	                              		
		strategy.show();                            //anzeigen
	}
	
	
	
	//Hier wird gemalt
	public void paintAll(Graphics g){
		gc = ge.getDefaultScreenDevice().getDefaultConfiguration();
		
		//Volatile Image noch o.k.?
		if(vimg.validate(gc)== VolatileImage.IMAGE_INCOMPATIBLE){
			vimg = loadPics("pics/car.gif",4,1)[0];
			System.out.println("neu geholt");
		}
		
		//Image zeichnen
		g.drawImage(vimg, x, 100, this);
	}

	//Delta ausrechnen
	protected void computeDelta(){

		delta = System.nanoTime() - last;
		last = System.nanoTime();
		
		fps = ((long) 1e9)/delta;
	}
	
	//VolatileImage laden - Parameter: Pfad, Anzahl Subimages pro Spalte, Anzahl Zeilen
	protected VolatileImage[] loadPics(String path, int column, int row){
		
		BufferedImage singlepic = null;
		URL location =getClass().getClassLoader().getResource(path);
		
		try {
			singlepic = ImageIO.read(location); //BufferedImage ladne
		} catch (IOException e) {
			e.printStackTrace();
		}

		int width = singlepic.getWidth() / column;
		int height = singlepic.getHeight() / row;

		BufferedImage[] pics = new BufferedImage[column * row];
		int count = 0;

		//Geladenes Bild aufdröseln entsprechen der angegebenen Parameter
		for (int n = 0; n < row; n++) {
			for (int i = 0; i < column; i++) {
				pics[count] = singlepic.getSubimage(i * width, n * height, width, height);
				count++;
			}
		}
		
		//Array für VolatileImages erstellen
		VolatileImage[] vimg = new VolatileImage[pics.length];

		//BufferedImage-Array abnudeln und in VolatileImage-Array reinzeichnen
		for(int i=0;i<vimg.length;i++){
			vimg[i] = gc.createCompatibleVolatileImage(pics[i].getWidth(), pics[i].getHeight());
			vimg[i].validate(gc);
			System.out.println(vimg[i].validate(gc));
			Graphics2D g = (Graphics2D) vimg[i].createGraphics();
			
			g.setComposite(AlphaComposite.Src);
			g.setColor(Color.black);
			g.clearRect(0, 0, vimg[i].getWidth(), vimg[i].getHeight()); // Clears the image.
			g.drawImage(pics[i],0,0,null);
			g.dispose();
			
		}
		
		return vimg;
		
	}

	public void keyPressed(KeyEvent e) {
				
	}

	public void keyReleased(KeyEvent e) {
		if(e.getKeyCode()==KeyEvent.VK_ESCAPE){
			System.exit(0);
		}
	}

	public void keyTyped(KeyEvent e) {
				
	}

}
 

Evolver

Bekanntes Mitglied
Also es ist jetzt schwer für mich, da Konkret etwas zu sagen, weil ich mit BufferStrategy und so noch garnichts gemacht habe, aber deine Verwendung von VolatileImage scheint mir der falsche Weg zu sein. Ich glaube es macht nur Sinn, wenn es als denn tatsächlich als Buffer für die Ausgabe verwendet wird. Die einzelnen Grafiken als VolatileImage zu machen dürfte nix bringen. Hier mal der Thread, als ich mich damit befasst hatte:
http://www.java-forum.org/de/viewtopic.php?t=57432
 

Marco13

Top Contributor
Hm. Bei mir läuft's flüssig mit 93 fps (mit einem 200x200 GIF)... Das mit dem kompletten Neuladen des VolatileImages sieht seltsam aus - ich habe damit zwar noch nichts gemacht, kann mir aber kaum vorstellen, dass man das Bild wirklich nue laden muss. (War wohl nur zum testen - das "neu geholt" hat er bei mir ohnehin nie ausgegeben....)
 

Wildcard

Top Contributor
Evolver hat gesagt.:
Die einzelnen Grafiken als VolatileImage zu machen dürfte nix bringen.
Richtig. Ein VolatileImage verwendet man als Buffer im Speicher der Grafikkarte der dann nur noch auf das Display kopiert werden muss (von der Grafikkarte).
Dort kommt also der fertig gerenderte Teil hin.
Auch bringen die ganzen Tricks wie BufferStrategy, VolatileImage, PageFlipping,... keine echte Performance, sondern nur gefühlte Performance (schnellere Bildübergänge).
Ohne den Thread gelesen zu haben, der Flaschenhals liegt meistens an anderer Stelle. Blindes optimieren verursacht mehr Schaden als Nutzen.
 

Quaxli

Top Contributor
Nachdem's bei Marco flüssigl läuft, scheint es die Möhre im Büro zu sein.. :(
Hier, privat, läuft es auch sehr flüssig. Dachte nicht, daß die Kiste im Büro sooo schlecht ist. :(

Das mit dem Neuladen der Images habe ich aus diversen Tutorials/Blogs. Es ist wohl so, daß eine VolatileImage "verloren gehen" kann. Ich habe beim Testen versucht, das zu provozieren, es aber nie geschafft. ???:L
Die ganze Grafiken habe ich mehr oder weniger testhalber in VolatileImage umgewandelt, weil ich das als vorteilhaft erachtet habe. Ich werde mich wohl noch eine Weile damit beschäftigen.

Danke für Eure Tipps.
 

Wildcard

Top Contributor
Ein Volatile Image liegt wie schon erwähnt im Speicher der Grafikkarte. Dieser wird jedoch (anders als der logische Adressraum im Arbeitsspeicher) nicht exklusiv einem Prozess zur Verfügung gestellt.
Das OS, die Grafikkarte, oder Prozess Y kann dein volatile Image also jederzeit zerstören.
 

Marco13

Top Contributor
Das VolatileImage an sich, ja, aber dafür dann jedesmal einen File-Zugriff zu machen ... häm ???:L Man sollte das Bild dann vielleicht(!?) eher in einem BuffereImage speichern, und das BufferedImage ggf. ins VolatileImage reinzeichnen ... aber es war ja nur ein kleines Testporgramm.
 

Quaxli

Top Contributor
Normalerweise verwende ich eine SpriteLib, die meine Grafiken als BufferedImage in einer HashMap speichert, aber ich wollte den Code, der so schon relativ lang war nicht noch unnötig aufbohren.

Und jetzt hört auf den Haken, der das Thema abschließt, zu ignorieren ;)
 

Evolver

Bekanntes Mitglied
Wildcard hat gesagt.:
Ohne den Thread gelesen zu haben, der Flaschenhals liegt meistens an anderer Stelle. Blindes optimieren verursacht mehr Schaden als Nutzen.
Aber da kommt in mir eine Frage auf: Wenn man nun festgestellt hat, dass der Flaschenhals die graphische Ausgabe ist, was dann? Ich meine damit, gibt es außer den von dir genannten "Tricks" denn echte Möglichkeiten, an dieser Stelle zu optimieren (außer den gängigen Sachen, wie überflüssiges Zeichenn vermeiden, überflüssige Transparenz vermeiden)? Ich meine eben Dinge, auf die man nicht von alleine kommt, wenn man nur einmal im Jahr zum Spaß an 'nem kleinen Spiel bastelt.
 

Evolver

Bekanntes Mitglied
Profiler? Sind das nicht Programme zum Suchen der Flaschenhälse? Der Lösung kommt man damit jan icht unbedingt näher, wenn man eh schon weiß (wie ich schrieb), dass die Schwachstelle die graphische Ausgabe ist (z.B. dnak zu vieler Grafiken, oder einiger großer mit Transparenz).
 

Quaxli

Top Contributor
So, nach all den Postings die hier eigentlich nicht reingehören, jezt doch noch auch eine Anmerkung von mir. Für mein eigentliches Programm war jetzt eine Kombination aus BufferStrategy und VolatileImage eine gute Lösung. Das Programm läuft auch auf der Büro-Möhre mit 85 fps.

Gezeichnet wird jetzt wie folgt:

Code:
//doPainting wird periodisch aus dem GameLoop aufgerufen
private void doPainting(){

  checkBackbuffer();  //Prüf-Methode für VolatileImage
  Graphics g = backbuffer.getGraphics();  //GraphicsObject vom VolatileImage holen
  render(g);  //alle Zeichenoperationen: Map, Player, etc.
  g.dispose();  //Graphics-Objekt verwerfen
		
  Graphics g2 = strategy.getDrawGraphics();  //Zeichenobjekt der BufferStrategy holen
  g2.drawImage(backbuffer,0,0,this);   //VolatileImage in den Buffer zeichnen
  g2.dispose(); //GraphicsObject verwerfen
    
  strategy.show();  //Bufferanzeigen.
	}
 
Status
Nicht offen für weitere Antworten.

Ähnliche Java Themen


Oben