Ihren ersten Ausflug in die Welt der Spieleprogrammierung erlebten Sie in Ausgabe 11/2012 an Hand der Kreation des Apple Shooters. Heute wagen wir uns noch ein Stück weiter: Mit Hilfe des App Game Kits erschaffen wir einen Clone des großen Spieleklassikers Sokoban.
Quellcode zum Download
Den Quellcode zum hier vorgestellten Spiel finden Sie unter [5] auf der Android-User-Homepage zum Download.
Das Original Sokoban erschien 1982 in Japan. Als Spektrum Holobyte 1984 eine europäische Variante veröffentlichte, wurde es prompt ein Hit (Abbildung 1 zeigt das Original von damals in einem Dos-Emulator). Noch heute erscheinen Freeware-Adaptionen und Fans tauschen sich im Internet über die Levels von damals aus. Auf den ersten Blick mutet die Idee dahinter erst einmal simpel an: In einem Lagerhaus, welches der Spieler aus der Vogelperspektive sieht, muss er einem kleinen Mann beim Aufräumen helfen. Dazu verschiebt die Figur herumliegende Kisten, so dass sie alle zum Schluss auf gekennzeichneten Ablagefeldern liegen. Doch es gibt ein paar Einschränkungen, wodurch besonders die schwierigeren Levels sehr knifflig werden können: Kisten können nur geschoben, nicht gezogen werden. Das Verschieben ist aber auch nur möglich, wenn sich direkt dahinter ein freies Feld befindet; Mauern oder weitere Holzkisten blockieren also möglicherweise den Weg. Wenn der Spieler einen Fehler macht, und zum Beispiel eine Kiste in einer Ecke einklemmt (von wo sie nur noch durch Ziehen wegzubekommen wäre, was ja nicht möglich ist), muss er auf Knopfdruck den Level neu starten. Diese Knobelei wurde derart beliebt, dass ihre Fans sie im Laufe der Zeit auf etliche Systeme portierten. Doch wie programmieren wir nun unseren eigenen Sokoban-Clone?
Planungsphase
Zunächst gilt es, ein paar Vorüberlegungen zu treffen. Was für einen Aufbau soll der spätere Bildschirm haben? Im unteren Bereich sollte die App Pfeiltasten-Buttons einblenden, mit denen der Spieler die Figur bewegen kann. Andere Steuermöglichkeiten – wie beispielsweise der Geschwindigkeitssensor – passen zwar zu Renn- und Actiongames, wären aber für eine Knobelei wie diese, bei der es um gezielte, wohlüberlegte Züge geht, eher unpassend. Für den Fall, dass sich der Nutzer in eine verfahrene Situation hineinmanövriert hat, ist ferner ein Level neu starten-Button wichtig. In die obere Hälfte des Bildschirms platzieren wir das eigentliche Spielfeld. Dieses besteht bei Sokoban normalerweise aus einem Raster, innerhalb dessen die einzelnen Spielelemente zeilen- und spaltenweise angeordnet werden. Wieviele Elemente passen später in diesen Bereich hinein? Setzen wir die neueren Smartphone-Modelle als Norm, dann ist der Bildschirm im Portrait-Modus oft 720 Pixel breit und 1280 Pixel hoch (falls die App auf einem Modell mit anderer Auflösung ausgeführt wird, skaliert das App Game Kit [1] dank des Kommandos SetVirtualDisplay(720, 1280)
das Bild später trotzdem so, dass es richtig angezeigt wird). Wenn wir für unsere Sprites Grafiken verwenden, die jeweils 60 Pixel breit und hoch sind, wäre dementsprechend eine Matrix von 11 Zeilen zu je 11 Spalten eine gute Wahl: Das Spielfeld benötigt dann 11*60 = 660 Pixel in der Breite (es bleibt also noch etwas Platz für einen seitlichen Rahmen) und Höhe (so dass die untere Hälfte des Schirms frei bleibt für die Steuerungsbuttons). Schauen Sie, um sich diese Aufteilung nocheinmal zu vergegenwärtigen, am besten einmal vorweg den Screenshot unseres späteren, fertigen Spiels an (Abbildung 2).

Nun benötigen wir als letzte Vorarbeit noch nett anzuschauende Sprite-Grafiken für unser Spiel: einen Lagerarbeiter, die Holzkisten, die Mauern und den Boden der Lagerhalle. Beim Apple Shooter in Heft 11/2012 [2] griffen wir dazu noch auf die Seite Opengameart [3] zurück – doch dort finden sich für dieses Projekt keine passenden und gleichzeitig ansprechenden Bilder. Dafür springt diesmal Openclipart [4] ein. Die meisten der dort verwendeten Werke stehen unter der Public Domain / CC0 – Lizenz, Entwickler dürfen diese also ohne irgendwelche Auflagen auch in eigenen Spielen verwenden. Sie können als Vektorgrafiken heruntergeladen oder – in unserem Falle interessanter – direkt online in eine Bitmap-Grafik mit einer wählbaren Wunschgröße (wie etwa 60 Pixel) exportiert werden. Auf der besagten Homepage finden sich auch taugliche Bilder für Steuerungsbuttons; der Erstellung der Sprites für den Boden und die Zielfelder sowie ein paar wenige graphische Anpassungen lassen sich schnell mit einem Malprogramm wie Gimp durchführen. Die fertigen Grafiken (sowie auch den kompletten Quellcode des folgenden Abschnitts) finden Sie als Downloadlink mittels des QR-Codes am Ende von diesem Artikel.

Die konkrete Umsetzung
Das Listing des Spiels ist aus Platzgründen nicht im Heft abgedruckt, sondern stattdessen in dem oben erwähnten Downloadarchiv enthalten. Für ein gutes Verständnis des Quellcodes rufen Sie diesen optimalerweise an ihrem Computer auf, und legen das Android-User-Heft mit den Erklärungen daneben.
Der Anfang des Programms (Zeile 1-46) führt die üblichen Aktivitäten durch, welche nach dem Neustart einer App anfallen: Er initialisiert Variablen, setzt diese auf bestimmte Startwerte und lädt Grafiken in den Speicher. Doch hier fällt schon ein erster Unterschied gegenüber dem Apple Shooter aus dem letzten Workshop auf: Die Sprite-Grafiken des Spielfeldes werden über ein Array angesprochen (Zeile 30-35). Dabei handelt es sich um eine durchnummerierte „Ansammlung“ an Variablen. Statt jeder Variable einen eigenen Namen zu geben, kann der Entwickler auf diese mit Zahlen (Indexwerten) zugreifen. Unser Initialisierungsteil macht jedoch zunächst einmal nichts anderes, als jedes Element dieses Arrays mit den Befehlen „For..Next“ in einer Schleife zu durchlaufen. Hierbei wird erstmal jede dieser Variablen mit dem Datentyp Sprite assoziiert.
Ein Array muss vor seiner erstmaligen Verwendung immer in seiner Größe benannt und entsprechend viel Speicher reserviert werden (sogenanntes „Dimensionieren“). Die korrespondierende Anweisung aus Zeile Nr. 10 lautet: Dim Sprites[12,12]
, was bedeutet, dass ein Array mit dem Namen „Sprites“ angelegt wird. Dieses soll zwei Dimensionen (für unsere Zeilen und Spalten) enthalten und jede davon 12 Elemente groß sein. Warum 12, wenn wir für unser Spielfeld nur 11 benötigen? Das App Game Kit fängt schon bei Element 0 zu zählen an. Wir werden aus Gründen der Quellcode-Übersichtlichkeit später das nullte Element nicht benutzen, sondern unser Array immer nur über die Zahlen 1-11 ansprechen. Element Nr. 0 ist trotzdem vorhanden, weswegen es bei der Größe unseres Datenfelds mitgezählt werden muss.
Zeile 49-51 ruft nun die Unterprogramme (Funktionen) „LevelNeuLaden()“ und „LevelNeuZeichnen()“ auf. Für die Umsetzung von diesen muss sich der Entwickler nun erstmal darüber im Klaren sein, wie er die Inhalte seiner Level (also welche Spielfigur wo steht) intern speichert (sogenanntes „Kodieren“). Wir werden dafür wie folgt vorgehen: Ein (diesmal) eindimensionales Array aus 11 Elementen speichert ebensoviele Zeichenketten, welche jeweils eine Zeile des Spielfeldes repräsentieren. Jede einzelne Zeile umfasst wiederrum 11 Zeichen, von denen jedes den Inhalt einer Spielfeld-Spalte speichert. Schauen Sie sich hierzu zum besseren Verständnis Abbildung 5 und (als Beispiel) Zeile 215 an: Leveldaten$[8] = "# $ ..#"
Hier nutzen wir willkürliche Zeichen als Platzhalter für Spielinhalte. Dabei verwendet diese App zu internen Speicherung #
als Kodierung für eine Mauer, $
für eine Kiste und .
für das Zielfeld einer solchen Truhe. Die achte Zeile dieses Levels beschreibt also, dass in dieser Reihe links und rechts Mauern entlang laufen, relativ mittig eine Kiste herumliegt und sich vor der rechten Mauer zwei Zielfelder befinden. Die Funktion LevelNeuLaden() definiert auf diese Weise die Inhalte des Levels, während LevelNeuZeichnen() die Befehle enthält, um an Hand der gewählten Kodierung die richtigen Sprite-Grafiken für das Spielfeld zu setzen. Sie werden sich nun fragen, warum für die interne Speicherung der Levelinhalte nicht ebenfalls ein zweidimensionaler Array verwendet wird (wie vorher bei der Speicherung der Verweise auf die Bilder), sondern ein eindimensionaler, welcher Zeichenketten nutzt? Dies hat rein pragmatische Gründe: Auf die Weise kann der Entwickler leichter neue Levels entwerfen, indem er einfach neue Zeichenketten für weitere Levels in die Funktion LevelNeuLaden() hineinschreibt.

Zeile 53-79 lässt der Initialisierungsphase nun die Hauptschleife des Spiels folgen: So lange die App läuft, wird immer wieder erneut überprüft, ob der Nutzer den Bildschirm berührt hat. Falls ja: Ist einer der Pfeilbuttons davon betroffen? Dann rufe das Unterprogramm SpielerBewegungAngefordert(ZielPositionX,ZielPositionY)
auf (Zeile 60-71). Ist stattdessen der „Level Neu starten“-Button gedrückt worden? Dann überschreibe mit Hilfe des Unterprogramms LevelNeuLaden() den aktuellen Inhalt von Leveldaten$[] mit der Ausgangssituation dieses Levels.
Was passiert nun, wenn der Nutzer eine Bewegung der Spielefigur „angefordert“ hat? Die Funktion SpielerBewegungAngefordert(ZielPositionX,ZielPositionY)
(Zeile 82-92) überprüft, ob es der Spielfigur überhaupt möglich ist, sich dort hin zu begeben. Dafür wird aus Leveldaten$ das Zeichen abgerufen, welches die Zielposition repräsentiert. Handelt es sich dabei um ein Leerzeichen, also dem, was in unserer Kodierung einem leeren Feld entspricht? Kein Problem, dann rufe das Unterprogramm BewegeSpieler(ZielPositionX,ZielPositionY)
(Zeile 95-111) auf. Dieses verschiebt das Spielerzeichen in Leveldaten$ entsprechend, der anschließende Aufruf von LevelNeuZeichnen() bringt die zunächst nur in der internen Speicherung vorgenommenene Änderung auch auf den Bildschirm.
Nun kann es vorkommen, dass der Spieler eine Bewegung zu einem Zielfeld anfordert, auf dem eine Kiste steht – er will diese also offenbar verschieben. In dem Fall prüft nach einem entsprechenden Aufruf die Funktion KistenBewegungAngefordert(QuelleX,QuelleY,ZielX,ZielY)
erst einmal, ob die Kiste in diese Richtung verschiebbar ist, oder ob diese durch eine in dieser Bewegungsrichtung dahinterstehende Wand oder zweite Kiste blockiert wird. Ob das erwünschte Verschieben von Erfolg gekrönt ist, erfährt das Unterprogramm SpielerBewegungAngefordert
über den Rückgabeparameter von KistenBewegungAngefordert
, und kann somit gegebenenfalls der verschobenen Holzkiste die Spielfigur hinterherfolgen lassen (siehe hierzu die Zeilen 88-89 und 113-121).
An welcher Stelle überprüft die App nun, ob schon alle Kisten auf Zielfeldern stehen – dass aktuelle Rätsel also gelöst ist? Dies wird innerhalb der Funktion „LevelNeuZeichnen“ realisiert. Jedes Mal, wenn ein anderer Teil des Codes dieses Unterprogramm aufruft, wurde gerade etwas an den Inhalten des Spielfelds geändert – also hat die Spielfigur potentiell gerade eine weitere Kiste auf ein Zielfeld geschoben. Dies lässt sich daran prüfen, ob im Leveldaten$-Array nirgendwo mehr die Kodierungszeichen für unbelegte Zielfelder auftauchen (also das Kodierungszeichen „.“ für ‚Leeres Zielfeld‘ oder „+“ für ‚Zielfeld, auf dem zwar der Spieler, aber keine Kiste steht‘). Ist dem so, dann lädt die App nun das nächste Rätsel. Außer dies war schon das letzte Level – dann startet das Spiel wieder komplett von vorne bei Level Nr. 1 (siehe dazu die Zeilen 139, 154-156, 164-168 & 173-180).
Leveldesign & Erweiterungsmöglichkeiten
Wenn Sie bereits über Erfahrungen mit anderen Programmiersprachen verfügen, dann war die Datenstruktur „Array“ für Sie ein alter Bekannter, auch die hier genutzte Ablaufsteuerung sollte Ihnen im Groben aus dem funktionalen Programmierparadigma bekannt sein. Ein großer Unterschied zu vielen anderen Sprachen besteht beim App Game Kit jedoch darin, dass keine Befehle existieren, um Grafiken unmittelbar auf den Bildschirm zu zeichnen. Alles läuft indirekt über Sprites.
Wenn der Entwickler trotz dieses Konzepts kein Plattformspiel, sondern ein klassisches „Tiled Game“ (also ein aus gleichgroßen Kacheln zusammengesetztes Spielfeld) realisieren möchte, ist es effizienter, nicht jedesmal eine Unmenge an Sprites während des Spielablaufs zu verschieben, sondern wie hier geschehen, jeweils ein Spiefeld durch ein Sprite darzustellen, alle Sprites an fixen Positionen zu belassen und bei Änderungen im Spielfeld nur die Sprite-Grafiken an den jeweiligen Positionen mittels SetSpriteImage()
auszutauschen. Wenn Sie möchten, können Sie jedoch auch als Übung überlegen, wie eine alternative Sokoban-Implementation an Hand der Hausmittel des App Game Kits aussehen könnte.
Sind Sie noch Programmieranfänger und haben bisher nicht vielmehr vollzogen als den Workshop zum Apple-Shooter aus Heft 11/2012 ? Dann haben Sie bei den letzten fünf Sätzen vermutlich nur Bahnhof verstanden – doch die sind derzeit für Sie auch nicht von Belang. Versuchen Sie zu verstehen, was es damit auf sich hat, Levelinhalte und aktuelle Spielzustände in Arrays und Zeichenketten zu „kodieren“. Fangen Sie dabei mit kleinen „Häppchen“ an (genauso wie bei Lebensmitteln ist der Wissenserwerb bekanntlich in kleinen Portionen leichter und nachhaltiger konsumierbar als geballt): Verändern Sie zunächst in der Funktion LevelNeuLaden()
die Inhalte des ersten Levels, und schauen Sie sich die Veränderungen anschließend live im Spiel an.
Der nächste Schritt besteht darin, in genau dieser Funktion einen weiteren, dritten Level einzubauen. Wenn dies geklappt hat, steht eine weitere Übung an: Die Einführung eines neuen Spielelements (binden Sie dazu eine neue Grafik mit 60×60 Pixeln ein, erfinden für diese ein eigenes Kodierungszeichen, und werten dieses im Programmablauf aus). Oder wie wäre es, wenn die Spielfigur auch diagonal ziehen könnte? Dafür müssten Sie nur entsprechende Steuer-Buttons einblenden. In der Do-Loop-Hauptschleife des Spiels (Zeile 53-79) kann eine Berührung von diesen dann hierzu passende Aufrufe des Unterprogramms SpielerBewegungAngefordert(ZielPositionX,ZielPositionY)
ausführen.
Infos
- App Game Kit: http://www.appgamekit.com/
- Einsteiger-Workshop zum App-Game-Kit: https://www.android-user.de/Magazin/Archiv/2012/11/Workshop-Android-Spiele-selbst-gemacht
- Opengameart: http://opengameart.org/
- Openclipart: http://openclipart.org/
- Quellcode zum Download: https://www.android-user.de/content/download/15268/106581/file/sokoban-fuer-android-android-user-download.zip