intro Java Spiel 7_vasabii_Fotolia_70882828

Java Spiel Programmieren Tutorial – Teil 7: Die Spiellogik unseres Java Spiels implementieren


Nun wünschen wir euch viel Spaß beim siebten Teil unseres Java Spiel Programmieren Tutorials. Los geht’s!

1. Implementieren der Spiellogik in der GamePanel-Klasse

Wir werden nun die Spiellogik unseres Java Spiels implementieren. Dazu öffnen wir die Klasse GamePanel im Editor der NetBeans IDE.

Die GamePanel-Klasse ist das Zeichenbrett für die Spielobjekte unseres Java Spiels. Wir haben sie bereits in Teil 3 dieses Java Tutorials angelegt.

Sie ist außerdem für die Spiellogik unseres Java Spiels verantwortlich. Von ihr werden alle Spielobjekte verwaltet und sie berechnet zyklisch den aktuellen Spielzustand des Java Spiels.

Um die Spiellogik in derGamePanel-Klasse zu implementieren, werden wir einige Änderungen am Quellcode der Klasse vornehmen. Dazu werden wir die folgenden Arbeitsschritte ausführen:

  1. Einfügen der benötigten Import-Anweisungen – Damit wir mit Hilfe einer generischen Liste die Missile-Objekte verwalten können, müssen wir vorher die Klasse LinkedList und die beiden Interfaces List und Iterator aus der Paket java.util importieren.
  2. Deklarieren drei weiterer Membervariablen – Momentan verwenden wir Test-Variablen für die Geschosse, den Spielerpanzer und den gegnerischen Panzer. Diese Test-Variablen werden wir entfernen und stattdessen drei neue Membervariablen einfügen, die auf Instanzen der entsprechenden Spielobjekte referenzieren.
  3. Überarbeiten der initGame() Methode – In der initGame() Methode werden wir einige kleine Änderungen vor. Hauptsächlich das Anpassen des neuen Namen der Membervariablen des Spielerpanzers.
  4. Überarbeiten der createGameObjects() und initPlayersTank() Methoden – Auch diese beiden Methoden müssen überarbeitet werden. Alter Test-Quellcode muss entfernt und die neuen Membervariablen müssen initialisiert werden.
  5. Überarbeiten der doOnTick() Methode – Hier implementieren wir die Spiellogik. Was passiert wenn ein Panzer von einem Geschoss getroffen wird? Wann endet das Spiel? Wie viele Treffer kann ein Panzer einstecken?
  6. Überarbeiten der paintComponent() Methode – Hier sorgen wir dafür, dass die in den Membervariablen gespeicherten Spielobjekte auch auf der Spielfläche dargestellt werden. Zusätzlich muss alter Test-Quellcode entfernt werden.

Wie an der oberen Auflistung zu sehen ist, liegt einiges an Arbeit vor uns. Damit nichts vergessen wird, gehen wir dabei schrittweise vor.

Die folgende Änderung ist optional und muss nur von einigen Lesern befolgt werden:



Nun beginnen wir mit dem ersten Arbeitsschritt, dem Einfügen der notwendigen Import-Anweisungen.

1.1 Einfügen der benötigten Import-Anweisungen

Die Klassendatei GamePanel.java ist bereits in unserem Java Projekt vorhanden.

Wir öffnen nun die Klassendatei GamePanel.java im Editorfenster von NetBeans und fügen die markierten Zeilen in den bereits vorhandenen Quellcode im Bereich der Import-Anweisungen 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.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import static java.awt.event.KeyEvent.*;
import java.net.URL;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;

.
.
.

Wir verwenden in unserem Java Spiel eine generische Liste, um darin die Missile-Objekte komfortabel verwalten zu können. Dazu benötigen wir die beiden Interfaces List und Iterator, sowie die Klasse LinkedList.

Eine Liste ist eine Sammlung, die sich dynamisch beliebig in ihrer Größe ändern kann. Dies ist bei Reihungen (arrays) nicht der Fall, deren Größe muss beim Erzeugen vorgegeben werden und ist somit statisch.

In Java sind Standardlisten in dem Paket java.util als generische Klassen und Schnittstellen realisiert. Generische Klassen und Schnittstellen bieten die Möglichkeit, einen Datentyp für Objekte, die innerhalb der Klasse bzw. Schnittstelle verwendet werden, offen zu halten.

Bei generischen Listen ist also der Datentyp der Listenelemente frei wählbar. Daher die Bezeichnung generische Liste. Welcher Art (Datentyp) die Elemente der Liste sind, muss bei der Deklaration der Listen-Variable vorgegeben werden. Wie das erfolgt werden wir im nächsten Arbeitsschritt beschreiben.

Die folgenden Links führen zu den entsprechenden Referenzen in der Java API:

1.2 Deklarieren drei weiterer Membervariablen

Jetzt wird es etwas kompliziert, da sich in dem vorhandenen Quellcode der Klassendatei GamePanel.java noch der Test-Quellcode befindet.

Diesen müsst ihr entfernen und anschließend die markierten Zeilen einfügen, so dass euer Quellcode exakt dem unten aufgeführten Code entspricht.

Falls ihr dabei durcheinander kommt, kein Grund zur Sorge. Am Ende dieser Arbeitsschritte führen wir noch einmal den kompletten Quellcode der GamePanel-Klasse auf. Diesen könnt ihr dann in Ruhe mit eurem vergleichen und dadurch mögliche Missverständnisse ausschließen.

Nehmt nun die notwendigen Änderungen an eurem Quellcode vor, so dass dieser exakt dem unten aufgeführten Code entspricht:

GamePanel.java

.
.
.

public class GamePanel extends JPanel{
        
    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;  
    
    private Tank playersTank = null;
    private EnemyTank enemyTank = null;
    
    private List<Missile> missiles;

.
.
.

Im oberen Quellcode haben wir die drei neuen Membervariablen playersTank, enemyTank und missiles angelegt. Die ersten beiden Variablen repräsentieren den Spielerpanzer und den gegnerischen Panzer. Die dritte Variable ist eine generische Liste, welche Listenelemente vom Datentyp Missile in sich aufnimmt.

Der Datentyp der Listenelemente wird in spitzen Klammern angegeben (). Bei Arrays ist das etwas anders. Dort würde man Missile[] schreiben.

Da List nur ein Interface (Schnittstelle) ist, können wir sie nicht instanziieren. Dies ist auch gar nicht gewollt, denn dafür sind spezielle Klassen in dem Package java.util vorgesehen, welche die Schnittstelle List komplett implementieren.

Wir haben dabei die Auswahl zwischen den Klassen Vector, ArrayList und LinkedList. Welche wir davon nehmen, müssen wir an dieser Stelle, der Membervariablen-Deklaration, noch nicht entscheiden. Und genau das ist der große Vorteil von Interfaces. Wir können später festlegen welche Listenklasse unsere Liste konkretisieren soll, da alle drei mit dem Interface List kompatibel sind.

Kommen wir nun zum dritten Arbeitsschritt.

1.3 Überarbeiten der initGame() Methode

Als Nächstes werden wir den Quellcode der initGame() Methode überarbeiten.

Dabei muss hauptsächlich der neue Name der Membervariable (playersTank) anstelle der Test-Variable eingesetzt werden. Zudem muss die Anweisung für das Abfeuern eines Geschosses, in Zeile 40, angepasst werden, da die Missile-Objekte jetzt mit Hilfe einer generischen Liste verwaltet werden.

In dem unten aufgeführten Quellcode ist die Methode initGame() markiert. Der bisherige Quellcode dieser Methode muss nun durch den neuen markierten komplett ersetzt werden:

GamePanel.java

.
.
.

    public GamePanel() {        
        setFocusable(true);
        setPreferredSize(prefSize);
        
        initGame();         
        startGame();
    }
    
    
    public boolean isGameOver() {
        return gameOver;
    }

    public void setGameOver(boolean gameOver) {
        this.gameOver = gameOver;
    }
    
    private void initGame () {
        setBackgroundImage(1);
        createGameObjects();
        
        t = new Timer(20, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                doOnTick();     
            }
        });
                
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                
                switch(e.getKeyCode()) {
                    case VK_SPACE: 
                        if (playersTank.isAbleToShoot()) {
                            missiles.add(playersTank.shoot());    
                        }                            
                        break;
                        
                    case VK_DOWN:                         
                    case VK_UP: playersTank.stopTank(); break;
                        
                    case VK_LEFT:
                    case VK_RIGHT: playersTank.stopTurningTank(); break;
                        
                    case VK_W:
                    case VK_E: playersTank.stopTurningCannon(); break;
                }
            }
            
            @Override
            public void keyPressed(KeyEvent e) {
                switch(e.getKeyCode()) {                    
                    case VK_LEFT: playersTank.turnTankLeft(); break;
                    case VK_RIGHT: playersTank.turnTankRight(); break;
                        
                    case VK_UP: playersTank.accelerateTank(); break;
                    case VK_DOWN: playersTank.decelerateTank(); break;
                        
                    case VK_W: playersTank.turnCannonLeft(); break;
                    case VK_E: playersTank.turnCannonRight(); break;
                }
            }
        });
    }

.
.
.

In Zeile 40 fügen wir der generischen Liste missiles ein neues Listenelement vom Typ Missile hinzu. Dazu lassen wir den Spielerpanzer, durch Aufrufen der Methode shoot(), schießen. Dabei erhalten wir als Rückgabewert das Missile-Objekt, welches das gerade abgefeuerte Geschoss repräsentiert.

Mit Aufrufen der Methode add() auf der Listenvariable missiles wird das Missile-Objekt schließlich der generischen Liste angefügt.

Das war auch schon alles. Kommen wir nun zu dem vierten Arbeitsschritt.

1.4 Überarbeiten der createGameObjects() und initPlayersTank() Methoden

In diesem Schritt instanziieren wir die drei neu eingefügten Membervariablen playersTank, enemyTank und missiles. Außerdem initialisieren wir den Spielerpanzer.

In dem unten aufgeführten Quellcode sind die beiden zu überarbeitenden Methoden createGameObjects() und initPlayersTank() bereits markiert und enthalten den neuen Quelltext. Der bisherige Quellcode dieser Methoden muss nun durch den neuen markierten komplett ersetzt werden:

GamePanel.java

.
.
.

    private void createGameObjects() {
        if (playersTank == null) {
            playersTank = new Tank(new Coordinate(900,150), 70, 45, Math.toRadians(180), 0);
        }
        initPlayersTank();
        
        missiles = new LinkedList<>();
        enemyTank = new EnemyTank(new Coordinate(40,600), 80, 50, Math.toRadians(-20), 0, playersTank);
    }
    
    private void initPlayersTank() {        
        playersTank.setObjectPosition(new Coordinate(900, 150));
        playersTank.setMovingAngle(Math.toRadians(180));
        playersTank.setAngleCannon(0);
        playersTank.setEnergy(10);
    }
    
    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();
    }

.
.
.

In der Methode createGameObjects() ist die Zeile 11 besonders interessant. An dieser Stelle erzeugen wir ein neues Listenobjekt mit missiles = new LinkedList<>().

Als Listenklasse haben wir uns für die LinkedList-Klasse entschieden, da sie beim Löschen von Listenelementen leistungsfähiger als die ArrayList-Klasse ist.

Auf diese Weise haben wir uns erst beim Erstellen des Listenobjekts entschieden, welche konkrete Listenklasse verwendet werden soll. Wir könnten dadurch später sehr einfach auf eine andere Listenklasse wechseln, bspw. Vector oder ArrayList.

Den Datentyp der Listenelemente müssen wir an dieser Stelle nicht explizit angeben, sondern können den Diamond Operator <> verwenden. Dies ist auch absolut logisch, da wir den zu verwendenden Datentyp der Listenelemente bereits bei der Deklaration der missiles Variable vorgegeben haben.

Somit haben wir die Vorbereitung abgeschlossen und können nun im nächsten Arbeitsschritt die Implementierung der Spiellogik unseres Java Spiels beginnen.

1.5 Überarbeiten der doOnTick() Methode

In der doOnTick() Methode implementieren wir die Spiellogik unseres Java Spiels.

Die Spiellogik haben wir zum besseren Verständnis extra sehr einfach gehalten. Sie kann später von euch natürlich erweitert werden.

Die doOnTick() Methode ist für das Zeichnen der Spielfläche, durch Aufruf der repaint() Methode, das Ausführen der Spielzüge und die Spiellogik verantwortlich. Sie wird alle 20 ms automatisch vom Timer unseres Java Spiels aufgerufen.

Bisher enthält die doOnTick() Methode hauptsächlich Test-Quellcode. Diesen werden wir nun durch die Spiellogik unseres Spiels ersetzen.

Dazu ersetzen wir die komplette doOnTick() Methode des bisherigen Quellcodes mit der im unteren Quellcode aufgeführten Methode:

GamePanel.java

.
.
.

    private void doOnTick() {        
        
        for (Iterator<Missile> itMissiles = missiles.iterator(); itMissiles.hasNext();) {
            Missile missile = itMissiles.next();
            missile.makeMove();
            if (missile.getRange() <= 0) itMissiles.remove();
            
            if (playersTank.touches(missile) && missile.getRange() > 1) {              
                if (playersTank.getEnergy() > 1) {
                    playersTank.setEnergy(playersTank.getEnergy()-1);
                }
                else {
                    playersTank.setEnergy(0);
                    endGame();
                }                    
                itMissiles.remove();
            }
            if (enemyTank.touches(missile) && missile.getRange() > 1) {              
                if (enemyTank.getEnergy() > 1) {
                    enemyTank.setEnergy(enemyTank.getEnergy()-1);
                }
                else {
                    double xStart = Math.random()*prefSize.width/3;
                    double yStart = prefSize.height*1.05;
                    double enemyWidth = 70 + Math.random()*15;
                    double enemyHeight = 40 + Math.random()*15;
                    double angleStart = -80 + Math.random()*60;
            
                    enemyTank = new EnemyTank(new Coordinate(xStart,yStart), enemyWidth, enemyHeight, Math.toRadians(angleStart), 0, playersTank);
                    tanksDestroyedCounter++;
                }                    
                itMissiles.remove();
            }
        }        
        
        playersTank.makeMove();              
        
        enemyTank.makeMove();
        if (enemyTank.isTargetLocked() && enemyTank.isAbleToShoot()) missiles.add(enemyTank.shoot());
        
        repaint();
    }

.
.
.

Die Methode doOnTick() enthält jetzt die Spiellogik unseres Java Spiels. Die Spiellogik regelt die folgenden Spielsituationen:

  • Einem Geschoss geht der Treibstoff aus – Ein Geschoss wird von der Spielfläche entfernt, wenn die maximale Flugdistanz zurückgelegt wurde.
  • Der Spielerpanzer wird von einem Geschoss getroffen – Bei einem eingesteckten Treffer wird die Energie des Spielerpanzers um den Wert 1 reduziert. Falls der Wert zum Zeitpunkt des Treffers 1 betrug, wird die Panzerenergie auf 0 gesetzt und das Spiel beendet.
  • Der gegnerische Panzer wird von einem Geschoss getroffen – Wird der gegnerische Panzer getroffen, verringern wir dessen Energie um den Wert 1. Falls der Wert zum Zeitpunkt des Treffers 1 betrug, wird ein neuer Panzer erstellt, welcher den bisherigen gegnerischen Panzer ersetzt. Die Startposition und Maße des neuen gegnerischen Panzers werden zufällig festgelegt. Außerdem erhöhen wir den Wert der Variable tanksDestroyedCounter um 1.

In dem oberen Quelltext sind einige Zeilen besonders interessant. Diese möchten wir nun kurz besprechen.

Mit der for-Schleife in den Zeilen 7 bis 38 durchlaufen wir alle in der generischen Liste gespeicherten Missile-Objekte. Wir nutzen dabei das Interface Iterator, mit dessen Hilfe wir sehr komfortabel die Liste durchlaufen können.

Wir können uns mit der Anweisung itMissiles.next() das nächste Missile-Objekt holen und mit itMissiles.remove() das aktuell angefragte Element aus der Liste entfernen lassen. Ob noch weitere Elemente in der Liste vorhanden sind, können wir mit itMissiles.hasNext() erfragen.

Mit den if-Abfragen in den Zeilen 12 bis 21 und Zeilen 22 bis 37 prüfen wir, ob eine Kollision zwischen Panzerobjekt und Geschoss aufgetreten ist. Dabei nutzen wir die verbesserte Kollisionsabfrage der Klasse Tank.

Außerdem stellen wir sicher, dass das Geschoss beim Treffer nicht die maximale Flugdistanz erreicht hat. Dieser Sonderfall ist sehr tückisch, da er zum Absturz unseres Java Spiels führen würde. Es würde eine NullPointerException auftreten, da wir mit unserem Quellcode versuchen würden ein und dasselbe Geschoss zweimal aus der Liste zu löschen. Dies wird durch den zweiten Teil der if-Abfrage verhindert.

Mit den vorgenommenen Änderungen sind wir nun weit voran geschritten. Es fehlt nur noch ein letzter Arbeitsschritt.

1.6 Überarbeiten der paintComponent() Methode

Als letzten Arbeitsschritt werden wir die paintComponent() Methode überarbeiten.

An der Methode müssen zwar nur geringe Änderungen vorgenommen werden, dennoch ist es am besten den Quelltext der gesamten Methode paintComponent() mit dem unteren Quellcode zu ersetzen:

GamePanel.java

.
.
.

    @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);        
        
        for (Missile missile:missiles) {
            missile.paintMe(g);
        }

        playersTank.paintMe(g);
        enemyTank.paintMe(g);
        
        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 der paintComponent() Methode zeichnen wir die Spielobjekte. Besonders interessant ist die for-Schleife in den Zeilen 19 bis 21, mit deren Hilfe wir die Missile-Objekte auf der Spielfläche darstellen lassen.

Für das Darstellen der Geschosse wird eine erweiterte for-Schleife verwendet, womit Standardlisten sehr komfortabel durchlaufen werden können.

Mit den vorgenommenen Änderungen an der paintComponent() Methode haben wir alle sechs Arbeitsschritte ausgeführt und die Spiellogik für unsere Java Spiel vollständig implementiert.

1.7 Der komplette Quellcode der GamePanel-Klasse

Nun haben wir alle Änderungen an der GamePanel-Klasse vorgenommen. In dem unten angegebenen Quellcode ist die gesamte GamePanel-Klasse zur Kontrolle für euch aufgeführt.

Der vollständiger Quelltext der Klasse GamePanel mit markierten Änderungen:

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 java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;

public class GamePanel extends JPanel{
    
    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;  
    
    private Tank playersTank = null;
    private EnemyTank enemyTank = null;
    
    private List<Missile> missiles;
  
    
    
    public GamePanel() {        
        setFocusable(true);
        setPreferredSize(prefSize);
        
        initGame();         
        startGame();
    }
    
    
    public boolean isGameOver() {
        return gameOver;
    }

    public void setGameOver(boolean gameOver) {
        this.gameOver = gameOver;
    }
    
    private void initGame () {
        setBackgroundImage(1);
        createGameObjects();
        
        t = new Timer(20, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                doOnTick();     
            }
        });
                
        addKeyListener(new KeyAdapter() {
            @Override
            public void keyReleased(KeyEvent e) {
                
                switch(e.getKeyCode()) {
                    case VK_SPACE: 
                        if (playersTank.isAbleToShoot()) {
                            missiles.add(playersTank.shoot());    
                        }                            
                        break;
                        
                    case VK_DOWN:                         
                    case VK_UP: playersTank.stopTank(); break;
                        
                    case VK_LEFT:
                    case VK_RIGHT: playersTank.stopTurningTank(); break;
                        
                    case VK_W:
                    case VK_E: playersTank.stopTurningCannon(); break;
                }
            }
            
            @Override
            public void keyPressed(KeyEvent e) {
                switch(e.getKeyCode()) {                    
                    case VK_LEFT: playersTank.turnTankLeft(); break;
                    case VK_RIGHT: playersTank.turnTankRight(); break;
                        
                    case VK_UP: playersTank.accelerateTank(); break;
                    case VK_DOWN: playersTank.decelerateTank(); break;
                        
                    case VK_W: playersTank.turnCannonLeft(); break;
                    case VK_E: playersTank.turnCannonRight(); break;
                }
            }
        });
    }

    
    private void createGameObjects() {
        if (playersTank == null) {
            playersTank = new Tank(new Coordinate(900,150), 70, 45, Math.toRadians(180), 0);
        }
        initPlayersTank();
        
        missiles = new LinkedList<>();
        enemyTank = new EnemyTank(new Coordinate(40,600), 80, 50, Math.toRadians(-20), 0, playersTank);
    }
    
    private void initPlayersTank() {        
        playersTank.setObjectPosition(new Coordinate(900, 150));
        playersTank.setMovingAngle(Math.toRadians(180));
        playersTank.setAngleCannon(0);
        playersTank.setEnergy(10);
    }
    
    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() {        
        
        for (Iterator<Missile> itMissiles=missiles.iterator(); itMissiles.hasNext();) {
            Missile missile = itMissiles.next();
            missile.makeMove();
            if (missile.getRange() <= 0) itMissiles.remove();
            
            if (playersTank.touches(missile) && missile.getRange() > 1) {              
                if (playersTank.getEnergy() > 1) {
                    playersTank.setEnergy(playersTank.getEnergy()-1);
                }
                else {
                    playersTank.setEnergy(0);
                    endGame();
                }                    
                itMissiles.remove();
            }
            if (enemyTank.touches(missile) && missile.getRange() > 1) {              
                if (enemyTank.getEnergy() > 1) {
                    enemyTank.setEnergy(enemyTank.getEnergy()-1);
                }
                else {
                    double xStart = Math.random()*prefSize.width/3;
                    double yStart = prefSize.height*1.05;
                    double enemyWidth = 70 + Math.random()*15;
                    double enemyHeight = 40 + Math.random()*15;
                    double angleStart = -80 + Math.random()*60;
            
                    enemyTank = new EnemyTank(new Coordinate(xStart,yStart), enemyWidth, enemyHeight, Math.toRadians(angleStart), 0, playersTank);
                    tanksDestroyedCounter++;
                }                    
                itMissiles.remove();
            }
        }        
        
        playersTank.makeMove();              
        
        enemyTank.makeMove();
        if (enemyTank.isTargetLocked() && enemyTank.isAbleToShoot()) missiles.add(enemyTank.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);        
        
        for (Missile missile:missiles) {
            missile.paintMe(g);
        }

        playersTank.paintMe(g);
        enemyTank.paintMe(g);
        
        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 dem oberen Quellcode wurden die überarbeiteten Zeilen markiert. Insgesamt haben wir folgende Änderungen an der GamePanel-Klasse vorgenommen:

  • Die benötigten Import-Anweisungen wurden eingefügt.
  • Drei weiterer Membervariablen wurden deklariert.
  • Die Methoden initGame(), createGameObjects() und initPlayersTank() wurden überarbeitet.
  • Die Spiellogik wurde in der doOnTick() Methode implementiert.
  • Die paintComponent() Methode wurde überarbeitet.

In NetBeans sollte die Klasse GamePanel nun wie folgt aussehen:

java spiel gamepanel final changes

Der Quellcode der Klasse GamePanel mit allen vorgenommenen Änderungen

In der oberen Abbildung haben wir die in den Quellcode der GamePanel-Klasse die vorgenommenen Änderungen jeweils mit einem blauen Rahmen markiert:

  • A: Einfügen der benötigten Import-Anweisungen – Import der benötigten Klassen und Interfaces.
  • B: Deklarieren drei weiterer Membervariablen – Anlegen neuer Membervariablen für die Spielobjekte.
  • C: Überarbeiten der initGame() Methode – Membervariable des Spielerpanzers eingefügt.
  • D: Überarbeiten der createGameObjects() und initPlayersTank() Methoden – Initialisieren der neuen Membervariablen und Startzustand des Spielerpanzers definieren.
  • E: Überarbeiten der doOnTick() Methode – Hier wurde die Spiellogik des Java Spiels implementiert.
  • F: Überarbeiten der paintComponent() Methode – Darstellen der Spielobjekte auf der Spielfläche.

1.8 Die Klassendateien unseres Java Projekts zum Download

Unser Java Spiel ist nun schon etwas komplexer und besteht aus insgesamt 8 Klassen. 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.java
GameWindow.java
GamePanel.java
Coordinate.java
GameObject.java
Missile.java
Tank.java
EnemyTank.java

Als Nächstes werden wir unser Java Spiel starten und die neu implementierte Spiellogik testen.

2. Testen der Spiellogik unseres Java Spiels

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 begegnen wir mehreren gegnerischen Panzern. Wir können den Spielerpanzer mit den Pfeiltasten steuern. Den Panzerturm können wir mit den Tasten W und E nach links bzw. rechts drehen. Geschosse werden mit der Leertaste abgefeuert.

Jeder Panzer kann mehrere Treffer einstecken, bevor er als zerstört gilt. Die verbleibende Energie wird über dem Panzer dargestellt. Pro abgeschossenen gegnerischen Panzer wird der Zähler Tanks destroyed um 1 erhöht.

java spiel spiellogik

Das Spielfenster unseres Java Spiels wir können nun gegen mehrere gegnerische Panzer kämpfen

In der oberen Abbildung ist der Spielerpanzer am linken Spielfeldrand zu sehen. Dieser hat gerade ein Geschoss abgefeuert und damit den Nachladevorgang gestartet. Dies erkennt man an dem schmalen grauen Balken über der Energieanzeige des Panzers.

Auch der gegnerische Panzer rechts hat ein Geschoss abgefeuert. Beide Panzer wurden schon mehrfach getroffen. Dies ist an dem nicht mehr vollständigen Energiebalken über den Panzern zu erkennen. Sobald der kritische Zustand erreicht wird, färbt sich der Energiebalken Rot.

2.1 Video – Funktionspräsentation unseres Java Spiels

In dem unteren Video ist das Spielfenster unseres selbst programmierten Java Spiels zu sehen. Wir sind 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.

Neu hinzugekommen ist die Spiellogik, durch die wir nun gegen mehrere Panzer kämpfen können. Auch die Abschüsse werden mitgezählt und dem Spieler unten links angezeigt.

Die neuen Funktionen unseres Java Spiels haben wir im unteren Video vorgeführt:

In dem oberen Video haben wir ein kleine Partie PanzerHQ gespielt. Es ist uns gelungen drei gegnerische Panzer zu eliminieren, bevor der Spielerpanzer den finalen Treffer einstecken musste und zerstört wurde.

In dem Video sind alle Funktionen unseres selbst programmierten Java Spiels zu sehen. Es ist natürlich noch kein vollständiges Spiel, aber dennoch schon sinnvoll spielbar.

Zusammenfassung

In der siebten Lektion unseres Java Spiel Programmieren Tutorials haben wir die Spiellogik implementiert und dabei die Klasse GamePanel überarbeitet.

Dazu haben wir den Quellcode der Klasse GamePanel an mehreren Stellen verändert und erweitern. Die notwendigen Änderungen haben wir schrittweise vorgenommen, da sie sich über den gesamten Quellcode der Klasse erstreckten.

Nachdem wir die Implementierung der Spiellogik abgeschlossen hatten, haben wir unser Java Spiel in der NetBeans IDE ausgeführt und es ausgiebig getestet.

Unser Panzer-Spiel hat nun einen fortgeschrittenen Entwicklungsstand erreicht, in welchem sich sinnvoll auf Panzerjagd begeben werden kann. Wir sind damit unserem Ziel, ein eigenes Spiel in Java zu programmieren, einen großen Schritt näher gekommen.

In der nächsten Lektion werden wir einen Dialog erstellen, mit dessen Hilfe es möglich sein wird, die Farben des Spielerpanzers zu wählen.



Comments 1

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

Schreibe einen Kommentar

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