Die neue Klasse wird die abstrakte Klasse GameObject erweitern und dadurch die grundlegenden Eigenschaften von Spielobjekten erben. Zusätzlich werden wir neue spezielle Eigenschaften und Fähigkeiten, wie das Drehen des Panzers und Schießen, in ihr definieren.
Nachdem wir die Tank-Klasse definiert und getestet haben, werden wir die Steuerung des Panzers implementieren. Der Panzer soll mit der Tastatur gesteuert werden, daher werden wir für das Überwachen der Tastatureingaben einen KeyListener in der GamePanel-Klasse registrieren.
Mit dem KeyListener prüfen wir, welche Taste gerade gedrückt wird und lassen unser Java Spiel entsprechend darauf reagieren. Der Panzer wird vor- und rückwärts fahren können und dabei sich selbst und auch den Turm drehen können.
Außerdem kann der Panzer Geschosse abfeuern. Die Geschosse sind Instanzen der Missile-Klasse, welche unser anderes bereits implementiertes Spielobjekt repräsentiert.
In dieser Lektion lernt ihr:
- Erstellen der Tank-Klasse, den Panzern in unserem Java Spiel
- Die Steuerung des Spielerpanzers implementieren
- Testen des Java Spiels und Steuern des Spielerpanzers
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:
Nun wünschen wir euch viel Spaß beim fünften Teil unseres Java Spiel Programmieren Tutorials. Los geht’s!
1. Erstellen der Tank-Klasse, den Panzern unseres Java Spiels
Wir werden nun eine weitere Klasse unserem Java Projekt hinzufügen. Die neue Klasse wird den Namen Tank tragen und den Bauplan für die Panzer in unserem Java Spiel bereitstellen.
Um die Tank-Klasse zu erstellen, werden wir die folgenden beiden Arbeitsschritte ausführen:
Beginnen wir nun mit dem ersten Arbeitsschritt, dem Anlegen der Tank.java
Klassendatei.
1.1 Anlegen der Klassendatei Tank.java in NetBeans
Die Klasse Tank 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 Tank
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 Tank.java
und fügt sie in das Hauptpaket unseres Java Projekts ein. Dies sollte bei euch wie folgt aussehen:
Die neue Klasse Tank ist momentan noch leer. Das werden wir nun ändern und sie mit Quellcode füllen.
1.2 Definieren der Tank-Klasse
Die Klassendatei Tank.java
haben wir nun mit Hilfe von NetBeans in unserem Java Projekt angelegt. Als Nächstes werden wir die Tank-Klasse ausprogrammieren, also den Klassenbereich mit Variablen und Methoden füllen.
Dazu fügen wir den folgenden Quellcode in die Tank-Klasse ein und ersetzen damit den generierten Code:
Tank.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 Tank extends GameObject{ public static final double TURNING_VELOCITY = 0.03; public static final double DRIVING_VELOCIY = 2.00; public static final int AMMO_LOADING_TIME = 50; private Shape transformedTankBody = new RoundRectangle2D.Double(); private double turningVelocity = TURNING_VELOCITY; private double drivingVelocity = DRIVING_VELOCIY; private double deltaMovingAngle = 0; private double angleCannon = 0; private double deltaAngleCannon = 0; private boolean ableToShoot = true; private int loadingTime = AMMO_LOADING_TIME; private int ammoLoadingTimer = AMMO_LOADING_TIME; private Color turretColor = new Color(160, 184, 98); private Color cannonColor = new Color(72, 94, 10); private int energy = 10; private int energyStart = 10; private boolean paintTankStatusBar = true; public Tank(Coordinate position, double width, double height, double movingAngle, double movingDistance) { super(position, width, height); setMovingAngle(movingAngle); setMovingDistance(movingDistance); } public Shape getTransformedTankBody() { return transformedTankBody; } public void setTransformedTankBody(Shape transformedTankBody) { this.transformedTankBody = transformedTankBody; } public double getTurningVelocity() { return turningVelocity; } public void setTurningVelocity(double turningSpeed) { this.turningVelocity = turningSpeed; } public double getDrivingVelocity() { return drivingVelocity; } public void setDrivingVelocity(double drivingSpeed) { this.drivingVelocity = drivingSpeed; } public double getAngleCannon() { return angleCannon; } public void setAngleCannon(double angle) { this.angleCannon = angle; } public boolean isAbleToShoot() { return ableToShoot; } public void setAbleToShoot(boolean ableToShoot) { this.ableToShoot = ableToShoot; ammoLoadingTimer = loadingTime; } public int getLoadingTime() { return loadingTime; } public void setLoadingTime(int loadingTime) { this.loadingTime = loadingTime; } public Color getTurretColor() { return turretColor; } public void setTurretColor(Color turretColor) { this.turretColor = turretColor; } public Color getCannonColor() { return cannonColor; } public void setCannonColor(Color cannonColor) { this.cannonColor = cannonColor; } public int getEnergy() { return energy; } public void setEnergy(int energy) { this.energy = energy; } public int getEnergyStart() { return energyStart; } public void setEnergyStart(int energyStart) { this.energyStart = energyStart; } public boolean isPaintTankStatusBar() { return paintTankStatusBar; } public void setPaintTankStatusBar(boolean paintTankStatusBar) { this.paintTankStatusBar = paintTankStatusBar; } public void turnTankRight() { deltaMovingAngle = turningVelocity; } public void turnTankLeft() { deltaMovingAngle = - turningVelocity; } public void stopTurningTank() { deltaMovingAngle = 0; } public void turnCannonRight() { deltaAngleCannon = turningVelocity*1.3; } public void turnCannonLeft() { deltaAngleCannon = - turningVelocity*1.3; } public void stopTurningCannon() { deltaAngleCannon = 0; } public void accelerateTank() { setMovingDistance(drivingVelocity); } public void decelerateTank() { setMovingDistance(-drivingVelocity/2); } public void stopTank() { setMovingDistance(0); } @Override public boolean touches(GameObject other) { Coordinate otherPosition = other.getObjectPosition(); double otherCenterX = otherPosition.getX() + other.getWidth()/2; double otherCenterY = otherPosition.getY() + other.getHeight()/2; return getTransformedTankBody().contains(otherCenterX,otherCenterY); } @Override public void makeMove() { double newMovingAngle = getMovingAngle() + deltaMovingAngle; if (newMovingAngle < 0) { newMovingAngle = newMovingAngle + 2 * Math.PI; } if (newMovingAngle > 2 * Math.PI) { newMovingAngle = newMovingAngle - 2 * Math.PI; } setMovingAngle(newMovingAngle); moveCannon(); if (ammoLoadingTimer > 0) { ammoLoadingTimer = ammoLoadingTimer - 1; } else { setAbleToShoot(true); } super.makeMove(); } public void moveCannon() { angleCannon = angleCannon + deltaAngleCannon; if (angleCannon < 0) { angleCannon = angleCannon + 2 * Math.PI; } if (angleCannon > 2 * Math.PI) { angleCannon = angleCannon - 2 * Math.PI; } } public Missile shoot() { double tankCenterX = getObjectPosition().getX() + getWidth()*0.5; double tankCenterY = getObjectPosition().getY() + getHeight()*0.5; double cannonLength = getWidth()*0.8; double missileSize = getWidth()*0.12; double missileAngle = getAngleCannon()+getMovingAngle(); Coordinate missileDirection = GameObject.polarToCartesianCoordinates(missileAngle); double cannonEndX = missileDirection.getX() * cannonLength; double cannonEndY = missileDirection.getY() * cannonLength; Coordinate missileStartPosition = new Coordinate(tankCenterX + cannonEndX - missileSize/2, tankCenterY + cannonEndY - missileSize/6); Missile missile = new Missile(missileStartPosition, missileSize, missileAngle, 5); setAbleToShoot(false); return missile; } @Override public void paintMe(java.awt.Graphics g) { Graphics2D g2d = (Graphics2D) g; if (isPaintTankStatusBar()) { paintTankStatusBars(g2d); } paintTank(g2d); } private void paintTank(Graphics2D g2d) { RoundRectangle2D tankBody = new RoundRectangle2D.Double(getObjectPosition().getX() + getWidth()*0.05, getObjectPosition().getY(), getWidth()*0.9, getHeight(), 15, 8); RoundRectangle2D tankTrackLeft = new RoundRectangle2D.Double(getObjectPosition().getX(), getObjectPosition().getY(), getWidth(), getHeight()*0.3, 15, 8); RoundRectangle2D tankTrackRight = new RoundRectangle2D.Double(getObjectPosition().getX(), getObjectPosition().getY() + getHeight()*0.7, getWidth(), getHeight()*0.3, 15, 8); RoundRectangle2D tankCannon = new RoundRectangle2D.Double(getObjectPosition().getX() + getWidth()*0.5, getObjectPosition().getY() + getHeight()*0.41, getWidth()*0.8, getHeight()*0.18, 5, 5); RoundRectangle2D tankTurret = new RoundRectangle2D.Double(getObjectPosition().getX() + getWidth()*0.14, getObjectPosition().getY() + getHeight()*0.1, getWidth()*0.65, getHeight()*0.8, 15, 8); AffineTransform transform = new AffineTransform(); transform.rotate(getMovingAngle(),tankBody.getCenterX(), tankBody.getCenterY()); g2d.setColor(Color.GRAY); Shape transformed = transform.createTransformedShape(tankBody); g2d.fill(transformed); setTransformedTankBody(transformed); g2d.setColor(Color.BLACK); transformed = transform.createTransformedShape(tankTrackLeft); g2d.fill(transformed); transformed = transform.createTransformedShape(tankTrackRight); g2d.fill(transformed); transform.rotate(getAngleCannon(),tankBody.getCenterX(), tankBody.getCenterY()); g2d.setColor(cannonColor); transformed = transform.createTransformedShape(tankCannon); g2d.fill(transformed); g2d.setColor(turretColor); transformed = transform.createTransformedShape(tankTurret); g2d.fill(transformed); } private void paintTankStatusBars(Graphics2D g2d) { double barOffsetY = getHeight()*0.6; // paint Tank Energy Bar g2d.setColor(Color.DARK_GRAY); RoundRectangle2D tankEnergyBarFrame = new RoundRectangle2D.Double(Math.round(getObjectPosition().getX()) - 1, Math.round(getObjectPosition().getY() - barOffsetY) - 1, getWidth() + 1, 6, 0, 0); g2d.draw(tankEnergyBarFrame); if (getEnergy() > 3) { g2d.setColor(Color.GREEN); } else { g2d.setColor(Color.RED); } RoundRectangle2D tankEnergyBar = new RoundRectangle2D.Double(Math.round(getObjectPosition().getX()), Math.round(getObjectPosition().getY() - barOffsetY), getWidth()/getEnergyStart()*(energy), 5, 0, 0); g2d.fill(tankEnergyBar); // paint Ammo Loading Bar g2d.setColor(Color.DARK_GRAY); RoundRectangle2D ammoLoadingBar = new RoundRectangle2D.Double(Math.round(getObjectPosition().getX()), Math.round(getObjectPosition().getY() - barOffsetY) - 5, getWidth()/getLoadingTime()*(ammoLoadingTimer), 2, 0, 0); if (isAbleToShoot()) { } else { g2d.fill(ammoLoadingBar); } } }
Der Quellcode der Tank-Klasse ist sehr umfangreich, daher werden wir ihn nicht am Stück, sondern etappenweise gemeinsam durchgehen.
Die Klasse Tank kann in die folgenden drei Bereiche unterteilt werden:
- Den Membervariablen – Sie stehen für die Eigenschaften der Tank-Klasse. Über die Getter/Setter-Methoden greifen wir auf die hinterlegten Werte, lesend und schreibend, zu.
- Den neu definierten Methoden – Sie stehen für die neuen Fähigkeiten der Tank-Klasse.
- Der überschriebenen abstrakten paintMe() Methode – Mit ihrer Hilfe lassen wir den Panzer und seine eigene Statusleiste auf die Spielfläche unseres Java Spiels zeichnen.
Beginnen wir nun mit dem Bereich der Membervariablen mit ihren zugehörigen Getter/Setter-Methoden.
Die Membervariablen der Tank-Klasse
In der Tank-Klasse werden 14 neue Membervariablen angelegt, sowie drei Konstanten.
Tank.java
. . . public static final double TURNING_VELOCITY = 0.03; public static final double DRIVING_VELOCIY = 2.00; public static final int AMMO_LOADING_TIME = 50; private Shape transformedTankBody = new RoundRectangle2D.Double(); private double turningVelocity = TURNING_VELOCITY; private double drivingVelocity = DRIVING_VELOCIY; private double deltaMovingAngle = 0; private double angleCannon = 0; private double deltaAngleCannon = 0; private boolean ableToShoot = true; private int loadingTime = AMMO_LOADING_TIME; private int ammoLoadingTimer = AMMO_LOADING_TIME; private Color turretColor = new Color(160, 184, 98); private Color cannonColor = new Color(72, 94, 10); private int energy = 10; private int energyStart = 10; private boolean paintTankStatusBar = true; . . .
Mit den drei Konstanten in den Zeilen 5 bis 7 definieren wir die Dreh- und Fahrgeschwindigkeit, sowie die Nachladezeit unseres Panzers.
Die Drehgeschwindigkeit gibt vor, wie schnell sich der Panzer selbst und sein Turm drehen lassen. Die Nachladezeit gibt vor, wieviel Zyklen vergehen müssen, bevor der Panzer wieder feuern kann.
In den Zeilen 9 bis 25 definieren wir die Membervariablen. Die meisten davon tragen selbsterklärende Namen. Wir werden nun auf jede einzelne Membervariablen kurz eingehen:
transformedTankBody
– Enthält die transformierte Form des Panzerkörpers. Wir nutzen diese Form später für eine exaktere Kollisionsabfrage.turningVelocity
unddrivingVelocity
– Mit diesen Variablen speichern wir den aktuellen Wert der Dreh- und Fahrgeschwindigkeit des Panzers.deltaMovingAngle
– Die Fahrtrichtung geben wir in Form eines Bewegungswinkel an. Mit dieser Variable halten wir fest, um wie viel Grad sich dieser Winkel im nächsten Zyklus ändern wird.angleCannon
unddeltaAngleCannon
– Diese beiden Variablen speichern den Winkel der Panzerkanone und die Änderung dieses Winkels im nächsten Zyklus.ableToShoot
– Unser Panzer soll nicht pausenlos Schießen können. Daher unterscheiden wir zwischen einem schussbereiten und nicht schussbereiten Zustand. Ist diese Variable true, dann ist der Panzer in der Lage ein Geschoss abzufeuern.loadingTime
undammoLoadingTimer
– Mit dem Timer zählen wir die Ladezeit runter. Erreicht der Zähler den Wert 0, ist der Ladevorgang abgeschlossen und der Panzer kann wieder schießen. Der Startwert des Timers beträgt standardmäßig 50 und wird in der Variable loadingTime gespeichert, wodurch die Ladezeit später für andere Panzer angepasst werden kann.turretColor
undcannonColor
– Später können die Farben des Spielerpanzers über einen Dialog geändert werden. In diesen beiden Variablen speichern wir die Farben des Turms und der Kanone.energy
undenergyStart
– Jeder Panzer besitzt eine gewisse Menge an Energie. Mit jedem Treffer wird die Energie des getroffenen Panzers um 1 reduziert. Wird der Wert 0 erreicht, gilt der getroffene Panzer als abgeschossen.paintTankStatusBar
– Die Energie eines jeden Panzers wird in einer Statusleiste über dem Spielobjekt angezeigt. Mit dieser Variable halten wir fest, ob diese Leiste angezeigt werden soll oder nicht.
Zu jeder der oben beschriebenen Membervariablen haben wir je eine Getter– und Setter-Methode definiert. Diese Getter/Setter-Methoden sind in dem oberen Quelltextausschnitt nicht aufgelistet, da sie meist nur einen Wert in die entsprechende Variable schreiben bzw. ihn aus dieser auslesen.
Kommen wir nun zu den neu definierten Methoden der Klasse Tank, mit welchem wir die speziellen Fähigkeiten des Panzer-Spielobjekts realisieren.
Die neu definierten Methoden der Tank-Klasse
Der folgende Quellcodeausschnitt enthält die neu definierten Methoden der Tank-Klasse.
Da der Code sehr lang ist, haben wir ihn aus Platzgründen standardmäßig eingeklappt. Um ihn betrachten zu können, müsst ihr einfach auf den Titel des Rahmens klicken. Dadurch klappt die umschließende Box auf und der Quellcode sichtbar wird.
Mit den ersten 9 Methoden wird das Bewegungsverhalten des Spielerpanzers realisiert.
Die Drehung des Panzers und seines Turms ist auf die gleiche Art umgesetzt. Mit den Methoden turnTankRight(), turnTankLeft(), turnCannonRight() und turnCannonLeft() setzen wir den Änderungswinkel der Bewegungsrichtung bzw. Schussrichtung auf die entsprechende Drehgeschwindigkeit. Die Winkel selbst dürfen wir an dieser Stelle nicht ändern, dies muss durch Aufrufen der Methode makeMove() vom Timer getaktet erfolgen.
Soll die jeweilige Drehung gestoppt werden, setzen wir einfach den entsprechenden Änderungswinkel auf den Wert 0. Dies führen wir mit den Methoden stopTurningTank() und stopTurningCannon() durch.
Das geradlinige Fahren des Panzers ist auf ähnliche Weise realisiert. Wir nutzen dafür die geerbte Methode setMovingDistance() der Superklasse GameObject, mit der wir die Schrittweite der nächsten Bewegung auf den Wert der Fahrgeschwindigkeit setzen. Rückwärtsfahren ist mit halber Geschwindigkeit möglich.
In den Zeilen 37 bis 43 überschreiben wir die touches() Methode, die für die Kollisionsabfrage verantwortlich ist. Da sich der Panzer drehen kann, ändert sich damit zwangsläufig auch die Ausrichtung seiner Grundfläche. Somit ist die bisher implementierte Kollisionsabfrage nicht mehr ausreichend.
Das ist aber kein großes Problem, da die Klasse RoundRectangle2D die Methode contains() des Interfaces Shape implementiert. Mit Hilfe der contains() Methode können wir eine ausreichend präzise Kollisionsabfrage durchführen.
Den Spielzug des Panzers führen wir mit der Methode makeMove() durch. Zuerst setzen wir den neuen Bewegungswinkel, dabei behandeln wir auch die Ausnahmesituation, nämlich wenn der Wert des Winkels unter 0 bzw. über 2 * PI
gesetzt wird. Anschließend rufen wir die Methode moveCannon() auf und lassen dadurch den Winkel der Panzerkanone auf den neuen Wert setzen.
Danach überprüfen wir, ob der Ladevorgang des Panzergeschosses abgeschlossen ist. Ist dies der Fall, setzen wir die Variable ableToShoot auf true. Falls nicht, verringern wir den Wert des ammoLoadingTimer um eins. Schließlich rufen wir die makeMove() Methode der Superklasse GameObject auf, wodurch das Spielobjekt (unser Panzer) auf der Spielfläche bewegt wird.
Als letzte neue Methode definieren wir die shoot() Methode in den Zeilen 78 bis 97. Durch sie feuern wir ein Geschoss aus dem freien Ende der Panzerkanone ab. Dazu bestimmen wir zuerst die Position des Kanonenendes als Startpunkt des Geschosses und die Richtung der Kanone als Geschossrichtung.
Anschließend erzeugen wir ein neues Missile-Objekt und setzen die Variable ableToShoot auf false. Das erzeugte Missile-Objekt geben wir als Rückgabewert der aufrufenden Methode zurück.
Kommen wir nun zum letzten Bereich im Quellcode der Tank-Klasse, zu der paintMe() Methode mit ihren beiden Hilfsmethoden paintTank() und paintTankStatusBars().
Die überschriebene abstrakte paintMe() Methode der Tank-Klasse
Der folgende Quellcodeausschnitt enthält die Methoden der Tank-Klasse, welche für das Zeichnen des Panzers verantwortlich sind. Auch diesmal ist der Code sehr lang, daher haben wir ihn wieder aus Platzgründen eingeklappt.
Die paintMe() Methode ist sehr klein. In ihr rufen wir die beiden Hilfsmethoden paintTankStatusBars() und paintTank(). Diese beiden Hilfsmethoden übernehmen das Zeichnen des Panzers und seiner Statusleiste.
Den Panzer lassen wir durch die Methode paintTank() auf die Spielfläche zeichnen. Ein Panzer besteht in unserem Java Spiel aus mehreren RoundRectangle2D-Objekten, deren Abmessung und Positionierung von der Panzergrundfläche abgeleitet wird.
Die Ausrichtung der Rechtecke wird von dem Bewegungswinkel des Panzers bestimmt. Mit Hilfe der AffineTransform-Klasse können wir die Rechteckformen sehr einfach um einen beliebigen Winkel rotieren lassen. In Zeile 39 rotieren wir das AffineTransform-Objekt um den Bewegungswinkel, dabei liegt der Ankerpunkt genau im Zentrum der Panzergrundfläche. Anschließend lassen wir die RoundRectangle2D-Objekte des Panzerkörpers und seiner Ketten gedreht um diesen Winkel zeichnen.
Zusätzlich speichern wir die Form der gedrehten Panzergrundfläche in der Variable transformedTankBody mit Hilfe der Setter-Methode setTransformedTankBody(). Mit Hilfe dieser gedrehten Grundform des Panzers, sind wir in der Lage eine präzise Kollisionsabfrage in der touches() Methode zu realisieren.
Danach rotieren wir das AffineTransform-Objekt in der Zeile 53 erneut. Diesmal um den Richtungswinkel der Panzerkanone. Somit ist das AffineTransform-Objekt um die beiden Winkel, dem Blickwinkel des Panzers und dem seiner Kanone, gedreht worden, beides mal mit dem Ankerpunkt im Panzermittelpunkt. Anschließend lassen wir die Kanone und den Turm des Spielerpanzers zeichnen.
Soviel zur paintTank() Methode. Damit wir wissen wie oft unser Panzer noch getroffen werden kann und wann er wieder schussbereit ist, haben wir die zweite Hilfsmethode paintTankStatusBars() definiert. In ihr zeichnen wir zwei Statusleisten, eine Energieleiste und eine Ladeleiste.
Beide Leisten befinden sich direkt über dem Panzerobjekt. Die tankEnergyBar, also die Energieleiste, wird immer angezeigt und ist grün bzw. rot (im kritischen Panzerzustand). Die ammoLoadingBar wird nur dargestellt, wenn ein Ladevorgang stattfindet. Dann läuft sie wie ein umgekehrter Fortschrittsbalken ab, bis sie ganz verschwindet.
Nun sind wir am Ende des Quellcodes der Tank-Klasse angekommen. Es ist ein bisschen viel Code geworden, dafür haben wir aber jetzt auch ein Panzerobjekt, dass fahren und schießen kann. Und sind in unserem Java Spiel einen großen Schritt voran gekommen.
1.3 Die Klassendatei Tank.java zum Download
Somit haben wir alle Änderungen am Quellcode der Tank.java
Klassendatei vorgenommen. Damit es nicht zu Missverständnissen kommt, haben wir unten die Klassendateien zum Download bereitgestellt.
Ihr solltet noch einmal in Ruhe den kompletten Quellcode der Tank-Klasse anschauen und mit eurem vergleichen:
Tank.java1.4 Einen Panzer auf der Spielfläche unseres Java Spiels erzeugen
Nun ist es an der Zeit unseren ersten Panzer über die Spielfläche fahren zu lassen. Dazu erzeugen wir ein Tank-Objekt und lassen es zu Testzwecken über das GamePanel bewegen. Dabei ergibt sich auch die günstige Gelegenheit die verbesserte Kollisionsabfrage unseres Java Spiels zu überprüfen, indem wir ein Geschoss den Panzer treffen lassen.
Um das Tank-Spielobjekt zu erzeugen, nehmen wir folgende Änderungen an der GamePanel-Klasse vor:
- Deklarieren des Tank-Objekts als Membervariablen der GamePanel-Klasse.
- Aufrufen der initPlayersTank() Methode in der initGame() Methode der GamePanel-Klasse.
- Instanziieren des Tank-Objekts in der initPlayersTank() Methode der GamePanel-Klasse.
- Bewegen des Tank-Objekts durch Aufrufen seiner makeMove() Methode in der doOnTick() Methode der GamePanel-Klasse und prüfen auf Kollision.
- Zeichnen des Tank-Objekts durch Aufrufen seiner paintMe() Methode in der paintComponent() Methode der GamePanel-Klasse.
Wie zu sehen ist, werden wir den Quellcode der GamePanel-Klasse an fünf 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; // In Lektion 5 eingefügt private Tank testTank; 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(); // In Lektion 5 eingefügt initPlayersTank(); 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 // In Lektion 5 eingefügt testTank = new Tank(new Coordinate(630,260), 70, 45, Math.toRadians(270), 0); testTank.accelerateTank(); testTank.turnTankLeft(); testTank.turnCannonRight(); testMissileOne = testTank.shoot(); } 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(); // In Lektion 5 auskommentiert //if (testMissileOne.touches(testMissileTwo)) endGame(); // In Lektion 5 eingefügt testTank.makeMove(); if (testTank.touches(testMissileTwo)) endGame(); if (testTank.isAbleToShoot()) testMissileOne = testTank.shoot(); 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); } // In Lektion 5 eingefügt testTank.paintMe(g); testMissileOne.paintMe(g); testMissileTwo.paintMe(g); } }
Die im oberen Quellcode vorgenommenen Änderungen sind leicht verständlich, daher werden wir nur kurz auf sie eingehen.
In Zeile 22 legen wir die Membervariable testTank
vom Datentyp Tank an. Wir werden ihr etwas weiter unten im Quelltext eine Instanz der Tank-Klasse zuweisen.
In Zeile 61 rufen wir die bereits vorhandene Methode initPlayersTank() auf, in welcher wir später der Panzer des Spielers initialisieren werden. Hier werden wir sie zum Initialisieren des Test-Panzers nutzen.
In Zeile 81 in der Methode initPlayersTank() erzeugen wir eine Instanz der Tank-Klasse und weisen dieses Objekt der Membervariable testTank
zu. Anschließend geben wir in den Zeilen 82 bis 84 dem Panzer zu Testzwecken die folgenden drei Bewegungsanweisungen:
- testTank.accelerateTank() – Den Panzer in Blickrichtung beschleunigen.
- testTank.turnTankLeft() – Den Panzer nach links drehen.
- testTank.turnCannonRight() – Den Turm des Panzers nach rechts drehen.
In Zeile 85 lassen wir den Panzer ein Geschoss abfeuern. Dabei erzeugen wir durch Aufrufen der Methode shoot() ein neues Missile-Objekt und weisen es der Variable testMissileOne zu.
Den Spielzug des Panzers lassen wir in der doOnTick() ausführen. Zuerst kommentieren wir die Zeile 126 aus. Die Abfrage, ob sich die beiden Geschosse treffen, benötigen wir nicht mehr.
In der Zeile 129 rufen wir die makeMove() Methode der Tank-Klasse auf, wodurch das Spielobjekt seine neue Position und Drehwinkel berechnet. Anschließend prüfen wir, ob der Panzer von dem Geschoss testMissileTwo getroffen wurde und beenden das Spiel, falls eine Kollision vorliegt.
In Zeile 131 lassen wir den Panzer erneut schießen, sobald er dazu in der Lage ist und den Ladevorgang abgeschlossen hat. Das Spielobjekt, also den Spielerpanzer, lassen wir mit der Anweisung in Zeile 156 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 Membervariable vom Tank-Datentyp – Zu Testzwecken legen wir ein Spielobjekt als Attribute der GamePanel-Klasse an.
- B: Aufrufen der initPlayersTank() Methode – In der initGame() Methode rufen wir die initPlayersTank() Methode auf.
- C: Instanziieren des Tank-Objekts – In der initPlayersTank() Methode erzeugen wir ein Tank-Objekt und weisen dieses der weiter oben deklarierten Membervariable zu. Anschließend befehlen wir dem Panzer zu beschleunigen, sich nach links und seinen Turm nach rechts zu drehen. Außerdem lassen wir den Panzer noch ein Geschoss abfeuern.
- D: Bewegen des Spielobjekts durch Aufrufen der makeMove() Methode – Mit dem Aufruf der makeMove() Methode lassen wir das Panzer-Spielobjekt seinen Spielzug durchführen. Dieser besteht aus der Bewegung des Spielobjekt auf der Spielfläche durch Änderung der Objektposition, des Bewegungswinkels und des Turmwinkels. Außerdem prüfen wir, ob der Panzer mit einem Geschoss kollidieren. Ist dies der Fall, beenden wir das Java Spiel und lassen GAME OVER! anzeigen. Schließlich lassen wir den Panzer erneut schießen, sobald er den Ladevorgang abgeschlossen hat.
- E: Zeichnen des Spielobjekts durch Aufrufen der paintMe() Methode – Damit das Panzer-Spielobjekt auf der Spielfläche auch zu sehen ist, muss es gezeichnet werden. Das Zeichnen lassen wir vom Spielobjekt selbst ausführen, durch Aufrufen der paintMe() Methode.
1.5 Testen der Fahrmodi des Spielerpanzers
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.
Das Panzer-Spielobjekt setzt sich sofort in Bewegung. Es fährt vorwärts in einem großen Bogen nach links, dabei dreht es seinen Turm nach rechts und feuert mehrere Geschosse ab. Nach kurzer Zeit wird es von einem Geschoss getroffen, wodurch das Java Spiel angehalten wird.
In der unteren Abbildung ist das neu erstellte Tank-Spielobjekt zum Zeitpunkt der Kollision mit einem Missile-Spielobjekt zu sehen. In diesem Moment wird auch zu Testzwecken der Kollisionsabfrage das Java Spiel beendet und GAME OVER! angezeigt:
2. Die Steuerung des Spielerpanzers implementieren
Unser Spielerpanzer kann nun fahren, sich und seinen Turm drehen sowie Geschosse abfeuern.
Somit sind alle Voraussetzungen geschaffen, um die Steuerung des Panzers zu realisieren. Dies werden wir uns nun vornehmen.
Der Spielerpanzer wird über die Tastatur gesteuert werden, daher werden wir in der GamePanel-Klasse einen KeyListener registrieren und damit die Tastatureingaben überprüfen.
Die Panzersteuerung implementieren wir durch Einfügen des markierten Quellcodes in die initGame() Methode der GamePanel.java
Klassendatei:
GamePanel.java
private void initGame () { setBackgroundImage(1); createGameObjects(); // In Lektion 5 eingefügt initPlayersTank(); t = new Timer(20, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { doOnTick(); } }); // In Lektion 5 Kapitel 2 eingefügt addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { switch(e.getKeyCode()) { case VK_SPACE: if (testTank.isAbleToShoot()) { testMissileOne = testTank.shoot(); } break; case VK_DOWN: case VK_UP: testTank.stopTank(); break; case VK_LEFT: case VK_RIGHT: testTank.stopTurningTank(); break; case VK_W: case VK_E: testTank.stopTurningCannon(); break; } } @Override public void keyPressed(KeyEvent e) { switch(e.getKeyCode()) { case VK_LEFT: testTank.turnTankLeft(); break; case VK_RIGHT: testTank.turnTankRight(); break; case VK_UP: testTank.accelerateTank(); break; case VK_DOWN: testTank.decelerateTank(); break; case VK_W: testTank.turnCannonLeft(); break; case VK_E: testTank.turnCannonRight(); break; } } }); }
Zudem müssen die folgenden drei Import-Anweisungen unter die bereits vorhandenen eingefügt werden:
GamePanel.java
import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import static java.awt.event.KeyEvent.*;
Einige Java Listener haben wir bereits in Lektion 2 des Java Spiel Programmieren Tutorials kennengelernt. Dort haben wir ActionListener und WindowListener ausführlich behandelt.
In dieser Lektion lernen wir den KeyListener des java.awt.event
Pakets kennen. Ein KeyListener ist ein Interface, welches die Methoden keyTyped(), keyPressed() und keyReleased() deklariert, die noch implementiert werden müssen.
Damit wir nicht alle drei Methoden implementieren müssen, nutzen wir die abstrakte Klasse KeyAdapter, von der wir eine inneren anonyme Klasse ableiten werden. Die abstrakte Klasse KeyAdapter definiert alle drei oben genannten Methoden mit leerem Methodenrumpf.
Wir müssen daher nur die Methoden implementieren, die wir auch nutzen wollen. In unserem Fall sind das die beiden Methoden keyPressed() und keyReleased(), die wir mit Hilfe einer inneren anonymen Klasse implementieren und mit Anweisungen füllen.
Weitere Infos über die KeyAdapter-Klasse könnt ihr hier finden: http://docs.oracle.com/javase/7/docs/api/java/awt/event/KeyAdapter.html
Wir eine Taste gedrückt, dann erkennt dies der KeyListener und die passende Callback-Methode wird aufgerufen. Diese erhält als Argument das eingetretene KeyEvent-Ereignis übergeben. Wir lesen aus dem KeyEvent-Objekt den KeyCode aus.
Da jede Taste über einen eigenen KeyCode verfügt, können wir mit Hilfe einer Switch-Anweisung der gewünschten Taste einen eigenen Panzerbefehl zuweisen. Wir haben für das Fortbewegen des Panzers folgende Tasten und Fahrbefehle miteinander verknüpft:
- Pfeiltaste Links – Panzer nach links drehen.
- Pfeiltaste Rechts – Panzer nach rechts drehen.
- Pfeiltaste Oben – Panzer vorwärts fahren.
- Pfeiltaste Unten – Panzer rückwärts fahren.
- Taste W – Panzerturm nach links drehen.
- Taste E – Panzerturm nach links drehen.
Sobald eine der oben aufgeführten Tasten wieder losgelassen wird, wird die entsprechende Panzerbewegung gestoppt. Zudem können Geschosse durch Drücken der Leertase (Space) abgefeuert werden.
Mit diesen wenigen Änderungen an der GamePanel-Klasse haben wir die Steuerung des Spielerpanzers bereits realisiert. Damit wir die Steuerung korrekt testen können, werden wir nun noch einige Zeilen Testcode, den wir nicht mehr benötigen, auskommentieren. Dies werden wir nun in den folgenden Methoden vornehmen:
- initPlayersTank() – In dieser Methode werden wir die Panzerbefehle auskommentieren, welche wir im vorherigen Kapitel zu Testzwecken eingefügt hatten.
- doOnTick() – In dieser Methode kommentieren wir eine Zeile aus und fügen dafür eine neue Zeile ein.
2.1 Auskommentieren von überflüssigem Testcode
Wir nehmen folgende Änderungen an der Methode initPlayersTank() vor:
GamePanel.java
private void initPlayersTank() { // hier werden wir den Panzer des Spielers initialisieren // In Lektion 5 eingefügt testTank = new Tank(new Coordinate(630,260), 70, 45, Math.toRadians(270), 0); // In Lektion 5 Kapitel 2 auskommentiert //testTank.accelerateTank(); //testTank.turnTankLeft(); //testTank.turnCannonRight(); //testMissileOne = testTank.shoot(); }
Die Panzerbefehle in den Zeilen 8 bis 11 werden nicht mehr benötigt, da wir den Spielerpanzer jetzt selbst steuern können.
Nun nehmen wir noch folgende Änderungen an der Methode doOnTick() vor:
GamePanel.java
private void doOnTick() { tanksDestroyedCounter++; if (tanksDestroyedCounter > 2015) endGame(); testMissileOne.makeMove(); testMissileTwo.makeMove(); // In Lektion 5 auskommentiert //if (testMissileOne.touches(testMissileTwo)) endGame(); // In Lektion 5 eingefügt testTank.makeMove(); if (testTank.touches(testMissileTwo)) endGame(); // In Lektion 5 Kapitel 2 auskommentiert //if (testTank.isAbleToShoot()) testMissileOne = testTank.shoot(); // In Lektion 5 Kapitel 2 eingefügt if(testMissileTwo.getRange() < 1) testMissileTwo = new Missile(new Coordinate(200,609), 9, Math.toRadians(-45), 5); repaint(); }
Die Zeile 16 kommentieren wir aus, das wir nun selbst das Schießen mit dem Spielerpanzer durchführen werden.
Die Zeile 19 fügen wir ein und simulieren dadurch einen Dauerbeschuss. Das Missile-Objekt testMissileTwo
wird erneut abgefeuert, sobald seine Reichweite erschöpft ist.
Das waren alle vorzunehmenden Änderungen an der Klassendatei GamePanel.java
. Zur besseren Übersicht haben wir den gesamten Quellcode der GamePanel-Klasse noch einmal unten aufgeführt:
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.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import static java.awt.event.KeyEvent.*; 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; // In Lektion 5 eingefügt private Tank testTank; 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(); // In Lektion 5 eingefügt initPlayersTank(); t = new Timer(20, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { doOnTick(); } }); // In Lektion 5 Kapitel 2 eingefügt addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { switch(e.getKeyCode()) { case VK_SPACE: if (testTank.isAbleToShoot()) { testMissileOne = testTank.shoot(); } break; case VK_DOWN: case VK_UP: testTank.stopTank(); break; case VK_LEFT: case VK_RIGHT: testTank.stopTurningTank(); break; case VK_W: case VK_E: testTank.stopTurningCannon(); break; } } @Override public void keyPressed(KeyEvent e) { switch(e.getKeyCode()) { case VK_LEFT: testTank.turnTankLeft(); break; case VK_RIGHT: testTank.turnTankRight(); break; case VK_UP: testTank.accelerateTank(); break; case VK_DOWN: testTank.decelerateTank(); break; case VK_W: testTank.turnCannonLeft(); break; case VK_E: testTank.turnCannonRight(); break; } } }); } 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 // In Lektion 5 eingefügt testTank = new Tank(new Coordinate(630,260), 70, 45, Math.toRadians(270), 0); // In Lektion 5 Kapitel 2 auskommentiert //testTank.accelerateTank(); //testTank.turnTankLeft(); //testTank.turnCannonRight(); //testMissileOne = testTank.shoot(); } 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(); // In Lektion 5 auskommentiert //if (testMissileOne.touches(testMissileTwo)) endGame(); // In Lektion 5 eingefügt testTank.makeMove(); if (testTank.touches(testMissileTwo)) endGame(); // In Lektion 5 Kapitel 2 auskommentiert //if (testTank.isAbleToShoot()) testMissileOne = testTank.shoot(); // In Lektion 5 Kapitel 2 eingefügt if(testMissileTwo.getRange() < 1) testMissileTwo = new Missile(new Coordinate(200,609), 9, Math.toRadians(-45), 5); 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); } // In Lektion 5 eingefügt testTank.paintMe(g); testMissileOne.paintMe(g); testMissileTwo.paintMe(g); } }
In dem oberen Quellcode wurden die, in diesem Abschnitt, neu eingefügten bzw. auskommentierten Zeilen der Klasse GamePanel markiert.
In NetBeans sollte die GamePanel.java
Klassendatei nun wie folgt aussehen:
In der oberen Abbildung haben wir die nun durchgeführten Änderungen am Quellcode der GamePanel-Klasse mit einem blauen Rahmen markiert:
- A: Einfügen der Import-Anweisungen – Wir fügen drei weitere Import-Anweisungen ein.
- B: Registrieren des KeyListener in der initGame() Methode – In der initGame() Methode registrieren wir einen KeyListener für das GamePanel-Objekt und realisieren dadurch die Steuerung des Panzers. Mit dem KeyListener überwachen wir die Tastatureingaben des Spielers und reagieren entsprechend darauf.
- C: Auskommentieren von überflüssigem Testcode in der initPlayersTank() Methode – In der initPlayersTank() Methode kommentieren wir vier Zeilen nicht mehr benötigten Quellcodes aus. Der auskommentierte Quellcode wird nicht mehr benötigt, da wir den Panzer jetzt selbst steuern können.
- D: Kleine Änderungen in der doOnTick() Methode – Wir kommentieren eine Zeile des bereits vorhandenen Quellcodes aus, da der Panzer nicht mehr selbst automatisch schießen soll. Außerdem fügen wir eine neue Zeile hinzu, wodurch
testMissileTwo
immer wieder neu abgefeuert wird. Dadurch simulieren wir den Dauerbeschuss auf den Spielerpanzer, um die Kollisionsabfrage unter Spielbedingungen selbst überprüfen zu können.
2.2 Die Klassendateien unseres Java Projekts zum Download
Somit haben wir alle Änderungen am Quellcode unseres Java Spiels für diese Lektion vorgenommen. Diesmal haben wir eine 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
Tank.java
Als Nächstes werden wir unser Java Spiel starten und das erste Mal den Spielerpanzer selbst über die Spielfläche steuern.
3. Testen unseres Java Spiels und der Panzersteuerung
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.
Diesmal müssen wir uns nicht nur mit dem Zusehen begnügen, sondern können selbst in das Spielgeschehen eingreifen. Der Panzer wird mit den Pfeiltasten gesteuert. Den Panzerturm können wir mit den Tasten W und E nach links bzw. rechts drehen. Geschosse werden mit der Leertaste abgefeuert.
In der oberen Abbildung ist der Spielerpanzer am rechten Spielfeldrand zu sehen. Wir hatten gerade ein Geschoss abgefeuert und damit den Nachladevorgang gestartet. Dies erkennt man an dem schmalen grauen Balken über der Energieanzeige des Panzers.
3.1 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 den Spielerpanzer selbst über die Spielfläche zu steuern. Auch die verbesserte Kollisionsabfrage ist jetzt implementiert, mit deren Hilfe wir Panzerabschüsse exakter erkennen können.
Die neuen Funktionen unseres Java Spiels haben wir im unteren Video vorgeführt:
Zu Beginn des Videos erklären wir die Steuerung des Spielerpanzers. Anschließend fahren wir mit dem Panzer ein paar Kreise und feuern dabei mehrere Geschosse ab. Dabei ist der Ladebalken zu sehen, der den Nachladevorgang darstellt.
Am Ende des Videos lassen wir den Spielerpanzer von einem Geschoss treffen und testen dadurch die neue verbesserte Kollisionsabfrage. Unmittelbar nach dem Treffer endet unser Java Spiel und es wird GAME OVER! in dem Spielfenster ausgegeben.
Zusammenfassung
In der fünften Lektion unseres Java Spiel Programmieren Tutorials haben wir die Klasse Tank angelegt. Die Tank-Klasse repräsentiert den Spielerpanzer und implementiert alle grundlegenden Eigenschaften und Fähigkeiten eines Panzers.
Die Tank-Klasse haben wir von der abstrakten Klasse GameObject abgeleitet. Somit sind jetzt zwei Arten von konkreten Spielobjekten in unserem Java Spiel implementiert, nämlich Geschosse (missile ) und Panzer (tank).
Anschließend haben wir die Tank-Klasse getestet und den Spielerpanzer erstmals auf der Spielfläche unseres Java Spiels fahren lassen. Danach haben wir die Steuerung des Panzers implementiert. Der Panzer wird mit der Tastatur gesteuert, daher überwachen wir Tastatureingaben mit einem KeyListener, den wir für das GamePanel-Objekt registriert haben.
Mit dem KeyListener prüfen wir, welche Taste gerade gedrückt wird und lassen unser Java Spiel entsprechend darauf reagieren. Der Panzer kann vor- und rückwärts fahren und dabei sich selbst und auch den Turm drehen. Außerdem kann der Panzer Geschosse abfeuern. Die Geschosse sind Instanzen der Missile-Klasse, welche unser anderes bereits implementiertes Spielobjekt repräsentiert.
In der nächsten Lektion unseres Java Spiele Programmieren Tutorials werden wir die gegnerischen Panzer implementieren und so erstmals unsere Schießkünste erproben können.
Comments 1
Pingback: Java Spiel Programmieren Tutorial - Das Java Projekt anlegen