# Tilemap Darstellungsfehler



## Raziell (24. Okt 2011)

Hallo zusammen,
ich zeichne eine Tilemap mittels aktivem Rendering (BufferStrategy + VolatileImage) 
allerdings habe ich ein Darstellungsproblem wenn die Tiles bis ans Ende gescrollt werden.
Ich hänge einfach mal mal ein Screenshot an, evtl. erkennt ja schon jmd. das Problem.
Auf Wunsch kann ich auch Code nachreichen wobei es nicht wirklich viel zu sehen gibt.

Danke


----------



## Tomate_Salat (24. Okt 2011)

Raziell hat gesagt.:


> Auf Wunsch kann ich auch Code nachreichen wobei es nicht wirklich viel zu sehen gibt.



Hmm ohne Code würde ich sagen, der Fehler liegt in Zeile 42.


----------



## Raziell (24. Okt 2011)

Hier mal eine Zusammenfassung:


```
private void initGUI(){	
        graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
		graphicsConfiguration = graphicsEnvironment.getDefaultScreenDevice().getDefaultConfiguration();

		this.setPreferredSize(new Dimension(Configuration.FRAME_WIDTH, Configuration.FRAME_HEIGHT));
		JFrame frame = new JFrame("");
		Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
		int left = (screenSize.width - Configuration.FRAME_WIDTH) / 2;
		int top = (screenSize.height - Configuration.FRAME_HEIGHT) / 2;
		frame.setLocation(left, top);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.add(this);
		frame.pack();
		frame.setIgnoreRepaint(true);
		frame.setFocusable(false);
		frame.setVisible(true);
		setFocusable(true);
		setBackground(Color.BLACK);
		addKeyListener(this);

		createBufferStrategy(2);
		bufferStrategy = getBufferStrategy();
		createBackbuffer();
}
```


```
private void createBackbuffer() {
		if (volatileImage != null) {
			volatileImage.flush();
			volatileImage = null;
		}
		graphicsEnvironment = GraphicsEnvironment.getLocalGraphicsEnvironment();
		graphicsConfiguration = graphicsEnvironment.getDefaultScreenDevice().getDefaultConfiguration();
		volatileImage = graphicsConfiguration.createCompatibleVolatileImage(getWidth(), getHeight());
	}
```


```
@Override
	public void run() {
		while (true) {
			calculateFPS();
			checkKeys();
			drawObjects();
			try {
				Thread.sleep(5);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
```


```
private void drawObjects() {
		checkBackbuffer();

		Graphics g = volatileImage.getGraphics();
		render(g);
		g.dispose();

		Graphics g2 = bufferStrategy.getDrawGraphics();
		g2.drawImage(volatileImage, 0, 0, Configuration.FRAME_WIDTH, Configuration.FRAME_HEIGHT, this);
		g2.dispose();
		bufferStrategy.show();
	}

	private void checkBackbuffer() {
		if (volatileImage == null) {
			createBackbuffer();
		}
		if (volatileImage.validate(graphicsConfiguration) == VolatileImage.IMAGE_INCOMPATIBLE) {
			createBackbuffer();
		}
	}

	public void render(Graphics g) {
		renderer.draw(g);
		renderer.drawFPS(g, fps);
	}
```


----------



## Quaxli (25. Okt 2011)

Ich denke nicht, daß es an den Methoden liegt, die das Rendering beinhalten.
Zeig' doch mal den Code,der die Tiles positioniert/bewegt.


----------



## Raziell (25. Okt 2011)

move() wird von einem Thread alle 10ms aufgerufen und bewegt die einzelnen Tiles.


```
public void move(double horizontalSpeed, double verticalSpeed) {
		for (Tile tile : map.getTiles()) {
			tile.move(horizontalSpeed, verticalSpeed, getDisplay());
		}
	}
```

Die move Methode vom Tile bewegt das Tile in x oder y Richtung.


```
public void move(double horizontalSpeed, double verticalSpeed, Display display) {
		if (horizontalSpeed != 0) {
			x += horizontalSpeed;
		}

		if (verticalSpeed != 0) {
			y += verticalSpeed;
		}
	}
```

EDIT:

Hm ich glaube mein Problem ist einfach das ich keine Begrenzung habe also ich scrolle die Tiles an den Enden der Map ja quasi über ihr dasein hinaus. 
Ich müsste mir ja eigtl eine künstliche Grenze mit Bergen/Wäldern/Wasser schaffen bis zu der ich maximal scrolle. 
Weil das Problem tritt ja nur auf wenn ich die Map mit dem Display "verlasse".


----------



## Evil-Devil (26. Okt 2011)

Wieso bewegst du die Tiles und nicht die Kamera?
Spätestens wenn du auch noch andere Elemente auf deinen Tiles darstellst wirst du damit imho gegen die Wand fahren.

Frag besser ab ob die Tiles innerhalb des Sichtfeldes sind und entsprechend gerendert werden müssen. Dann sollte es mit dem scrollen auch ohne Probleme klappen.


----------



## Raziell (31. Okt 2011)

Hi,
sorry für die späte Antwort.

Also prinzipiell stellt die Kamera ja den sichtbaren Bereich auf der Tilemap dar.
Wenn sich nun die Kamera verschiebt, dann verschieben sich ja die Tiles oder sehe ich das falsch?
Ich wüsste jedenfalls nicht wie ich ein verschieben der Kamera anders realisieren könnte als die Tiles zu verschieben.



> Spätestens wenn du auch noch andere Elemente auf deinen Tiles darstellst wirst du damit imho gegen die Wand fahren.


Die Objekte auf der Map werden ja genauso wie die Tiles verschoben, deshalb sehe ich da kein Problem.

Ist mein Ansatz denn falsch?


----------



## Evil-Devil (31. Okt 2011)

Wenn die Kamera sich verschiebt, bleiben die Tiles an ihrer Position. Einzig der Einstiegspunkt in deiner Tilemap ändert sich.

Also wenn deine Kamera am Anfang 0/0 bis 9/9 darstellt und du sie um 5+ verschiebst, würde der Ausschnitt bei 5/5 bis 14/14 liegen. Die Tiles sollten also völlig unberührt davon bleiben.


----------



## Raziell (31. Okt 2011)

Ok ich verstehe, allerdings weiß ich nicht, wie ich das mit klassischem Java2D lösen soll.

Ich habe ja lediglich ein Canvas, auf dem ich zeichne, dass ist ja quasi meine "Kamera".
Bewegen kann ich das Canvas ja nicht aber ich kann das bewegen, was gezeichnet wird und das sind doch die Tiles. 

Also irgendwie stehe ich da gerade aufm Schlauch


----------



## Evil-Devil (31. Okt 2011)

Naja, ich würde eben nicht die Tiles bewegen, sondern einfach nur die Tiles zeichnen die nach dem Bewegen in der Kamera (deinem Canvas) liegen. Ein Tile hat bei dir doch sicherlich eine Größe oder und um wie viele Pixel man sich seit der letzten Bewegung bewegt hast weißt du vermutlich auch. Somit kannst du ermitteln an welcher Stelle deiner Tilemap du anfangen musst mit dem zeichnen. Der Rest wird einfach nicht gezeichnet, er bleibt aber an seiner Position bestehen.

Du brauchst also eine Art Welt-Koordinate für deine Tilemap, die sicherlich größer ist als das Canvas und die lokalen Koordinaten.


----------



## Mofi (1. Nov 2011)

Raziell hat gesagt.:


> Ok ich verstehe, allerdings weiß ich nicht, wie ich das mit klassischem Java2D lösen soll.
> 
> Ich habe ja lediglich ein Canvas, auf dem ich zeichne, dass ist ja quasi meine "Kamera".
> Bewegen kann ich das Canvas ja nicht aber ich kann das bewegen, was gezeichnet wird und das sind doch die Tiles.
> ...



Ich bin mir nicht sicher was du genau mit Canvas meinst, aber ich geh mal davon aus, dass du mit einem Graphicspbjekt zeichnest? Wenn ja gibt es eine Möglichkeit die "Kamera" (ich nenn es normalerweise Viewport...Liegt aber daran, dass ich sowas meist in Englisch lese) zu bewegen.
Bei Graphics gibt es eine Methode die heißt translate. Graphics (Java Platform SE 6)
Der Methode übergibst du die neuen Koordinate von dem was nun sichtbar sein soll. Dadurch kannst du die Kamera quasi hin und herschieben. Allerdings muss man das ein wenig üben, da es ein wenig tückisch sein kann  

Bei mir hat das eine Weile gedauert bis ich den richtigen Dreh raus hatte ^^

Aber ich zum Beispiel muss meine Tiles nicht bewegen sondern nur die Kamera/Viewport damit sich mein Männchen auf der Karte weitläufig bewegen kann


----------



## Raziell (1. Nov 2011)

> Ich bin mir nicht sicher was du genau mit Canvas meinst, aber ich geh mal davon aus, dass du mit einem Graphicspbjekt zeichnest?



Also ich extende Canvas und zeichne mittels aktivem Rendering.

Meine draw Methode sieht in etwa so aus:


```
private void drawObjects() {
        checkBackbuffer();
 
        Graphics g = volatileImage.getGraphics();
        render(g);
        g.fillRect(0,0,Configuration.FRAME_WIDTH, Configuration.FRAME_HEIGHT);
        g.dispose();
 
        Graphics g2 = bufferStrategy.getDrawGraphics();
        g2.drawImage(volatileImage, 0, 0, Configuration.FRAME_WIDTH, Configuration.FRAME_HEIGHT, this);
        g2.dispose();
        bufferStrategy.show();
    }
```


```
public void render(Graphics g) {
        renderer.draw(g);
    }
```

Das heisst ich muss theoretisch anstatt die Tiles zu bewegen die translate() Methode vor dem zeichnen 
mit den jeweiligen x/y Koordinaten aufrufen, wenn ich das richtig verstehe?
Und wie ist das mit der Bewegung soll dann beispielsweise die Spielerfigur wirklich bewegt werden?


----------



## Mofi (1. Nov 2011)

Mal davon abgesehen, dass ich nicht verstanden habe, was du da machst (Also warum du das eine Graphicsobjekt vernichtest um danach eine neues zu erstellen). Darum gehts ja ansich auch nicht 

Bei mir bewegt sich meine Figur auf der Karte und die Kamera/der Viewport bewegt sich in einem bestimmten Verhältnis dazu.. Ich hab es so gemacht, dass die Figur zum Beispiel immer mittig ist solange sie nicht zu nah am Rand ist (Dann bewegt sich die Kamera nicht und die Figur bewegt sich quasi alleine an den Rand)

Ansonsten ja die translate Methode wird vor dem Zeichnen aufgerufen. Ich berechne halt noch vorher inwiefern sich die Koordinaten ändern müssen. Man muss nur halt bei Sachen aufpassen die sich mit bewegen sollen (wie irgendwelche Anzeigen)


----------



## Raziell (1. Nov 2011)

> Mal davon abgesehen, dass ich nicht verstanden habe, was du da machst (Also warum du das eine Graphicsobjekt vernichtest um danach eine neues zu erstellen). Darum gehts ja ansich auch nicht



Dazu habe ich mir verschiedene Beispiele aus dem Internet bzw. hier aus dem Forum angeschaut.
Ob das jetzt die optimale Lösung ist, weiss ich nocht nicht allerdings funktioniert es bisher ganz gut.
Ich werde mir das aber auch noch einmal genauer anschauen und ein bisschen rumprobieren.



> Bei mir bewegt sich meine Figur auf der Karte und die Kamera/der Viewport bewegt sich in einem bestimmten Verhältnis dazu.. Ich hab es so gemacht, dass die Figur zum Beispiel immer mittig ist solange sie nicht zu nah am Rand ist (Dann bewegt sich die Kamera nicht und die Figur bewegt sich quasi alleine an den Rand)
> 
> Ansonsten ja die translate Methode wird vor dem Zeichnen aufgerufen. Ich berechne halt noch vorher inwiefern sich die Koordinaten ändern müssen. Man muss nur halt bei Sachen aufpassen die sich mit bewegen sollen (wie irgendwelche Anzeigen)



Ich verstehe. 

Am besten ich probiere das heute Abend wenn ich Zeit habe einfach mal aus.
Ist ja prinzipiell nicht viel Umbauaufwand.

Bis hierhin vielen Dank, ich melde mich dann wenn ich soweit bin und nicht weiterkomme


----------



## Raziell (2. Nov 2011)

Hi,
habe das ganze jetzt mit bewegen des Viewports realisiert.
Klappt auch soweit alles ganz gut.

Eine andere Frage ist:
Kennt jemand das Problem, dass es beim diagonalen leicht flackert/ruckelt/lagt (schwer zu beschreiben)?
Beim vertikalen/horizontalen Bewegen ist alles in Ordnung.

Danke


----------



## Mofi (2. Nov 2011)

Raziell hat gesagt.:


> Hi,
> habe das ganze jetzt mit bewegen des Viewports realisiert.
> Klappt auch soweit alles ganz gut.
> 
> ...



Nein bei mir läuft alles flüssig. (Also bei meinem Code ^^)
Vielleicht hast du zu viele Bilder die du gleichzeitig malst? Ohne Code wird man das nicht sagen können 
Bei mir hatte es beispielsweise leicht geflackert (allerdings in alle Richtungen und nur auf langsamen Rechnern), weil ich die eine Methode nicht 100% richtig geschriebene hatte bzw es jetzt etwas umgeschrieben habe und es nun besser läuft.

Manchmal sind es einfach Kleinigkeiten die auch eher auf älteren Rechner auffallen, wollt ich damit sagen 

Bei sowas spielt auch immer eine Rolle, wie viele Bilder gemalt werden. Hier ist eben wichtig, dass wirklich nur das gemalt wird, was man auch sieht und nicht mehr.


----------



## Raziell (13. Nov 2011)

Also bei mir läufts von Computer zu Computer sehr unterschiedlich.
Manchmal ruckelt es garnicht manchmal heftig.
Ich poste einfach mal ein bisschen Code aus unterschiedlichen Klassen zusammengefasst:


```
private void init() {
		createBufferStrategy(2);
		bufferStrategy = getBufferStrategy();

		last = System.nanoTime();
		Thread t = new Thread(this);
		t.start();
	}

	@Override
	public void run() {
		long lastLoopTime = System.currentTimeMillis();

		while (isVisible()) {
			try {
				delta = System.currentTimeMillis() - lastLoopTime;
				lastLoopTime = System.currentTimeMillis();

				moveObjects();
				drawObjects();
				checkKeys();
				Thread.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	private void drawObjects() {
		Graphics2D g = (Graphics2D) bufferStrategy.getDrawGraphics();
		g.clearRect(0, 0, getWidth(), getHeight());
		render(g);
		g.dispose();
		bufferStrategy.show();
}

	public void moveObjects() {
		renderer.getViewport().move(delta);
		gameObjetcs.getPlayer().move(delta);
	}

	public void draw(Graphics g) {
		g.translate((int) viewport.getX(), (int) viewport.getY());
		map.draw(g, viewport);
		player.draw(g);
		viewport.draw(g);
		g.setColor(Color.CYAN);
		g.drawString("FPS: " + Long.toString(fps), (int) viewport.getWorldX() + 20, (int) viewport.getWorldY() + 20);
	}

	public void move(long delta) {
		// update the location of the entity based on move speeds
		x += (delta * dx) / 1000;
		y += (delta * dy) / 1000;
	}

	public void setHorizontalMovement(double dx) {
		this.dx = dx;
	}

	public void setVerticalMovement(double dy) {
		this.dy = dy;
	}
```

Meine Tiles sind 100x100px große JPGs und es werden nur die gezeichnet, die auch im sichtbaren Bereich liegen.

Ich hoffe jmd. hat ne Idee.


----------



## fleckdalm (16. Nov 2011)

Vielleicht hilft es ja einfach mal den Thread.sleep() wert zu verkleinern?


----------



## Fu3L (17. Nov 2011)

Daran wird es bei Thread.sleep(10) wohl eher nicht liegen... Das wären, wenn das Berechnen und Zeichnen vernachlässigbar kurz dauert, 100 FP/s. 60 FP/s kann ein Mensch schon eigentlich nicht mehr als Bilderfolge wahrnehmen. Bei einer Schlafdauer von 5 ms wird Java wohl auch nicht mehr so schnell neu zeichnen.
Für genauere Aussagen bräuchte man meine ich auch Einsicht in die Map-Klasse.


----------



## Raziell (20. Nov 2011)

Hier die Map Klasse:


```
public class Map {

	private List<Tile> tiles = new LinkedList<Tile>();
	private int width;
	private int height;
	private int horizontalTileCount;
	private int verticalTileCount;

	public void draw(Graphics g, Viewport viewport) {
		for (Tile tile : getTiles()) {
			if (tile.intersects(viewport.getWorldX(), viewport.getWorldY(), Configuration.FRAME_WIDTH, Configuration.FRAME_HEIGHT)) {
				tile.draw(g);
			}
		}
	}

	public void addTile(Tile tile) {
		getTiles().add(tile);
	}

	public List<Tile> getTiles() {
		return tiles;
	}

	public void setTiles(List<Tile> tiles) {
		this.tiles = tiles;
	}

	private int getHorizontalTileCount() {
		return horizontalTileCount;
	}

	public void setHorizontalTileCount(int horizontalTileCount) {
		this.horizontalTileCount = horizontalTileCount;
	}

	private int getVerticalTileCount() {
		return verticalTileCount;
	}

	public void setVerticalTileCount(int verticalTileCount) {
		this.verticalTileCount = verticalTileCount;
	}

	public int getHeight() {
		if (height <= 0) {
			height = getVerticalTileCount() * Configuration.TILE_HEIGHT;
		}
		return height;
	}

	public void setHeight(int height) {
		this.height = height;
	}

	public int getWidth() {
		if (width <= 0) {
			width = getHorizontalTileCount() * Configuration.TILE_WIDTH;
		}
		return width;
	}

	public void setWidth(int width) {
		this.width = width;
	}

}
```

Meine Vermutung ist das die Berechnung des delta bzw. der Bewegungsgeschwindigkeit nicht optimal ist und es daher von System zu System unterschiedlich gut läuft.

Danke


----------



## Fu3L (21. Nov 2011)

Versuch mal nicht mit System.currentTimeMillis(), sondern System.nanoTime(). Dann musste natürlich die Werte durch 1.000.000 teilen, ist aber wesentlich genauer. (Und ich meine nicht, weil du theoretisch auf die Nanosekunde genau messen kannst, sondern weil currentTimeMillis() nicht den besten Ruf hat.)

Ansonsten bitte auch noch die Klasse Tile.


----------



## Raziell (22. Nov 2011)

> Versuch mal nicht mit System.currentTimeMillis(), sondern System.nanoTime(). Dann musste natürlich die Werte durch 1.000.000 teilen, ist aber wesentlich genauer. (Und ich meine nicht, weil du theoretisch auf die Nanosekunde genau messen kannst, sondern weil currentTimeMillis() nicht den besten Ruf hat.)


Könnte man mal probieren.

Hier die Tile Klasse:

```
public class Tile extends Rectangle2D.Double implements Drawable {

	private static final long serialVersionUID = 1L;

	private BufferedImage image;
	private String location;

	public Tile(BufferedImage image, int width, int height, int x, int y, String location) {
		this.image = image;
		this.width = width;
		this.height = height;
		this.location = location;
		this.x = x;
		this.y = y;
	}

	@Override
	public void draw(Graphics g) {
		g.drawImage(image, (int) x, (int) y, (int) width, (int) height, null);
		// g.setColor(Color.GRAY);
		// g.fill3DRect((int) x, (int) y, (int) width, (int) height, true);
	}

	public String getLocation() {
		return location;
	}

}
```


----------



## Raziell (23. Nov 2011)

Also ich habe es jetzt mit Nanosekunden probiert und konnte keinen Unterscheid feststellen.

Ich habe die aktuelle Version(mit Nanosekunden) mal hochgeladen:
Share-Online - dl/PXWBP4VLI5J

Wer möchte kann sie ja mal kurz laufen lassen und mitteilen ob und wie es läuft.

Danke


----------



## Fu3L (26. Nov 2011)

Also ich kann leider nicht erkennen, woran die Ruckler liegen könnten. Bei mir läuft es jedenfalls wunderbar flüssig. 

PS: Schöner Hintergrund


----------



## Raziell (26. Nov 2011)

Erstmal danke das dus getestet hast.

Bei mir unter Win7 läuft auch alles super aber beim letzten Test an an der Arbeit unter Ubuntu konnte ich ein leichtes ruckeln/flackern feststellen.

Wäre super wenn es sich nochmal der ein oder andere anschauen könnte und mir ein kurzes Feedback geben kann wie es läuft.

Danke


----------

