intro Java Spiel 5 vasabii_Fotolia_70882828

Java Spiel Programmieren Tutorial – Teil 5: Den Panzer und die Steuerung des Java Spiels programmieren


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

Android Apps Programmieren Online-Kurs

Unser großes
Android Online-Kurs
Gesamtpaket



Weitere Infos

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:

  1. Anlegen der Klassendatei in NetBeans – Zuerst erstellen wir mit Hilfe der Entwicklungsumgebung NetBeans IDE die Klasse Tank.

  2. Definieren der Tank-Klasse – Im zweiten Schritt programmieren wir die neu erstellte Klasse aus.

    Die Tank-Klasse ist ziemlich groß und wird von der GameObject-Klasse abgeleitet. Sie verfügt über mehrere Membervariablen mit den zugehörigen Getter/Setter-Methoden, sowie über einige spezielle Methoden, mit dessen Hilfe wir die Fähigkeiten des Panzers realisieren, wie bspw. sich zu drehen und Geschosse abzufeuern.

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:

  1. Wir klicken mit der rechten Maustaste auf den Haupt-Packagenamen in der Projektleiste.
  2. Danach auf den New-Eintrag im Kontextmenü klicken.
  3. Anschließend auf den Eintrag Java Class... klicken.
java spiel t5 new class tank

Eine neue Klasse unserem Java Projekt in NetBeans hinzufügen

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.

java spiel t5 klassenname tank

Der New Java Class Dialog von NetBeans

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:

java spiel t5 klasse tank start

Die neu erstellte Java Klasse Tank – Sie stellt den Bauplan für alle Panzer im Spiel bereit

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.

plhq_teaser_hbox_gelb_fotolia_RA Studio_46292813

Unser großes
Android Online-Kurs
Gesamtpaket



Weitere Infos

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 und drivingVelocity – 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 und deltaAngleCannon – 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 und ammoLoadingTimer – 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 und cannonColor – 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 und energyStart – 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.java

1.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:

  1. Deklarieren des Tank-Objekts als Membervariablen der GamePanel-Klasse.
  2. Aufrufen der initPlayersTank() Methode in der initGame() Methode der GamePanel-Klasse.
  3. Instanziieren des Tank-Objekts in der initPlayersTank() Methode der GamePanel-Klasse.
  4. Bewegen des Tank-Objekts durch Aufrufen seiner makeMove() Methode in der doOnTick() Methode der GamePanel-Klasse und prüfen auf Kollision.
  5. 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:

java spiel t5 gameobject code changes k1

Die Klasse GamePanel mit dem zusätzlich eingefügten Quellcode

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.

java spiel entwickeln projekt starten

Starten unseres Java Projekts über den Run Projekt Button

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:

java spiel t5 panzer treffer

Das Spielfenster unseres Java Spiels jetzt mit fahrenden Panzer und einem Abschuss

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:

java spiel t5 gameobject code changes k2

Die Klasse GamePanel mit dem zusätzlich eingefügten Quellcode

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.

plhq_teaser_hbox_gelb_fotolia_RA Studio_46292813

Unser großes
Android Online-Kurs
Gesamtpaket



Weitere Infos

Ihr solltet noch einmal in Ruhe den kompletten Quellcode der aufgelisteten Klassendateien unseres Java Spiels anschauen und mit eurem vergleichen:

PanzerHQ.java
GameWindow.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.

java spiel entwickeln projekt starten

Starten unseres Java Projekts über den Run Projekt Button

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.

java spiel t5 panzer steuern

Das Spielfenster unseres Java Spiels jetzt mit fahrendem Panzer den wir selbst steuern können

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

  1. Pingback: Java Spiel Programmieren Tutorial - Das Java Projekt anlegen

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.