Java cooperation home
Tutorial
type and press Enter

Ein schießendes Raumschiff

In diesem Kapitel möchte ich euch kurz erklären, wie man ein kleines Raumschiff (bzw. jeden anderen Spieler, Gegner...) dazu bringen kann Schüsse abzufeuern. Dieses Verhalten ist in Spielen wie MarsAttacks oder auch meinem Spiel J-Rio unerlässlich und obwohl das Prinzip wirklich sehr einfach zu implementieren ist habe ich in letzter Zeit immer wieder Mails bekommen, die mich danach fragten (was natürlich keine Schande ist ;-). Also, nun wollen wir uns mal den Schüssen widmen.

Das Prinzip

Wie immer möchte ich kurz auf das Prinzip und auch das Klassendesign eingehen. Java ist eine objektorientierte Programmiersprache und da ein Schuss in jedem Spiel auch sehr eigenständige Eigenschaften hat (Bewegung, Tests auf Kollisionen mit Gegener, Zerstören des Schusses...) ist es eigentlich selbstverständlich, eine eigene Klasse Shot einzuführen. Diese implementiert das Verhalten des Schusses (z. B. Bewegung), speichert seine Kooridinaten und zeichent den Schuss an der richtigen Stelle im Spiel. Ein Schuss wird im allgemeinen von einem Spieler erzeugt, also werden wir unserem Spieler eine Methode generateShot() hinzufügen, die ein Objekt der Klasse Shot erzeugt (mit den richtigen Startkoordinaten, der richtigen Schussrichtung...) und dieses Objekt an die aufrufende Klasse zurückgibt. Die aufrufende Klasse wird in meinem Beispielapplet die Klasse Main sein. Diese speichert den gerade erzeugten und von der Playerklasse zurückgegebenen Schuss in einem Array aus Shots. In der run - Methode der Klasse Main wird nun das Shot - Array durchlaufen, die Schüsse werden bewegt, zerstört, wenn sie das Spielfeld verlassen und könnten auch auf Collisionen mit Gegnern, Levelelementen... getestet werden (in meinem Beispielapplet nicht implementiert). In der paint - Methode geschieht das selbe, auch hier wird das gesammte Array durchlaufen und alle Schüsse werden gezeichnet.
Wenn ihr diesen Absatz und die dort dargestellten Zusammenhänge verstanden habt, dann braucht ihr eigentlich garnicht mehr weiterlesen. Denn die dazugehörigen Klassen sind wirklich extrem einfach und tun nichts Außergewöhnliches. Geübte Javaprogrammierer können sich also nun den Sourcecode runterladen und gleich mit eigenen Spielen loslegen. Für alle anderen, die Interesse haben, werde ich noch kurz auf die wichtigen Punkte in den einzelnen Klassen eingehen.

Die Klasse Shot

Wie schon erwähnt speichert diese Klasse die Kooridinaten des Schusses, verfügt über eine moveShot - Methode, die den Schuss in y - Richtung bewegt, sowie über eine drawShot() - Methode, die den Schuss zeichnet. Alle Methoden sind denkbar einfach, hier kommt nochmal der Sourcecode:

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

    public class Shot
    {
      // Variablen
      private int x_pos;
      private int y_pos;

      // Schussradius
      private final int radius = 3;

      // Konstruktor
      public Shot(int x, int y)
      {
        x_pos = x;
        y_pos = y;
      }

      // liefert die y - Position zurück
      public int getYPos()
      {
        return y_pos;
      }

      // bewegt den Schuss in y - Richtung
      public void moveShot(int speed)
      {
        y_pos += speed;
      }

      // zeichnet den Schuss
      public void drawShot(Graphics g)
      {
        g.setColor(Color.yellow);
        g.fillOval(x_pos, y_pos, radius, radius);
      }
    }

Die Klasse Player

Auch die Klasse Player ist denkbar einfach. Auch sie verfügt über eine move - Methode (Bewegung in x - Richtung), eine draw - Methode und speichert die Kooridinaten des Schiffes. Die eigentlich interessante Methode ist die generateShot - Methode. Hier also nochmal kurz der Sourcecode:


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

    public class Player
    {
      // Variablen
      private int x_pos;
      private int y_pos;

      // Konstruktor
      public Player(int x, int y)
      {
        x_pos = x;
        y_pos = y;
      }

      // Bewegt das Raumschiff in x - Richtung
      public void moveX(int speed)
      {
        x_pos += speed;
      }

      // Erzeugt einen Schuss an der aktuellen Position des Schiffes
      //und liefert diesen zurück
      public Shot generateShot()
      {
        Shot shot = new Shot(x_pos, y_pos);

        return shot;
      }

      // zeichnet den Spieler
      public void drawPlayer(Graphics g)
      {
        g.setColor(Color.red);
        int [] x_poly = {x_pos, x_pos - 10, x_pos, x_pos + 10};
        int [] y_poly = {y_pos, y_pos + 15, y_pos + 10, y_pos + 15};
        g.fillPolygon(x_poly, y_poly, 4);
      }
    }

Die Klasse Main

Und zum Abschluss nochmal die Klasse Main. Ich habe alle, für die Erzeugung und "Verwaltung" der Schüsse unwichtigen Teile (Doppelpufferung...) aus dem Sourcecode hier entfernt (die Abschnitte, in denen etwas fehlt sind mit "..." gekennzeichnet). Diese Teile findet ihr dann im Sourcecode, den ihr runterladen könnt.

    import java.applet.*;
    import java.awt.*;

    public class Main extends Applet implements Runnable
    {
      // Variablen
      ...
      private Player player;
      private Shot [] shots;

      // Konstanten
      private final int shotSpeed = -2;
      ...

      // double buffering
      private Image dbImage;
      private Graphics dbg;

      public void init()
      {
        ...
        // Erzeugen des Schussarrays
        shots = new Shot[5];
      }

      ...

      public void run ()
      {

        while (true)
        {
          // Durchlaufe das Schussarray, bewege die Schüsse und
          // teste, ob ein Schuss das Spielfeld verlassen hat.
          // Hier könnten auch weitere Tests folgen (z. B. nach
          // Kollisionen mit Gegnern...
          for(int i=0; i<shots.length; i++)
          {
            if(shots[i] != null)
            {
              // Schüsse bewegen
              shots[i].moveShot(shotSpeed);

              // Testen ob der Schuss das Spielfeld verlassen
              // hat, wenn ja, lösche ihn aus dem Array
              if(shots[i].getYPos() < 0)
              {
                // Schuss aus dem Array löschen
                shots[i] = null;
              }

              // Weitere Operationen einfügen
              // ...
              // z. B. testen nach Kollisionen...
              // ...
            }
          }

          // Spieler bewegen
          ...

          ...
        }
      }

      public boolean keyDown(Event e, int key)
      {
        ...

        else if(key == 32)
        {
          // neuen Schuss generieren und im Array speichern
          for(int i=0; i<shots.length; i++)
          {
          // nur dann im Array speichern, wenn ein Platz frei ist
            if(shots[i] == null)
            {
              shots[i] = player.generateShot();
              // break, um Schuss nur einmal zu speichern
              break;
            }
          }
        }

        ...
      }

      public void paint (Graphics g)
      {
        // Spieler zeichnen
        ...

        // Schüsse zeichnen
        for(int i=0; i<shots.length; i++)
        {
          if(shots[i] != null)
          {
            shots[i].drawShot(g);
          }
        }
      }
    }

Zusammenfassung

Also, wie ihr euch nun hoffentlich überzeugen konntet ist das Ganze wirklich extrem einfach. Die verwendete Technik, nämlich das Speichern der Schussobjekte in einem Array und durchlaufen des Arrays in jedem run - Methoden - Aufruf und die dortige Anwendung von move... - Methoden auf die gespeicherten Objekte, ist eine wichtige Technik und kann auf viele andere Situationen übertragen werden. Ich hoffe, dass ich euch mit diesem Tutorial ein wenig helfen konnte, viel Spaß weiterhin und ihr findet den Sourcecode und das Beispielapplet wie immer am Ende dieses Kapitels (also zwei Zeilen tiefer ;-).

SourceCode download (*.zip - Datei)
Applet ansehen

Nächstes Kapitel

Grundlagen eines Platfrom - Games
Fabian Birzele, 2001-2004.
web-design: Vadim Murzagalin, 2004.