Dazu werden wir drei neue Java Klassen in der Entwicklungsumgebung NetBeans anlegen. Zwei Klassen davon werden die Basis bilden, auf der alle Spielobjekte unseres Java Spiels aufbauen.
Jedes Spielobjekt besitzt eine exakte Position auf unserer Spielfläche. Die Objektposition geben wir mit Hilfe von Koordinaten (X, Y) an.
Damit dies möglich wird, erstellen wir die neue Java Klasse Coordinate, die einen Punkt im zweidimensionalen Raum repräsentiert.
Jedes Spielobjekt in unserem Java Spiel soll die gleichen grundlegenden Eigenschaften besitzen. Daher definieren wir eine weitere Java Klasse mit dem Namen GameObject. Diese Klasse wird abstrakt sein, wodurch keine konkreten Objekte dieses Typs instanziiert werden können.
In dieser Lektion lernt ihr:
- Erstellen der Coordinate-Klasse
- Erstellen der GameObject-Klasse
- Erstellen der Missile-Klasse
- Das erste Spielobjekt erzeugen
Lektionen des Java Tutorials:
- Java Spiel Programmieren Tutorial
- Teil 1 – Java Projekt erstellen
- Teil 2 – Spielfenster erstellen
- Teil 3 – Spielfläche erstellen
- Teil 4 – Spielobjekte erstellen
- Teil 5 – Panzer und Spielsteuerung
- Teil 6 – Gegnerische Panzer erstellen
- Teil 7 – Die Spiellogik implementieren
- Teil 8 – Einstellungsdialog erstellen
Andere hilfreiche Beiträge:
Das Erzeugen von konkreten Spielobjekten wird schließlich durch die dritte Klasse realisiert. Diese dritte neue Java Klasse mit dem Namen Missile repräsentiert die Geschosse in unserem Java Spiel. Sie erbt die Eigenschaften und Fähigkeiten der Klasse GameObject und fügt neue hinzu.
Da die Klasse Missile nicht abstrakt ist, wird es möglich sein, Objekte ihres Datentyps zu erzeugen und über die Spielfläche unseres Java Spiels zu bewegen.
Nun wünschen wir euch viel Spaß beim vierten Teil unseres Java Spiel Programmieren Tutorials. Los geht’s!
1. Erstellen der Coordinate-Klasse zur Positionierung der Spielobjekte auf der Spielfläche
Zu Beginn legen wir die Hilfsklasse mit dem Namen Coordinate mit der NetBeans IDE an. Wir werden sie zur Positionierung der Spielobjekte auf der Spielfläche unseres Java Spiels verwenden.
Um die Coordinate-Klasse zu erstellen, werden wir die folgenden beiden Arbeitsschritte ausführen:
Beginnen wir nun mit dem ersten Arbeitsschritt, dem Anlegen der Coordinate.java
Klassendatei.
1.1 Anlegen der Klassendatei Coordinate.java in NetBeans
Die Klasse Coordinate legen wir wie folgt an:
- Wir klicken mit der rechten Maustaste auf den Haupt-Packagenamen in der Projektleiste.
- Danach auf den
New
-Eintrag im Kontextmenü klicken. - Anschließend auf den Eintrag
Java Class...
klicken.
Daraufhin öffnet sich der New Java Class-Dialog von NetBeans. In diesem Dialog geben wir als Namen der zu erstellenden Klasse Coordinate
ein.
Alle anderen Einstellungen lassen wir unverändert und bestätigen den Dialog mit einem Klick auf Finish
.
Jetzt generiert NetBeans eine neue Java-Datei mit dem Namen Coordinate.java
und fügt sie in das Hauptpaket unseres Java Projekts ein. Dies sollte bei euch wie folgt aussehen:
Die neue Klasse Coordinate ist momentan noch leer. Das werden wir nun ändern und sie mit Quellcode füllen.
1.2 Definieren der Coordinate-Klasse
Die Klassendatei Coordinate.java
haben wir nun mit Hilfe von NetBeans in unserem Java Projekt angelegt. Als Nächstes werden wir die Coordinate-Klasse ausprogrammieren, also den Klassenbereich mit Variablen und Methoden füllen.
Dazu fügen wir den folgenden Quellcode in die Coordinate-Klasse ein und ersetzen damit den generierten Code:
Coordinate.java
package de.programmierenlernenhq.panzerhq; public class Coordinate { private double x; private double y; public Coordinate(double x, double y) { this.x = x; this.y = y; } public double getX() { return x; } public void setX(double x) { this.x = x; } public double getY() { return y; } public void setY(double y) { this.y = y; } }
Die neue Klasse ist wirklich sehr klein. Mit Hilfe der zwei Membervariablen x
und y
kann ein Punkt im zweidimensionalen Raum definiert werden. Über die Get– und Set-Methoden wird der Wert der jeweiligen Variable abgefragt.
In NetBeans sollte die Coordinate-Klasse nun wie folgt aussehen:
Soviel zu der neu erstellten Klasse Coordinate. Als Nächstes werden wir die abstrakte Klasse GameObject erstellen, die allen konkreten Spielobjekten in unserem Java Spiel als grundlegender Bauplan dienen wird.
2. Erstellen der GameObject-Klasse, die Mutter aller Spielobjekte
Als nächste Klasse legen wir die GameObject-Klasse mit der NetBeans IDE an. Sie wird die Mutter aller spezialisierten Spielobjektklassen sein. Dadurch stellen wir sicher, dass alle Spielobjekte über identische Basiseigenschaften und -fähigkeiten verfügen.
Um die GameObject-Klasse zu erstellen, werden wir wieder die folgenden beiden Arbeitsschritte ausführen:
- Anlegen der Klassendatei in NetBeans – Der erste Schritt ist schnell gemacht. Wir erstellen mit Hilfe der Entwicklungsumgebung NetBeans IDE die Klasse GameObject.
- Definieren der GameObject-Klasse – Im zweiten Schritt programmieren wir die neu erstellte Klasse aus. Die GameObject-Klasse ist der allgemeine Bauplan, nach dem alle Spielobjekte in unserem Java Spiel erstellt werden. Die Klasse selbst ist als abstract deklariert, wodurch keine Objekte ihres Typs instanziiert werden können. Dies ist so gewollt, da die GameObject-Klasse nur als Rahmenwerk für die verschiedenen Spielobjekte fungieren soll. Erst von ihr abgeleitete Klassen werden konkrete Spielobjekte vollständig definieren und dadurch auch erzeugen können.
Beginnen wir nun mit dem ersten Arbeitsschritt, dem Anlegen der GameObject.java
Klassendatei.
2.1 Anlegen der Klassendatei GameObject.java in NetBeans
Die Klasse GameObject legen wir wie folgt an:
- Wir klicken mit der rechten Maustaste auf den Haupt-Packagenamen in der Projektleiste.
- Danach auf den
New
-Eintrag im Kontextmenü klicken. - Anschließend auf den Eintrag
Java Class...
klicken.
Daraufhin öffnet sich der New Java Class-Dialog von NetBeans. In diesem Dialog geben wir als Namen der zu erstellenden Klasse GameObject
ein.
Alle anderen Einstellungen lassen wir unverändert und bestätigen den Dialog mit einem Klick auf Finish
.
Jetzt generiert NetBeans eine neue Java-Datei mit dem Namen GameObject.java
und fügt sie in das Hauptpaket unseres Java Projekts ein. Dies sollte bei euch wie folgt aussehen:
Die neue Klasse GameObject ist momentan noch leer. Das werden wir nun ändern und sie mit Quellcode füllen.
2.2 Definieren der GameObject-Klasse
Die Klassendatei GameObject.java
haben wir nun mit Hilfe von NetBeans in unserem Java Projekt angelegt. Als Nächstes werden wir die GameObject-Klasse ausprogrammieren, also den Klassenbereich mit Variablen und Methoden füllen.
Dazu fügen wir den folgenden Quellcode in die GameObject-Klasse ein und ersetzen damit den generierten Code:
GameObject.java
package de.programmierenlernenhq.panzerhq; public abstract class GameObject { private Coordinate objectPosition; private double width; private double height; private double movingAngle; private double movingDistance; public GameObject(Coordinate objectPosition, double width, double height) { this.objectPosition = objectPosition; this.width = width; this.height = height; movingAngle = 0; movingDistance = 0; } public Coordinate getObjectPosition() { return objectPosition; } public void setObjectPosition(Coordinate objectPosition) { this.objectPosition = objectPosition; } public double getWidth() { return width; } public void setWidth(double width) { this.width = width; } public double getHeight() { return height; } public void setHeight(double height) { this.height = height; } public double getMovingAngle() { return movingAngle; } public void setMovingAngle(double movingAngle) { this.movingAngle = movingAngle; } public double getMovingDistance() { return movingDistance; } public void setMovingDistance(double movingDistance) { this.movingDistance = movingDistance; } public boolean isLeftOf(GameObject that) { return this.getObjectPosition().getX() + this.getWidth() < that.getObjectPosition().getX(); } public boolean isAbove(GameObject that) { return this.getObjectPosition().getY() + this.getHeight() < that.getObjectPosition().getY(); } public boolean touches(GameObject that) { if(this.isLeftOf(that)) return false; if(that.isLeftOf(this)) return false; if(this.isAbove(that)) return false; if(that.isAbove(this)) return false; return true; } public static Coordinate polarToCartesianCoordinates(double angle) { // X-Achse zeigt nach Osten, Y-Achse zeigt nach Süden beim Zeichnen double x = Math.cos(angle); double y = Math.sin(angle); return new Coordinate(x, y); } public void moveGameObject() { Coordinate direction = polarToCartesianCoordinates(movingAngle); objectPosition.setX(objectPosition.getX() + direction.getX() * movingDistance); objectPosition.setY(objectPosition.getY() + direction.getY() * movingDistance); } public void makeMove() { moveGameObject(); } protected abstract void paintMe(java.awt.Graphics g); }
Bereits die dritte Zeile im oberen Quellcode definiert die wichtigste Eigenschaft der Klasse GameObject. Durch das Schlüsselwort abstract legen wir fest, dass die GameObject-Klasse eine abstrakte Klasse sein wird, von der keine Instanzen erzeugt werden können.
Mit der GameObject-Klasse möchten wir die Grundlage für alle Spielobjekte in unserem Java Spiel legen. In ihr definieren wir alle Basiseigenschaften und -fähigkeiten von Spielobjekten. Die speziellen Eigenschaften und Fähigkeiten, wie das Aussehen oder Bewegungsart, sollen dann in abgeleiteten Klassen nachträglich definiert werden.
Die GameObject-Klasse ist somit nur der minimale Bauplan, nach dem alle Spielobjekte gebaut werden. Als Membervariablen besitzen dadurch alle Spielobjekte:
- Eine Position auf der Spielfläche (objectPosition).
- Angaben für Breite und Höhe des Spielobjekts (width, height).
- Angaben für die Bewegungsrichtung und die dabei zurückzulegende Entfernung (movingAngle, movingDistance).
Weiterhin besitzen alle Spielobjekte die Fähigkeit zu erkennen, ob sie mit einem anderen Spielobjekt kollidieren. Dies wird durch die Methoden isLeftOf(), isAbove() und touches() realisiert.
Mit Hilfe dieser drei Methoden können wir einfach prüfen, ob zwei Spielobjekte nebeneinander oder übereinander liegen. Ist dies der Fall, dann berühren sie sich nicht. Ist dies nicht der Fall, dann ist eine Kollision eingetreten. Dieses Vorgehen entspricht einer sehr simple Kollisionsabfrage für viereckige Spielobjekte. Es geht besser, aber für unsere Zwecke ist es absolut ausreichend.
Kommen wir nun zu einer sehr wichtigen Methode aller Spielobjekte unseres Java Spiels, der makeMove()-Methode. Diese Methode wird bei jedem Tick unseres Timers aufgerufen und ermöglicht den Spielobjekten ihren Zug durchzuführen.
Bei simplen Spielobjekten, wie bspw. Geschossen, besteht ein Zug hauptsächlich aus der Bewegung des Spielobjekts in die jeweilige Bewegungsrichtung. Bei komplexeren Spielobjekten, wie bspw. den Panzern, werden neben dem Bewegen des Objekts noch weitere Aktionen (Schießen, Turm Drehen, Nachladen) in einem Zug durchgeführt.
In der Klasse GameObject ist das Bewegen des Spielobjekts bereits mit der Methode moveGameObject() implementiert. Diese Methode wird standardmäßig in der makeMove() Methode aufgerufen.
Damit ein Spielobjekt in die korrekte Bewegungsrichtung fortbewegt wird, muss der Bewegungswinkel in einen Bewegungsvektor umgewandelt werden. Dies wird durch die statische Methode polarToCartesianCoordinates() ausgeführt. Sie wandelt eine Polarkoordinate der Länge 1 in eine kartesische Koordinate um, oder anders ausgedrückt einen Winkel in einen X,Y-Richtungsvektor der Länge 1.
Ganz am Ende der GameObject-Klasse, in der Zeile 97, befindet sich die abstrakte Methode paintMe(). Genauer gesagt befindet sich dort die Deklaration der noch abstrakten Methode paintMe(). Aufgrund dieser abstrakten Methode muss die gesamte Klasse GameObject selbst abstrakt sein.
Spielobjekte können erst instanziiert werden, wenn die abstrakte Methode paintMe() ausprogrammiert wird, also der Methodenrumpf mit Quellcode gefüllt wird. Dies werden wir in Unterklassen der GameObject-Klasse später vornehmen.
Auf diese Weise erzwingen wir, dass alle konkreten Spielobjekte die Methode paintMe() implementieren müssen. Das ist auch der Grund, warum wir die GameObject-Klasse als abstrakt deklariert haben.
Weitere Informationen über abstrakte Klassen in Java findet ihr hier.
In NetBeans sollte die GameObject-Klasse nun wie folgt aussehen:
In der oberen Abbildung haben wir einige Markierungen vorgenommen. Wir möchten damit auf einige wichtige Bestandteile der GameObject-Klasse hinweisen:
- A: Deklaration der abstrakten Klasse – Mit dem Schlüsselwort abstract legen wir fest, dass die GameObject-Klasse eine abstrakte Klasse sein wird. Dies müssen wir auch tun, da die Klasse eine abstrakte Methode besitzt (siehe Punkt D).
- B: Die statische Methode zum Umrechnen der Koordinaten – Die Methode polarToCartesianCoordinates() haben wir als static deklariert. Dadurch wird sie zu einer Klassenmethode und kann über den Klassennamen auch von anderen Klassen aus aufgerufen werden.
- C: Die makeMove() Methode – Durch diese Methode können die Spielobjekte ihren Spielzug durchführen. Ein Spielzug besteht mindestens aus der Bewegung des Spielobjekts auf der Spielfläche.
- D: Die abstrakte Methode paintMe() – Diese Methode ist nicht implementiert, sondern nur mittels Methodenkopf deklariert. Sie muss von abgeleiteten Klassen implementiert werden, erst dann können konkrete Spielobjekte erzeugt werden.
Mit dem Anlegen der GameObject-Klasse haben wir nun die Grundlage für die Spielobjekte unseres Java Spiels gelegt. Im nächsten Abschnitt werden wir eine Klasse von der GameObject-Klasse ableiten, die das Erstellen unseres ersten konkreten Spielobjekts ermöglichen wird.
3. Erstellen der Missile-Klasse, die Geschosse im Java Spiel
Als dritte neue Klasse legen wir nun die Missile-Klasse mit der NetBeans IDE an. Die Missile-Klasse wird die GameObject-Klasse erweitern und ihre abstrakte Methode paintMe() implementieren, wodurch es möglich sein wird, konkrete Spielobjekte des Missile-Typs erstellen zu können.
Um die Missile-Klasse zu erstellen, werden wir wieder die folgenden beiden Arbeitsschritte ausführen:
- Anlegen der Klassendatei in NetBeans – Der erste Schritt ist schnell gemacht. Wir erstellen mit Hilfe der Entwicklungsumgebung NetBeans IDE die Klasse Missile.
- Definieren der Missile-Klasse – Im zweiten Schritt programmieren wir die neu erstellte Klasse aus. Die Missile-Klasse ist das erste konkrete Spielobjekt unseres Java Spiels. Wir werden Missile-Objekte später als Geschosse einsetzen, die von den Panzerobjekten abgefeuert werden können.
Beginnen wir nun mit dem ersten Arbeitsschritt, dem Anlegen der Missile.java
Klassendatei.
3.1 Anlegen der Klassendatei Missile.java in NetBeans
Die Klasse Missile legen wir wie folgt an:
- Wir klicken mit der rechten Maustaste auf den Haupt-Packagenamen in der Projektleiste.
- Danach auf den
New
-Eintrag im Kontextmenü klicken. - Anschließend auf den Eintrag
Java Class...
klicken.
Daraufhin öffnet sich der New Java Class-Dialog von NetBeans. In diesem Dialog geben wir als Namen der zu erstellenden Klasse Missile
ein.
Alle anderen Einstellungen lassen wir unverändert und bestätigen den Dialog mit einem Klick auf Finish
.
Jetzt generiert NetBeans eine neue Java-Datei mit dem Namen Missile.java
und fügt sie in das Hauptpaket unseres Java Projekts ein. Dies sollte bei euch wie folgt aussehen:
Die neue Klasse Missile ist momentan noch leer. Das werden wir nun ändern und sie mit Quellcode füllen.
3.2 Definieren der Missile-Klasse
Die Klassendatei Missile.java
haben wir nun mit Hilfe von NetBeans in unserem Java Projekt angelegt. Als Nächstes werden wir die Missile-Klasse ausprogrammieren, also den Klassenbereich mit Variablen und Methoden füllen.
Dazu fügen wir den folgenden Quellcode in die Missile-Klasse ein und ersetzen damit den generierten Code:
Missile.java
package de.programmierenlernenhq.panzerhq; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Shape; import java.awt.geom.AffineTransform; import java.awt.geom.RoundRectangle2D; public class Missile extends GameObject { private int range = 100; public Missile (Coordinate position, double size, double movingAngle, double movingDistance) { super(position, size, size/3); setMovingAngle(movingAngle); setMovingDistance(movingDistance); } public int getRange() { return range; } public void setRange(int range) { this.range = range; } @Override public void makeMove() { if (range > 0) super.makeMove(); range--; } @Override public void paintMe(java.awt.Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.setColor(Color.BLACK); AffineTransform transform = new AffineTransform(); RoundRectangle2D missileShape = new RoundRectangle2D.Double(getObjectPosition().getX(), getObjectPosition().getY(), getWidth(), getHeight(), 3, 3); transform.rotate(getMovingAngle(),missileShape.getCenterX(), missileShape.getCenterY()); Shape transformedMissileShape = transform.createTransformedShape(missileShape); g2d.fill(transformedMissileShape); } }
Die Missile-Klasse erweitert die abstrakte Klasse GameObject (Zeile 9). Damit sie nicht selbst eine abstrakte Klasse ist, muss sie alle abstrakten Methoden ihrer Superklasse implementieren. In unserem Fall ist dies nur eine Methode und zwar die paintMe() Methode.
Mit dem Konstruktor in den Zeilen 13 bis 18 setzen wir die Werte der geerbten Membervariablen. Dazu rufen wir auch den Konstruktor der Superklasse mit der Anweisung in Zeile 14 auf.
Mit den Get- und Set-Methoden in den Zeilen 20 bis 26 können wir die Flugweite von Geschossen abfragen bzw. festlegen. Standardmäßig bewegt sich ein Geschoss 100 Schritte, also Spielzüge.
Mit dem Quellcode in den Zeilen 29 bis 32 überschreiben wir die makeMove() Methode der Superklasse GameObject. Solange der range
-Wert größer als 0 ist, rufen wir die makeMove() Methode der Superklasse, mit der Anweisung super.makeMove(), auf. Dadurch wird das Geschoss-Spielobjekt einen Schritt in die Bewegungsrichtung fortbewegt.
Anschließend implementieren wir mit den Zeilen 35 bis 50 die Methode paintMe(), die bisher nur abstrakt, ohne Methodenrumpf, in der Elternklasse deklariert wurde. Die Aufgabe der paintMe() Methode ist das Zeichnen des Spielobjekts, hier eines Geschosses.
Da sich die Spielobjekte auf der Spielfläche auch drehen können, verwenden wir zum Zeichnen der verschiedenen Formen die Klasse Graphics2D verwenden. Diese Klasse leitet sich von der Klasse Graphics ab und erweitert sie um einige komplexe Zeichenfunktionen, bspw. der Transformation von Formen (shapes).
Die Rotation des Spielobjekt um seinen Bewegungswinkel führen wir mit Hilfe eines AffineTransform-Objekts durch. Bei einer Affine Abbildung bleibt die Geradheit und Parallelität der Linien der transformierten Formen erhalten.
Zuerst erzeugen wir in den Zeilen 41 bis 43 ein Rechteck als Form unseres Geschosses. Dabei greifen wir auf die Membervariablen der Missile-Klasse zu und lesen die aktuelle Position und Ausmaße des Spielobjekts aus. Anschließend rotieren wir die Affine Abbildung um den Bewegungswinkel des Spielobjekts. Als Ankerpunkt übergeben wir die Koordinaten des Mittelpunkts unseres Spielobjekts.
Nun erzeugen wir in Zeile 46 die transformierte Form unseres Spielobjekts mit Hilfe der Methode createTransformedShape(), die wir über das AffineTransform-Objekt aufrufen. Als Ergebnis erhalten wir die Form des Spielobjekts gedreht um den Bewegungswinkel.
Jetzt müssen wir die transformierte Form nur noch zeichnen. Dies führen wir in Zeile 48 durch und erhalten somit unser transformierten Spielobjekt.
Zu vielen weiteren nützliche Informationen über die Graphics-Klassen und Affine Transformations gelangt ihr über folgende Links:
- Erklärungen über die Klasse Graphics2D.
- Was ist eine Affine Abbildung (affine transformation)?
- Erklärungen über die AffineTransform-Klassen.
- Rotieren und andere Transformationen in Java.
- Die Java API-Referenz der Graphics-Klasse.
- Die Java API-Referenz der Graphics2D-Klasse.
- Die Java API-Referenz der AffineTransform-Klasse.
In NetBeans sollte die Missile-Klasse nun wie folgt aussehen:
In der oberen Abbildung haben wir einige Markierungen vorgenommen. Wir möchten damit auf einige wichtige Bestandteile der Missile-Klasse hinweisen:
- A: Festlegen der GameObject-Klasse als Superklasse – Mit dem Schlüsselwort extends legen wir fest, dass die Missile-Klasse die GameObject-Klasse erweitert.
- B: Überschreiben der makeMove() Methode – Wir überschreiben die makeMove() Methode der Superklasse, so dass Geschosse sich nur eine begrenzte Anzahl an Schritten fortbewegen können.
- C: Implementieren der paintMe() Methode – Damit wir Objekte vom Missile-Datentyp instanziieren können, implementieren wir die abstrakte Methode paintMe() der Superklasse. Dadurch wird die Missile-Klasse zu einer konkreten Klasse, von der sich Instanzen erzeugen lassen.
Mit dem Anlegen der Missile-Klasse haben wir nun die Möglichkeit erste konkrete Spielobjekte in unserem Java Spiels anzulegen. Im nächsten Abschnitt werden wir ein konkretes Spielobjekt vom Typ Missile erzeugen und es über die Spielfläche bewegen lassen.
4. Das erste Spielobjekt auf der Spielfläche erzeugen
Nun haben wir alle Voraussetzungen geschaffen, um unsere ersten konkreten Spielobjekte auf der Spielfläche unseres Java Spiels zu erzeugen und darauf zu bewegen.
Wir werden jetzt zwei Missile-Spielobjekte erzeugen und diese über die virtuelle Spielfläche fliegen lassen. Dabei ergibt sich auch die günstige Gelegenheit die Kollisionsabfrage unseres Java Spiels zu überprüfen, indem wir die beiden Geschosse kollidieren lassen.
Um die Missile-Spielobjekte zu erzeugen, nehmen wir folgende Änderungen an der GamePanel-Klasse vor:
- Deklarieren der Missile-Objekte als Membervariablen der GamePanel-Klasse.
- Instanziieren der Missile-Objekte in der createGameObjects() Methode der GamePanel-Klasse.
- Bewegen der Missile-Objekte durch Aufrufen ihrer makeMove() Methode in der doOnTick() Methode der GamePanel-Klasse.
- Zeichnen der Missile-Objekte durch Aufrufen ihrer paintMe() Methode in der paintComponent() Methode der GamePanel-Klasse.
Wie zu sehen ist, werden wir den Quellcode der GamePanel-Klasse an vier Stellen erweitern. Es werden nur kleine Änderungen sein. Wichtig ist, dass diese an der korrekte Stelle der GamePanel-Klasse vorgenommen werden.
Wir öffnen nun die Klassendatei GamePanel.java
im Editorfenster von NetBeans und fügen die markierten Zeilen in den bereits vorhandenen Quellcode ein:
GamePanel.java
package de.programmierenlernenhq.panzerhq; import java.awt.Color; import java.awt.Dimension; import java.awt.Font; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.net.URL; import javax.swing.ImageIcon; import javax.swing.JPanel; import javax.swing.Timer; public class GamePanel extends JPanel{ private Missile testMissileOne; private Missile testMissileTwo; public static final String IMAGE_DIR = "images/"; private final Dimension prefSize = new Dimension(1180, 780); private ImageIcon backgroundImage; private final String[] backgroundImages= new String [] {"bg_mud.jpg", "bg_snow.jpg", "bg_sand.jpg"}; private boolean gameOver = false; private int tanksDestroyedCounter = 0; private Timer t; public GamePanel() { setFocusable(true); setPreferredSize(prefSize); initGame(); startGame(); } public boolean isGameOver() { return gameOver; } public void setGameOver(boolean gameOver) { this.gameOver = gameOver; } private void initGame () { setBackgroundImage(1); createGameObjects(); t = new Timer(20, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { doOnTick(); } }); } private void createGameObjects() { // hier werden wir später die Spielobjekte erzeugen testMissileOne = new Missile(new Coordinate(200,100), 9, Math.toRadians(45), 5); testMissileTwo = new Missile(new Coordinate(200,609), 9, Math.toRadians(-45), 5); } private void initPlayersTank() { // hier werden wir den Panzer des Spielers initialisieren } public void setBackgroundImage(int imageNumber) { String imagePath = IMAGE_DIR + backgroundImages[imageNumber]; URL imageURL = getClass().getResource(imagePath); backgroundImage = new ImageIcon(imageURL); } private void startGame() { t.start(); } public void pauseGame() { t.stop(); } public void continueGame() { if (!isGameOver()) t.start(); } public void restartGame() { tanksDestroyedCounter = 0; setGameOver(false); createGameObjects(); startGame(); } private void endGame() { setGameOver(true); pauseGame(); } private void doOnTick() { tanksDestroyedCounter++; if (tanksDestroyedCounter > 2015) endGame(); testMissileOne.makeMove(); testMissileTwo.makeMove(); if (testMissileOne.touches(testMissileTwo)) endGame(); repaint(); } @Override public void paintComponent (Graphics g) { super.paintComponent(g); Graphics2D g2d = (Graphics2D) g; g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); backgroundImage.paintIcon(null, g, 0, 0); g.setFont(new Font(Font.MONOSPACED, Font.BOLD, 19)); g.setColor(Color.BLUE); g.drawString("Tanks destroyed: " + tanksDestroyedCounter, 22 , prefSize.height - 5); if (isGameOver()) { g.setFont(new Font(Font.MONOSPACED, Font.BOLD, 50)); g.setColor(Color.RED); g.drawString("GAME OVER!", prefSize.width/2 - 130, prefSize.height/5); } testMissileOne.paintMe(g); testMissileTwo.paintMe(g); } }
Der neu eingefügte Quellcode ist ziemlich selbsterklärend. Wir wollen ihn trotzdem noch einmal kurz erklären.
In den Zeilen 18 und 19 deklarieren wir zwei Membervariablen vom Missile-Datentyp. Das sind unsere beiden ersten Spielobjekte. Wir werden sie aber nur zu Testzwecken hier in dieser Lektion verwenden.
Die beiden Spielobjekte müssen wir noch Konkretisieren, also virtuelle Instanzen von der Missile-Klasse erzeugen. Dies führen wir mit den beiden Zeilen 67 und 68 der createGameObjects() Methode aus. Wir erstellen zwei Missile-Objekte mit Hilfe des new-Schlüsselwortes und dem Konstruktor der Missile-Klasse. Die die Missile-Klasse nicht abstrakt ist, können wir auch Instanzen von ihr anlegen.
Alle Spielobjekte sollen pro Spielzyklus einmal die Möglichkeit erhalten ihren Spielzug auszuführen. Dies realisieren wir mit dem Aufruf der makeMove() Methode der beiden Missile-Objekte in den Zeilen 109 bis 111 der doOnTick() Methode.
Das reine Bewegen der Spielobjekte ist aber noch nicht genug. Um die Spielobjekte und ihre Bewegung auch zu sehen, müssen wir sie noch auf der Spielfläche unseres Java Spiels zeichnen. Dies führen wir mit den Anweisungen in den Zeilen 135 und 136 durch. Dabei rufen wir die paintMe() Methode der Missile-Objekte auf, wodurch sich die Spielobjekte selbst auf die Spielfläche zeichnen.
In NetBeans sollte der Quellcode der GamePanel.java
Klassendatei nun wie folgt aussehen:
In der oberen Abbildung haben wir die in den Quellcode der GamePanel-Klasse neu eingefügten Anweisungen jeweils mit einem blauen Rahmen markiert:
- A: Deklaration der Membervariablen vom Missile-Datentyp – Zu Testzwecken legen wir zwei Spielobjekte als Attribute der GamePanel-Klasse an.
- B: Instanziieren der beiden Missile-Objekte – In der createGameObjects() Methode erzeugen wir zwei Missile-Objekte und weisen diese den beiden Membervariablen zu.
- C: Bewegen der Spielobjekte durch Aufrufen der makeMove() Methode und Prüfen auf Kollision – Mit dem Aufruf der makeMove() Methode lassen wir unsere beiden Spielobjekte ihren Spielzug durchführen. Dieser besteht hauptsächlich aus der Bewegung des Spielobjekt auf der Spielfläche durch Änderung der Objektposition. Außerdem prüfen wir, ob die beiden Geschosse miteinander kollidieren. Ist dies der Fall, beenden wir das Java Spiel und lassen GAME OVER! anzeigen.
- D: Zeichnen der Spielobjekte durch Aufrufen der paintMe() Methode – Damit die Spielobjekte auf der Spielfläche auch zu sehen sind, müssen diese gezeichnet werden. Das Zeichnen lassen wir vom Spielobjekt selbst ausführen, durch Aufrufen der paintMe() Methode.
4.1 Testen unseres Java Spiels und der erstellten Spielobjekte
Wir führen nun unser Java Projekt mit einem Klick auf das Run Project-Symbol aus.
Im Hintergrund wird jetzt unser Java Spiel von der NetBeans IDE erstellt und anschließend ausgeführt.
Obwohl wir ganze drei neue Java Klassen angelegt haben, hat sich im Spielfenster nicht viel geändert. Und doch sind wir einen großen Schritt bei der Entwicklung unseres Java Spiels voran gekommen. Es bewegen sich jetzt Spielobjekte auf der Spielfläche.
Die Spielobjekte können wir rotieren lassen und prüfen, ob sie miteinander kollidieren. Damit ist die Grundlage für komplexere Spielobjekte und das Prüfen auf Treffer mit Hilfe der Kollisionsabfrage gelegt.
In der unteren Abbildung sind die beidem Missile-Spielobjekte zum Zeitpunkt ihrer Kollision zu sehen. In diesem Moment beenden wir auch zu Testzwecken der Kollisionsabfrage das Java Spiel und lassen GAME OVER! anzeigen:
4.2 Die sechs Klassendateien unseres Java Projekts zum Download
Somit haben wir alle Änderungen am Quellcode unseres Java Spiels für diese Lektion vorgenommen. Diesmal haben wir drei neue Klassen angelegt und an einer bestehenden Klasse Änderungen vorgenommen. Damit es nicht zu Missverständnissen kommt, haben wir unten alle Klassendateien zum Download bereitgestellt.
Ihr solltet noch einmal in Ruhe den kompletten Quellcode der aufgelisteten Klassendateien unseres Java Spiels anschauen und mit eurem vergleichen:
PanzerHQ.javaGameWindow.java
GamePanel.java
Coordinate.java
GameObject.java
Missile.java
Wir sind nun am Ende der vierten Lektion unseres Java Spiel Programmieren Tutorials angekommen. Mittlerweile besitzt das Spielfenster unseres Java Spiele schon etwas Funktionalität und auch bewegte Spielobjekte, die wir als Nächstes in einem kleinen Video vorstellen werden.
4.3 Video – Funktionspräsentation unseres Java Spiels
In dem unteren Video ist das Spielfenster unseres selbst programmierten Java Spiels zu sehen. Wir sind nun in der Lage erste Spielobjekte auf der Spielfläche zu bewegen. Es ist sogar schon eine einfache Kollisionsabfrage implementiert, mit deren Hilfe wir später Panzerabschüsse erkennen werden.
Die neuen Funktionen unseres Java Spiels haben wir im unteren Video vorgeführt:
Wie man im Video sehen kann, fliegen nun kleine schwarze Geschosse über die Spielfläche unseres Java Spiels. Zu Testzwecken lassen wir das Spiel enden, sobald es zu eine Kollision zwischen zwei Spielobjekten kommt. Dann geben wir GAME OVER! in dem Spielfenster aus.
Zusammenfassung
In der vierten Lektion unseres Java Spiel Programmieren Tutorials haben wir die Grundlage für bewegte Spielobjekte geschaffen. Dazu haben wir zuerst die Hilfsklasse Coordinate erstellt, mit deren Hilfe wir die Spielobjekte auf der Spielfläche positionieren.
Anschließend haben wir die abstrakte Klasse GameObject in NetBeans angelegt. Da diese Klasse die abstrakte Methode paintMe() enthält, können keine Instanzen dieser Klasse erzeugt werden. Stattdessen müssen Unterklassen die GameObject-Klasse erweitern und dabei die abstrakte Methode implementieren. Erst dann können konkrete Spielobjekte instanziiert werden.
Aus diesem Grund haben wir als dritten Schritt die Klasse Missile erstellt, die Geschosse in unserem Java Spiel repräsentiert. Sie leitet sich von der GameObject-Klasse ab und implementiert die paintMe() Methode. Somit haben wir die Voraussetzungen geschaffen konkrete Spielobjekte auf der Spielfläche zu erzeugen.
Dies haben wir dann auch ausgeführt und in der GamePanel-Klasse, welche die Zeichenfläche und Spiellogik unseres Java Spiels realisiert, zwei Missile-Objekte angelegt. Die beiden Spielobjekte haben wir über die Spielfläche bewegen und schließlich miteinander kollidieren lassen. Dabei haben wir gleich die Kollisionsabfrage unseres Java Spiels mit überprüft.
In der nächsten Lektion unseres Java Spiele Programmieren Tutorials werden wir ein weiteres Spielobjekt erstellen, den Panzer des Spielers.
Comments 1
Pingback: Java Spiel Programmieren Tutorial - Das Java Projekt anlegen