Das Mobile-Game Dukerunner

Grundlagen der 2D-Action Spieleprogrammierung mit MIDP 2.0

Lassi Kinnunen und Kai Kramhöft

„Lade Dir das neueste Java-Spiel auf dein Handy.“ Eine Aufforderung, die täglich in der TV-Werbung zu hören ist. Wie wäre es aber, wenn man selbst Hand anlegen und seine eigenen Spiele entwickeln würde? Mit der aktuellen MIDP 2.0 hat Sun die Entwicklung von 2D-Spielen im Vergleich zu MIDP 1.0 stark vereinfacht, was exemplarisch am Action Game Dukerunner gezeigt werden soll.

Auf die Einführung immer leistungsfähigerer Mobiltelefone (auch Smartphones genannt) hat Sun Microsystems im November 2002 mit MIDP 2.0 (Mobile Information Device Profile) reagiert. MIDP ist ein Profil der J2ME und speziell auf die Fähigkeiten kleiner mobiler Endgeräte wie Mobiltelefone ausgelegt. Mit dem Schritt zu 2.0 wird man den neuen Eigenschaften und Fähigkeiten der Handys (höhere Prozessortaktung und Display-Auflösung, verbesserte Soundwiedergabe/polyphoner Klang) gerecht. Die meisten Hersteller bieten inzwischen Handys mit MIDP 2.0 an. Diese sind im Markt jedoch noch nicht so verbreitet wie MIDP 1.0-Telefone. Zukünftig wird die Version 2.0 sicherlich auf den meisten neuen Smartphones zu finden sein. Die MIDP 1.0 Plattform hat aber auch weiterhin ihre Berechtigung für Low-Budget-Telefone.
Aber starten wir mit dem eigentlichen Thema. Eine der wesentlichen Erweiterungen in MIDP 2.0 wendet sich an Spieleprogrammierer. So gibt es in der aktuellen MIDP-Version ein Game API, welches die Entwicklung von 2-D-Spielen beschleunigen und vereinfachen soll. Die fünf neuen Klassen des Game API befinden sich im Paket javax.microedition.lcdui.game.

  • Die GameCanvas-Klasse ist eine spezialisierte Version der Canvas-Klasse. Sie vereinfacht die Eingabeverarbeitung (z.B. Tastenfeld des Handys) und den Bildaufbau von Grafiken.
  • Die Layer-Klasse ist die Basisklasse von Ebenen in der Spielgrafik (Layern). Zwei Arten von Layers werden in MIDP 2.0 unterstützt: Sprites und TiledLayer.
  • Die Sprite-Klasse ist von der Layer-Klasse abgeleitet und dient der Animation und Transformation von Spielfiguren.
  • Die TiledLayer-Klasse ist ebenfalls ein Kind der Layer-Klasse. Ein Hintergrundbild bei einem Spiel kann aus gleich großen Grafiken (so genannten Tiles) zusammengesetzt werden. Dafür wird ein TiledLayer verwendet.
  • Die LayerManager-Klasse verwaltet die einzelnen Layer (Ebenen) und sorgt dafür, dass diese auf dem Display gezeichnet werden.

Im Folgenden soll nun überprüft werden, ob das Game API und andere neue Features die Spieleprogrammierung vereinfachen und nicht nur auf dem Papier einen guten Eindruck hinterlassen. Dazu dient das Action Game Dukerunner, das im Laufe des Artikels entwickelt wird.

Was ist Dukerunner?
Dukerunner ist ein 2D-Action-Spiel und ist an den Achtzigerjahre-Klassiker „Lode Runner“ der Spieleschmiede Broderbund angelehnt. In Dukerunner muss sich der Spieler mit seiner Hauptfigur „Duke“ durch ein Höhlensystem kämpfen, Gold einsammeln und dabei den bösen „Micros“ aus dem Weg gehen. Ist alles Gold aufgesammelt, öffnet sich ein Tor, durch das Duke in den nächsten Level kommt. Um sich zu verteidigen, kann Duke Löcher in die Erde graben und seine Gegner darin begraben. Aber nicht überall gibt es Erde. Das Spiel hat bisher vier Level. Eigene Level können leicht erstellt werden. Kleine Anmerkung für Historiker: Das Original „Lode Runner“ war wahrscheinlich das erste Spiel, das mit einem Level-Editor ausgerüstet war.
Hauptrolle bei Spielen: GameCanvas

Basis der Programmierung von Spielen mit MIDP 2.0 bildet die GameCanvas-Klasse. Durch zwei neue Eigenschaften vereinfacht sie die Spieleprogrammierung. Tastendrücke können nun direkt an den Stellen im Programm abgefragt werden, an denen sie gebraucht werden, was zu einer klareren Struktur des Programmcodes führt. Somit können einzelne Funktionen im Spiel linearer programmiert werden. Bei der Canvas-Klasse (bekannt aus MIDP 1.0) musste hierzu noch die Methode keyPressed() implementiert werden. Im GameCavas reicht der Aufruf von getKeyStates() an beliebiger Stelle, um den Status der Tasten abzufragen. Dabei werden Tastendrücke auf die entsprechenden Steuerungstasten (Game Keys) abgebildet. Folgendes Beispiel zeigt, wie der keyState geholt und danach überprüft wird, ob die Feuertaste gedrückt wurde. Für einige Situationen im Spiel muss aber weiterhin eine keyPressed-Methode implementiert werden.

int keyStates = getKeyStates();
if ((keyStates & FIRE_PRESSED) != 0) {
    // Lass Duke ein Loch graben
}

Ein Nachteil dieser Methode ist, auf bestimmte Tasten festgelegt zu sein. Eine freie Konfiguration der Steuertasten des Spiels (beispielweise durch ein Konfigurationsmenü innerhalb des Spiels) ist nur eingeschränkt realisierbar.
Auch die Grafikprogrammierung wird durch MIDP 2.0 einfacher. Der Grafikkontext ist nun von jeder Stelle des Programms und nicht wie bei einem Canvas nur in der paint-Methode verfügbar. Es reicht ein Aufruf von Graphics g = getGraphics() . Soll der Inhalt des Grafik-Kontextes auf dem Display angezeigt werden, wird einfach die Methode flushGraphics() aufgerufen. Dadurch wird der Inhalt des Hintergrundbuffer (Off-screen Buffer) auf das Display kopiert. Dabei kümmert sich das GameCanvas automatisch um das Double Buffering, um das Flackern beim Bildaufbau eines Spiels zu vermeiden.

Die Spielgrafik

Kommen wir nun zur Spielgrafik von Dukerunner. Diese wird aus einzelnen Ebenen aufgebaut. Zu den Layer-Klassen gehören die Basisklasse Layer und die davon abgeleiteten Klassen Sprite und TiledLayer. Bei Dukerunner wurden drei Ebenen benutzt. Ein TiledLayer für das Hintergrundbild des Spiels und Sprites für die Spielfiguren Duke und die gegnerischen Micros. Die Anzahl der Ebenen, die benutzt werden können, ist aber nicht begrenzt. Aus den Ebenen, die übereinander gezeichnet werden, ergibt sich dann das Gesamtbild des Spiels.
Einem TiledLayer liegt ein Bild zugrunde, welches in sich mehrere Einzelbilder enthält. Diese Einzelbilder (Tiles/Kacheln) müssen alle gleich groß sein und können kombiniert in einem Raster angeordnet werden, um ein Gesamtbild zu ergeben. Das Bild wird also wie ein Mosaik aus einzelnen Kacheln zusammengesetzt. Abbildung 1 zeigt die Einzelbilder, die für den Hintergrund von Dukerunner verwendet werden und ein Beispiel, wie ein Bild bestehend aus den Tiles aussehen kann.

Abb. 1: Tiles und TiledLayer: die Hintergrundgrafik

Zum Anlegen einer eigenen TiledLayer-Instanz werden folgende Parameter benötigt: Die Größe des Rasters, das Bild, aus dem die einzelnen Tiles extrahiert werden sollen, und die Größe der einzelnen Kacheln im Bild., wozu nur das Wissen benötigt wird, wie man ein Bild lädt.

try {
tilesImage = Image.createImage("/tiles.png");
}catch(IOException ioex) {}

Um ein TiledLayer mit der Rastergröße 32×32 und Kachelgröße von 20×20 anzulegen, muss einfach TiledLayer tileLayer = new TiledLayer(32,32, tilesImage, 20, 20); kodiert werden. In Dukerunner ist die Klasse Background von TiledLayer abgeleitet, um die Aufgaben des TiledLayer im Spiel besser zusammenzufassen. Nachdem ein TiledLayer instanziert wurde, können Kacheln im Raster platziert werden. Hierzu gibt es die Methoden setCell() und fillCell(). Die einzelnen Kacheln aus dem Bild sind durchnummeriert. Sind beispielsweise fünf Kacheln im Bild, so sind diese von links nach rechts mit 1 bis 5 bezeichnet. Die Nummer 0 bezeichnet ein leeres Tile, das nicht gezeichnet wird. In Dukerunner werden den Nummern verständlichere Konstantennamen zugeordnet (Listing 1).

Listing 1
-----------------------------------------
public interface MapElement {
  // Nicht animierte physikalische Elemente der Welt  
  public static final int NOTHING = 0; // Luft. 
  public static final int EARTH = 1;   // Erde
  public static final int LADDER = 2;  // Leiter
  public static final int GOLD = 3;    // Gold
  public static final int ROCK = 4;    // Fels
  public static final int GATE = 5;    // Tor
  public static final int FLOATING_EARTH = 6; // frei schwebende Erde
  public static final int FLOATING_ROCK = 7;  // frei schwebender Fels
  public static final int SOLID_EARTH = 8;    // solide Erde 
  public static final int SOLID_ROCK = 9;     // solider Fels
  public static final int SOLID_STEEL = 10;   // solider Stahl
}

Die setCell()-Methode hat als Parameter die Zeilen-, die Spaltennummer und die Kachelnummer. Mit dem eben Gelernten und ein wenig Kreativität lässt sich jetzt schon ein Level in Dukerunner aufbauen. Das Laden des Levels wird in der Methode loadTileLevel() der Klasse Background vorgenommen (Listing 2). Dabei wird der jeweilige Level einfach aus einer Textdatei ausgelesen. Jeder Buchstabe in der Textdatei (Abb. 2) beschreibt ein Element des jeweiligen Levels. Aufgrund der Position eines Buchstabens wird eine entsprechende Kachel im Raster des TiledLayer gesetzt. Dieses einfache Prinzip zum Level-Design ist nicht auf Dukerunner begrenzt, sondern lässt sich auf andere 2-D-Spiele übertragen. Es muss nicht immer XML sein.

Listing 2
-----------------------------------------
public void loadTileLevel(int aLevel) {
 fillCells(0, 0, GameConstant.DIM_MAP_X - 1, GameConstant.DIM_MAP_Y - 1,        MapElement.NOTHING);
 InputStream is = null;
 try {
  is = getClass().getResourceAsStream(
     "/levels/level-" + aLevel + ".map");

  if (is != null) {

   try {
    int x = 0, y = 0;
    int c = 0;

    while ((c = is.read()) != -1) {
     switch (c) {
      case 'n':
       y++;
       x = 0;
       break;

     case 'X':
      setCell(x, y, MapElement.EARTH);
       x++;
       break;
     case 'x':
       setCell(x, y, MapElement.FLOATING_EARTH);
       x++;
       break;

       ...

     default:
       x++;
    }// switch

   }// while
  } catch (java.io.IOException ex) {}
  } finally {
       is.close();
  }
 } else {
    System.out.println("Kann Map fuer Level " + aLevel + "nicht laden!");
    fillCells(0, 0, GameConstant.DIM_MAP_X - 1, GameConstant.DIM_MAP_Y - 1,
              MapElement.EARTH);// leere den Level
 }
} catch (java.io.IOException ex) {}
System.out.println("level load end : " + aLevel);
}

Abb. 2: Ein Level in Dukerunner wird als Textdatei beschrieben
Geschrieben von
Lassi Kinnunen und Kai Kramhöft
Kommentare

Schreibe einen Kommentar

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