Tile Game Performance

Status
Nicht offen für weitere Antworten.

Templon

Bekanntes Mitglied
Hi,

Also ich programmiere im Moment ein 2 Dimensionales Tile basiertes Game in Java. Den Level Editor hab ich fertig nun hab ich mich an das eigentliche game gemacht. Ich habe ein Funktion geschrieben um den Spieler zu bewegen, welche einfach die x-Koordinaten des Spieler images einfach um 2 erhöt (Einfach mal zum testen, also ich schaue noch nicht ob rechts oder links...), jetzt läuft das ganze aber ziemlich unschön, und ich habe keine Ahnung wieso. Kann mir einer sagen wie ich das schön mache, so das der Spieler flüssig läuft. Hier wäre dann noch der Code dazu:

Code:
	public void keyPressed(KeyEvent e) {
		gameMainFrameData.getGamePlayer().movePlayer();
		repaint();
	}


Code:
	public void movePlayer() {
		x += 2;
	}

Templon
 
Das läuft so schlecht, weil wenn du ne Taste hältst nur alle x millisekunden ein KeyEvent ausgelöst wird.
Du solltest in keyPressed eine flag auf true setzen, und solange die true ist den Player bewegen.
In keyReleased kannst du die flag dann wieder auf false setzen um die Bewgung zu stoppen.
 
Was hat das mit Threads zu tun? Du nimmst ein Flag (z.B. "up") und setzt das in keyPressed auf true und in keyReleased wieder auf false (natürlich nur, wenn der Event auch die richtige Taste für "up" betrifft). In der Schleife, in der deine Spiellogik stattfindet, wertest du das Flag aus und machst die Bewegung entsprechend.
 
:shock: Wieso nicht?

genereller Aufbau einer Spielschleife:
Code:
Gegner.bewegen();  //Hier erfragst du ob irgendeine flag für irgendeine Richtung gesetzt ist und bewegst die Figur um x Pixel.

Spieler.bewegen();  // Laß die Gegner machen was Gegner eben so machen

kollisionsbehandlung(); // Überprüfe die Kollisionen wenn du das nicht schon in den bewegen-Methoden tust.

zeichnen()  // zeichne dein Spielfeld neu.
Das packst du in eine Schleife die du meinetwegen alle 16 ms (~60FPS) durchlaufen läßt.
 
Das hier z.B funktioniert nicht.. Während er im loop ist kann er ja nicht noch die KeyEvents abfangen?

Code:
	public void gameLoop() {
		while(true) {
			while (gameMainFrameData.isMoveRight()) {
				gameMainFrameData.getGamePlayer().movePlayer(Bearing.RIGHT);
				repaint();
			}
			
			while (gameMainFrameData.isMoveLeft()) {
				gameMainFrameData.getGamePlayer().movePlayer(Bearing.LEFT);
				repaint();
			}
		}
	}
 
Die KeyEvents sind sowieso in einem eigenen Thread, dem AWT Event Dispatch Thread. Du kannst das problemlos so machen. Allerdings würde ich die beiden whiles rauswerfen. Die übergeordnete Schleife sollte reichen. repaint() würde ich auch gleich entfernen und auf "active rendering" umsteigen, d.h. du machst das Neuzeichnen selber. repaint() ist nämlich nur ein "Vorschlag" zum Neuzeichnen an den AWT Event Thread. Es zeichnet nicht selber neu.

Edit: also etwa so:

Code:
  public void gameLoop() {
      while(!exit) {
         if (gameMainFrameData.isMoveRight()) {
            gameMainFrameData.getGamePlayer().movePlayer(Bearing.RIGHT);
         }
         
         if (gameMainFrameData.isMoveLeft()) {
            gameMainFrameData.getGamePlayer().movePlayer(Bearing.LEFT);
         }
         myRepaint();
         Thread.sleep(20); // Oder sonstiger Timingcode...
      }
   }
 
Ah das habe ich nicht gewusst, mit dem active rendering meinst du das?

Code:
gamePanel.paintComponent(getGraphics());

Wenn ja, so funktionierts immer noch nicht 🙁 wann soll ich diese schleife starten? Und mit dem code von oben braucht er 500 millisekunden um neu zu zeichnen. Und die KeyEvents werden nicht abgefangen wenn ich das in einer alles in einer schleife hab.

Das ganze sieht jetzt so aus:

KeyPressed:
Code:
	public void keyPressed(KeyEvent e) {
		System.out.println("keyPressed");
		if (gameMainFrameData.getGamePlayer() != null) {
			switch (e.getKeyChar()) {
			case 'd':
				gameMainFrameData.getGamePlayer().setMoveRight(true);
				break;
			case 'a':
				gameMainFrameData.getGamePlayer().setMoveLeft(true);
				break;
			}
		}
	}

Das "keyPressed" wird nicht in der Konsole ausgegeben.


Hauptschleife:
Code:
	public void gameLoop() {
		while(true) {
			if (gameMainFrameData.isMoveRight()) {
				gameMainFrameData.getGamePlayer().movePlayer(Bearing.RIGHT);
			} else  if (gameMainFrameData.isMoveLeft()) {
				gameMainFrameData.getGamePlayer().movePlayer(Bearing.LEFT);
			}
			gamePanel.paintComponent(getGraphics());
		}
	}

Die Move Funktion (Noch keine collisions Abfrage):
Code:
	public void movePlayer(Bearing bearing) {
		switch (bearing) {
		case RIGHT: 
			x += 2;
			break;
		case LEFT:
			x -= 2;
			break;
		}
	}

Und die schliefe wird im cunstructor gestartet.
 
Du fragst jetzt auch zweimal auf Rechts ab... :wink: Aber davon abgesehen: Was genau geht denn nicht? Was macht denn der KeyListener und wo hängst du den dran? Probiere es auch mal mit dem Thread.sleep(..). Es könnte noch sein, dass der Eventthread sonst verhungert", weil dein Schleifenthread zuviel CPU-Leistung erfordert. Obwohl das bei neueren VMs eigentlich kein Problem mehr sein sollte...aber einen Versuch ist es wert.

Edit: Ach sorry, den Listener hast du ja zumindest zum Teil mitgepostet...hatte ich übersehen.
 
Oh ja zwei mal rechts abgefragt 😛 aber das ist bestimmt nich das problem ^^ bei welchem Thread soll ich das denn machen? Hab keinen. Und wo?
 
In der Schleife. So wie ich das in meinem Codeschnippsel auch gemacht hatte. Natürlich hast du Threads: Den Eventthread und den, in dem deine Schleife läuft. Nur weil du die nicht explizit erzeugt hast, heißt das ja nicht, dass sie nicht da sind.
 
Wenn ich in meiner Schleife Thread.sleep(250) aufrufe ändert das nichts.. geht nur länger bis es neu gezeichnet wird.
 
Wie soll ich es dann neu zeichnen? Repaint() hat Egon gesagt sei nicht gut weil es nicht gleich neu zeichnet...
 
Ob mit oder ohne repaint() hängt davon ab, was genau du wie machen willst. Wenn du das Spiel aus Swing-Komponenten zusammenbauen willst, dann kann und sollte es bleiben. Dann solltest du aber auch die anderen Eigenarten von Swing berücksichtigen (also z.B. keine sichtbaren Komponenten (von Ausnahmen abgesehen) außerhalb des Event Threads ändern usw). Wenn du selber alles auf eine Canvas oder in einen Frame malen willst, dann weg damit. Man kann nicht pauschal sagen, das eine oder andere ist pöse. Ich persönlich halte gar nichts davon, ein (Action-)Spiel auf Basis von Swing zu bauen, aber letztendlich ist es ein Stück weit Geschmackssache und hat mit dem Problem, das du hast, wohl auch nichts zu tun.
Wenn dein KeyListener nicht dran kommt, dann ist irgendwas anderes im Argen. Guck mal hier hinein:
www.java-forum.org/de/viewtopic.php?t=41482

Da kommt so etwa auf der Hälfte ein Post von mir, der Code enthält, der etwa so ist, wie das was ich hier meine. Vielleicht hilft's.
 
Nabend,

Ich wurde ehrlich gesagt nicht richtig schlau aus den anderen Beiträgen, bin in Java 2D noch ein ziemlicher Noob 😛 Bin allgemein noch nicht so erfahren in Java.

Ich schreib mal den Code vllt kannst jemand etwas dazu sagen, was verbesserungswürdig wäre...

MainFrame vom Game:

Code:
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;

import game.model.mainFrame.GameMainFrameData;


import javax.swing.JFrame;

import model.properties.Player.Bearing;



/**
 * 
 * @author Templar
 *
 */

public class GameMainFrame extends JFrame implements KeyListener{
	
	private GameFieldPanel gamePanel;
	private GameMainFrameData gameMainFrameData;

	/**
	 * 
	 *
	 */
	
	public GameMainFrame(GameMainFrameData gameMainFrameData) {
		super("Mario");
		setBounds(200, 200, 800, 600);
		setDefaultCloseOperation(DISPOSE_ON_CLOSE);
		
		this.gameMainFrameData = gameMainFrameData;
		gamePanel = new GameFieldPanel(gameMainFrameData);
		
		gameMainFrameData.readFile();
		gameMainFrameData.initializePlayer();
		add(gamePanel);
		addKeyListener(this);
		setVisible(true);
		
		gameLoop();
	}
	
	public void keyPressed(KeyEvent e) {
		if (gameMainFrameData.getGamePlayer() != null) {
			switch (e.getKeyChar()) {
			case 'd':
				gameMainFrameData.setMoveRight(true);
				break;
			case 'a':
				gameMainFrameData.setMoveLeft(true);
				break;
			}
		}
	}

	public void keyReleased(KeyEvent e) {
		gameMainFrameData.getGamePlayer().setMoveRight(false);
	}

	public void keyTyped(KeyEvent e) {
		
	}
	
	/**
	 * 
	 *
	 */
	
	public void gameLoop() {
		Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
		try {
			while(true) {
				if (gameMainFrameData.isMoveRight()) {
					gameMainFrameData.getGamePlayer().movePlayer(Bearing.RIGHT);
				} else  if (gameMainFrameData.isMoveLeft()) {
					gameMainFrameData.getGamePlayer().movePlayer(Bearing.LEFT);
				}
				repaint();
			}
		} catch (Exception e) {
			System.out.println(e);
		}
	}
	
	public GameMainFrameData getGameMainFrameData() {
		return gameMainFrameData;
	}

	public void setGameMainFrameData(GameMainFrameData gameMainFrameData) {
		this.gameMainFrameData = gameMainFrameData;
	}

	public GameFieldPanel getGamePanel() {
		return gamePanel;
	}

	public void setGamePanel(GameFieldPanel gamePanel) {
		this.gamePanel = gamePanel;
	}
}

Hier die Daten Klasse:

Code:
import levelEditor.model.editorPanel.EditorData;
import levelEditor.model.editorPanel.Field;
import levelEditor.view.mainFrame.EditorMainFrame;
import model.properties.Player;
import model.utils.MapFileReader;


public class GameMainFrameData {
	
	private Field gameField;
	
	private Player gamePlayer;
	
	private boolean moveRight;
	
	private boolean moveLeft;
	

	/**
	 * 
	 *
	 */
	
	public void initializePlayer() {
		for (int x = 0; x < EditorData.CELL_NUMBERS; ++x) {
			for (int y = 0; y < EditorData.CELL_NUMBERS; ++y) {
				if (gameField.getField()[x][y].getPlayer() != null) {
					gamePlayer = gameField.getField()[x][y].getPlayer();
				}
			}	
		}
	}
	
	/**
	 * 
	 *
	 */
	
	public void readFile() {
		if (EditorMainFrame.actualFilePath != null) {
			MapFileReader mpf = new MapFileReader(EditorMainFrame.actualFilePath);
			setGameField((Field)mpf.readFile());
		}
	}
	
	
	public Field getGameField() {
		return gameField;
	}

	public void setGameField(Field gameField) {
		this.gameField = gameField;
	}
	
	public Player getGamePlayer() {
		return gamePlayer;
	}
	
	public void setGamePlayer(Player gamePlayer) {
		this.gamePlayer = gamePlayer;
	}
	
	public boolean isMoveRight() {
		return moveRight;
	}

	public void setMoveRight(boolean moveRight) {
		this.moveRight = moveRight;
	}

	public boolean isMoveLeft() {
		return moveLeft;
	}

	public void setMoveLeft(boolean moveLeft) {
		this.moveLeft = moveLeft;
	}
}


Hier zeichne ich:

Code:
import game.model.mainFrame.GameMainFrameData;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

import levelEditor.model.editorPanel.EditorData;

public class GameFieldPanel extends JPanel {
	
	private GameMainFrameData gameMainFrameData;
	
	GameFieldPanel(GameMainFrameData gameMainFrameData) {
		this.gameMainFrameData = gameMainFrameData;
	}
	
	public void paintComponent(Graphics graphics) {
		long start = System.currentTimeMillis();
		super.paintComponent(graphics);
		setBackground(new Color(0, 255, 255));
		Graphics2D graphics2D = (Graphics2D) graphics;
		paintItems(graphics2D);
		long end = System.currentTimeMillis();
		System.out.println("Time to repaint: " + (end - start) + " miliseconds.");

	}
	
	
	public void paintItems(Graphics2D graphics) {
		for (int x = 0; x < EditorData.CELL_NUMBERS; ++x) {
			for (int y = 0; y < EditorData.CELL_NUMBERS; ++y) {
				if (gameMainFrameData.getGameField() != null & gameMainFrameData.getGameField().getField()[x][y].getBlock() != null) {
					gameMainFrameData.getGameField().getField()[x][y].getBlock().getImage().paintIcon(this,graphics,gameMainFrameData.getGameField().getField()[x][y]
					                                                                                .getBlock().getX() * 32, gameMainFrameData.getGameField().getField()[x][y].getBlock().getY() * 32);                                                     
				}
				if (gameMainFrameData.getGamePlayer() != null) {
					gameMainFrameData.getGamePlayer().getImage().paintIcon(this, graphics, gameMainFrameData.getGamePlayer().getX(), gameMainFrameData.getGamePlayer().getY());
				}
			}
		}
	}
}

Templon
 
Eins fällt sofort ins Auge:
public void gameLoop() {
Code:
      Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
      try {
         while(true) {
            if (gameMainFrameData.isMoveRight()) {
               gameMainFrameData.getGamePlayer().movePlayer(Bearing.RIGHT);
            } else  if (gameMainFrameData.isMoveLeft()) {
               gameMainFrameData.getGamePlayer().movePlayer(Bearing.LEFT);
            }
            repaint();
         }
      } catch (Exception e) {
         System.out.println(e);
      }
   }
1. Die Schleife die Bewegung steuert muss ein eigener Thread sein.
2. Diese Schleife muss ein sleep enthalten (sonst hast du konstant 100%CPU Last).
 
Danke, jetzt funktionierts wenigstens wieder.. 😛 Aber es läuft immer noch überhaupt nicht flüssig...
 
Neuen Code zeigen :wink:
Ich würde dir übrigens empfehlen das du deutlich mehr Logik in die Objekte verlagerst.
Ein Objekt das sich auf deinem Spielfeld bewegt sollte in der Lage sein dies selbstständig zu tun (der Steuerungsthread dient dabei als Taktgeber).
Weiterhin sollten sich die Objekte auch selbst zeichnen können, d.h. du musst in Objekte die dargestellt werden sollen eine paint oder draw Methode implementieren die von paintComponent aus aufgerufen wird.
 
Ja das werde ich mal noch machen müssen, was für Klassen würdest du dann gerne noch sehen? Hier ist noch die Player Klasse:

Code:
import java.awt.Rectangle;
import java.io.Serializable;

import javax.swing.ImageIcon;

public class Player implements Serializable{
	
	// members
	private String name;
	private ImageIcon image;
	private int x;
	private int y;
	private Rectangle collideRectangle;
	
	public enum Bearing {LEFT, RIGHT, UP, DOWN};
	
	public Player(String name,ImageIcon image,int x, int y) {
		this.name = name;
		this.image = image;
		this.x = x;
		this.y = y;
		
	}
	
	/**
	 * 
	 */
	
	public String toString() {
		return name;
	}
	
	/**
	 * 
	 * @param bearing
	 */
	
	public void movePlayer(Bearing bearing) {
		switch (bearing) {
		case RIGHT: 
			x += 1;
			break;
		case LEFT:
			x -= 1;
			break;
		}
	}
	
	//getter and setters
	public ImageIcon getImage() {
		return image;
	}

	public void setImage(ImageIcon image) {
		this.image = image;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getX() {
		return x;
	}

	public void setX(int x) {
		this.x = x;
	}

	public int getY() {
		return y;
	}

	public void setY(int y) {
		this.y = y;
	}
	
	public Rectangle getCollideRectangle() {
		return collideRectangle;
	}

	public void setCollideRectangle(Rectangle collideRectangle) {
		this.collideRectangle = collideRectangle;
	}
}

Templon
 
Ah oke die sieht im Moment noch so aus 😛
Code:
	public void run() {
		try {
			while(true) {
				Thread.sleep(20);
				if (gameMainFrameData.isMoveRight()) {
					gameMainFrameData.getGamePlayer().movePlayer(Bearing.RIGHT);
				} else  if (gameMainFrameData.isMoveLeft()) {
					gameMainFrameData.getGamePlayer().movePlayer(Bearing.LEFT);
				}
				repaint();
			}
		} catch (Exception e) {
			System.out.println(e);
		}
	}

Templon
 
Weil ein sleep sein muss wenn man keine CPU Volllast produzieren will und er den EDT nicht schlafen legen kann.
Die einzige Alternative wäre ein Timer.
 
Wildcard hat gesagt.:
Weil ein sleep sein muss wenn man keine CPU Volllast produzieren will und er den EDT nicht schlafen legen kann.
Die einzige Alternative wäre ein Timer.
Aber die Schleife wird im Konstruktor angestartet. Der wird ja bestimmt nicht im EDT aufgerufen. Wenn er das als letztes in seiner main- Methode macht, dann hat er sowieso zwei Threads: Den EDT und den, der die Applikation gestartet hat. Wenn er einen eigenen baut, dann stirbt der Startthread nach dem Ende von main() und an seine Stelle tritt der selbsterzeugte. Ich sehe da den Vorteil bzw. die Notwendigkeit nicht!?
 
Naja, ist doch Jacke wie Hose ob man main sterben lässt und einen neuen Thread startet oder den main Thread direkt verwendet.
Der wird ja bestimmt nicht im EDT aufgerufen.
Das kann man so nicht sagen, sehr viele Lehrbücher lehren das starten eines Frames in der main über SwingUtilities#invokeLater in den EDT zu verlagern.
Mit einem neuen Thread ist's eben nicht mehr von der Position des Aufrufs abhängig und damit IMO schlüssiger was genau passiert (und kostet ja nichts :bae: ).
 
Ok, auf Jacke wie Hose kann ich mich einlassen...ich war nur vom Wörtchen "muss" etwas irritiert. Das Problem an "muss" ist meiner Ansicht nach, dass jemand, der das nicht überblickt, dann wirklich denkt, es müsse immer so sein ohne zu hinterfragen ob und wieso. Und das führt dann zu diesen Thread-Happy-Games, die für alles und jedes neue Threads bauen, damit besch...den und nicht-deterministisch laufen und den Ruf von Java als "spieletauglich" endgültig in den Keller ziehen.
 
Hat jetzt noch jemand eine Idee was ich ändern muss um flussiges laufen zu ermöglichen, das mit dem Thread hab ich auch gemacht und soweit verstande.

Templon
 
EgonOlsen hat gesagt.:
Ok, auf Jacke wie Hose kann ich mich einlassen...ich war nur vom Wörtchen "muss" etwas irritiert. Das Problem an "muss" ist meiner Ansicht nach, dass jemand, der das nicht überblickt, dann wirklich denkt, es müsse immer so sein ohne zu hinterfragen ob und wieso. Und das führt dann zu diesen Thread-Happy-Games, die für alles und jedes neue Threads bauen, damit besch...den und nicht-deterministisch laufen und den Ruf von Java als "spieletauglich" endgültig in den Keller ziehen.
ok, agreed.
Wollte damit auch nur ausdrücken das die Schleife nicht im EDT liegen darf.
 
Status
Nicht offen für weitere Antworten.

Zurück
Oben