Java/Tutorials/Karteneditor II Kapitel 5

Aus Scientia
Wechseln zu: Navigation, Suche

Kommen wir nun zum wichtigen Teil und zwar zum Anzeigen der Karte. Wir erstellen eine Klasse die von JPanel erbt und Implementieren Runnable um später die Grafik Aktualisieren zu können. Als Klassenvariablen brauchen wir die SpielFenster-Klasse, die KartenKlasse und die SpielerSprite-Klasse. Die Karten und die SpielerSprite-Klassen übernehmen wir um uns schreib Arbeit zu ersparen. Wir legen die größe unseres JPanel mit setPreferredSize auf 640x480 fest und schalten Double Buffered an um Flackern zu beseitigen.

public class SpielAnsicht extends JPanel implements Runnable{
 
	SpielerSprite spieler;
	SpielFenster fenster;
	Karte karte;
 
	public SpielAnsicht(SpielFenster f)
	{
		fenster=f;
		karte=fenster.karte;
		spieler=fenster.spieler;
		setPreferredSize(new Dimension(640,480));
		setDoubleBuffered(true);
	}
 
	public void paintComponent(Graphics g)
	{
 
	}
 
 
	@Override
	public void run() {
 
	}
}

Bevor wir nun weiter machen, fügen wir erstmal unsere SpielAnsicht in unser SpielFenster ein. Zusätzlich werden wir einen Punkt hinzufügen, der die obere linke Ecke repräsentiert. Dieser sagt an welcher Teil der Karte gezeichnet werden soll. Hierfür werden wir die Point-Klasse verwenden und wir setzen den Punkt auf 0,0.

public class SpielFenster extends JFrame{
 
 	Point ansichtsPunkt=new Point(0,0);
	SpielAnsicht ansicht;
	Steuerung steuerung;
	SpielerSprite spieler;
	Karte karte;
 
 
	public SpielFenster()
	{
		steuerung=new Steuerung();
		addKeyListener(steuerung);
 
		spieler=new SpielerSprite("Char.png",this);
		karte=Karte.ladeKarte("1.karte");
		ansicht=new SpielAnsicht(this);
 
		add(ansicht);
		setSize(new Dimension(640,480));
		setTitle("Spiel");
		setVisible(true);
	}
}

Im nächsten Schritt erstellen wir eine Methode zeichneKarte() Die uns unsere Karte zeichnet. Wir verwenden hierbei den Punkt den wir in der SpielFenster Klasse erstellt hatten. Diesen verwenden wir als Startpunkt für den Endpunkt addieren wir den Wert 20 in der Breite und den Wert 15 in der Höhe. Diese Zahlen kommen zustanden in dem wir 640/32 und 480/32 Rechnen. Diese Zahlen sagen aus wieviel Tile in Höhe und Breite passen. Für das darstellen der einzelnen Tile auf der Karte benötigen wir 4 Variablen einmal 2 um das Tile zu erhalten und einmal 2 um dieses Tile auf den Bildschirm dazustellen. Die Variablen rx und ry sorgen dafür, das das erhaltene Tile auch auf den JPanel dargestellt wird.

public class SpielAnsicht extends JPanel implements Runnable{
 
	SpielerSprite spieler;
	SpielFenster fenster;
	Karte karte;
 
	public SpielAnsicht(SpielFenster f)
	{
		fenster=f;
		karte=fenster.karte;
		spieler=fenster.spieler;
		setPreferredSize(new Dimension(640,480));
		setDoubleBuffered(true);
	}
 
	public void paintComponent(Graphics g)
	{
 
	}
 
	public void zeichneKarte(Graphics g)
	{
		int startx=fenster.ansichtsPunkt.x;
		int starty=fenster.ansichtsPunkt.y;
		int endx=startx+20;
		int endy=starty+15;
		for(int x=startx, rx=0;x<endx;x++, rx++)
		{
			for(int y=starty,ry=0;y<endy;y++,ry++)
			{
				g.drawImage(karte.getTileImage(x, y), rx*32, ry*32,this);
			}
		}
	}
 
	@Override
	public void run() {
 
	}
}

Die nächste Methode wird unsere Spieler Figur zeichnen hierfür brauchen wir nur die Methode drawImage(). Die Spieler Position erhalten wir aus der SpielerSprite Klasse. In der Y-Position müssen 12 Pixel abgezogen werden, weil die Charakter-Grafik nicht wie bei den Tile 32x32 ist, sondern 32x48, also 12 Pixel zu hoch. Wenn man das nicht macht, wird der Held nicht genau auf Tile für Tile laufen.

public class SpielAnsicht extends JPanel implements Runnable{
 
	SpielerSprite spieler;
	SpielFenster fenster;
	Karte karte;
 
	public SpielAnsicht(SpielFenster f)
	{
		fenster=f;
		karte=fenster.karte;
		spieler=fenster.spieler;
		setPreferredSize(new Dimension(640,480));
		setDoubleBuffered(true);
	}
 
	public void paintComponent(Graphics g)
	{
 
	}
 
	public void zeichneSpieler(Graphics g)
	{
		g.drawImage(spieler.getImage(), spieler.position.x*32, spieler.position.y*32-16, this);
	}
 
	public void zeichneKarte(Graphics g)
	{
		int startx=fenster.ansichtsPunkt.x;
		int starty=fenster.ansichtsPunkt.y;
		int endx=startx+20;
		int endy=starty+15;
		for(int x=startx, rx=0;x<endx;x++, rx++)
		{
			for(int y=starty,ry=0;y<endy;y++,ry++)
			{
				g.drawImage(karte.getTileImage(x, y), rx*32, ry*32,this);
			}
		}
	}
 
	@Override
	public void run() {
 
	}
}


Im nächsten Schritt werden wir dafür sorgen das die Methoden die wir angelegt haben auch aufgerufen werden, daher schreiben wir diese in die paintComponent()-Methode. Dabei muss die Reihenfolge beachtet werden!

public class SpielAnsicht extends JPanel implements Runnable{
 
	SpielerSprite spieler;
	SpielFenster fenster;
	Karte karte;
 
	public SpielAnsicht(SpielFenster f)
	{
		fenster=f;
		karte=fenster.karte;
		spieler=fenster.spieler;
		setPreferredSize(new Dimension(640,480));
		setDoubleBuffered(true);
	}
 
	public void paintComponent(Graphics g)
	{
		zeichneKarte(g);
		zeichneSpieler(g);
	}
 
	public void zeichneSpieler(Graphics g)
	{
		g.drawImage(spieler.getImage(), spieler.position.x*32, spieler.position.y*32, this);
	}
 
	public void zeichneKarte(Graphics g)
	{
		int startx=fenster.ansichtsPunkt.x;
		int starty=fenster.ansichtsPunkt.y;
		int endx=startx+20;
		int endy=starty+15;
		for(int x=startx, rx=0;x<endx;x++, rx++)
		{
			for(int y=starty,ry=0;y<endy;y++,ry++)
			{
				g.drawImage(karte.getTileImage(x, y), rx*32, ry*32,this);
			}
		}
	}
 
	@Override
	public void run() {
 
	}
}

Kommen wir nun zur run()-Methode, in dieser Methode werden wie eine while-Schleife schreiben, die die repaint() von JPanel nach einer bestimmten Zeit immer wieder aufruft. Wir werden aber dafür sorgen das die Methode immer in gleicher Geschwindigkeit aufgerufen wird, so das wir eine Konstante Geschwindigkeit auf jeden Rechner haben, sei es auf einen Quad Core oder auf einen Pentium M. Hierfür brauchen wir zu erst eine Klassen Variable die unsere Maximale Spielgeschwindigkeit darstellt, daher die Variable MAX_GAME_SPEED sorgt dafür das wir 33 FPS haben. In unserer while schleife werden wir als erstes eine Startzeit festlegen, dafür verwenden wir die Methode System.currentTimeMillis() die uns die aktuelle Zeit in Millisekunden zurück gibt. Anschließend werden wir die Methode repaint() Aufrufen und anschließend werden wir von der Aktuellen Zeit die Startzeit abziehen um festzustellen wie lange das neuzeichnen gedauert hat. Anschließend überprüfen wir ob die Ausführungs Geschwindigkeit kleiner ist als unsere FPS von 33. Falls ja wird die Methode Thread.sleep() Aufgerufen. Damit sorgen wir das die while-Schleife einen moment anhält.

public class SpielAnsicht extends JPanel implements Runnable{
 
	SpielerSprite spieler;
	SpielFenster fenster;
	Karte karte;
	final int MAX_GAME_SPEED=33;
 
	public SpielAnsicht(SpielFenster f)
	{
		fenster=f;
		karte=fenster.karte;
		spieler=fenster.spieler;
		setPreferredSize(new Dimension(640,480));
		setDoubleBuffered(true);
	}
 
	public void paintComponent(Graphics g)
	{
		zeichneKarte(g);
		zeichneSpieler(g);
	}
 
	public void zeichneSpieler(Graphics g)
	{
		g.drawImage(spieler.getImage(), spieler.position.x*32, spieler.position.y*32, this);
	}
 
	public void zeichneKarte(Graphics g)
	{
		int startx=fenster.ansichtsPunkt.x;
		int starty=fenster.ansichtsPunkt.y;
		int endx=startx+20;
		int endy=starty+15;
		for(int x=startx, rx=0;x<endx;x++, rx++)
		{
			for(int y=starty,ry=0;y<endy;y++,ry++)
			{
				g.drawImage(karte.getTileImage(x, y), rx*32, ry*32,this);
			}
		}
	}
 
	@Override
	public void run() {
		while(true)
		{
			float START = System.currentTimeMillis();
			repaint();
			float AUSFUEHR = System.currentTimeMillis()-START;
			if(MAX_GAME_SPEED>AUSFUEHR)
			{
				try {
					Thread.sleep(MAX_GAME_SPEED-(int)AUSFUEHR);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
}