Projects Case Study

KOMOREBI: ein Arcade-Spiel, das nur aus einem Tipp besteht

Ein schnelles Tap-Tap-Kill für Android in Godot 4.7. Acht Kapitel, Endlos-Modus, prozedurale Grafik und prozeduraler Sound, ohne Assets, ohne Werbung, ohne Internet.

Beitragsgrafik: dunkles Raster mit dem Titel des Spiels und dem App-Icon von KOMOREBI
Eigene Grafik – Haas Arts

Ein Spiel mit einem einzigen Verb#

Es gibt eine Klasse von Spielen, die auf eine einzige Geste zusammengefällt: antippen. Lädt eine Figur an der passenden Stelle, und sie zerfällt. Mehr braucht die Steuerung nicht, mehr braucht sie auch nicht.

KOMOREBI ist genau so ein Spiel, und die Designfrage war deshalb nicht „welche Mechanik baue ich“, sondern: wie viel Substanz bekommt eine Mechanik, bevor sie langweilig wird?

Die Antwort lag in der Mechanik selbst. Kombo, kritische Treffer, Mehrfach-Treffer, drei Gaben und Gegner, die unterschiedliche Aufmerksamkeit verlangen. Ein Finger, fünf Arten ihn zu belohnen – oder zu bestrafen.

KOMOREBI
Werbung keine
In-App-Käufe keine
Login / Konto keins
Tracking / Analytics keines
Internet zum Spielen nicht nötig, vollständig offline
Berechtigungen keine
Server keiner
Cloud-Speicherung keine
Sterne, Bestwerte, Kapitel-Freischaltung lokal in user://komorebi.cfg

Die Regel ist eine Zeile#

Zerstöre die Schatten, bevor das Licht erlischt.

Daraus folgt der ganze Loop: Schatten treiben aus der Dunkelheit ins Bild, man tippt sie weg, hält die Kombo am Laufen und sammelt Gaben ein, bevor die Zeit abläuft. Jedes Kapitel hat eine Soll-Zahl an Schatten und ein Zeitlimit. Erreicht oder nicht – der Screen zeigt Sterne, der nächste Versuch ist einen Tipp entfernt.

Die Zeit ist der einzige Gegner. Es gibt keine Lebensenergie, keinen Herzverlust, keine Game-Over-Sequenz mit Tod. Nur eine Uhr, die leerläuft.

Fünf Gegner, fünf Arten Aufmerksamkeit#

Der eigentliche Inhalt des Spiels sind nicht die Kapitel, sondern die Gegner. Jeder verlangt eine andere Sorte von Blicken:

Gegner Verhalten Punkte Was ihn interessant macht
Schatten treibt langsam 10 Ein Tipp, und er zerfällt
Funkenleiter zickt, stößt in kurzen Schüben 16 muss abgewartet werden
Splitter treibt langsam 20 zerfällt beim Tod in drei Scherben (je 6)
Panzerschatten sehr langsam 30 drei Treffer; beim Tod eine Sprengladung, die alle Nachbarn mitnimmt
Großer Schatten pendelt zur Mitte 150 Kapitel-Boss mit 6–8 zu löschenden Kerzen

Der Panzerschatten ist das Beispiel für eine Regel, die weitergreift: Seine Sprengladung macht Nachbarn zu Bonusmaterial. Wer ihn im dichten Feld killt, räumt auf – wer ihn in der Ecke killt, verliert nur die eigenen Punkte. Das Verhalten ist damit nicht nur Kosmetik, sondern eine echte Entscheidung.

Kombo, Kritisch, Mehrfach#

Drei Mechaniken tragen das Scoring, und alle drei hängen an derselben Frage: war das letzte Drittel einer Sekunde gut?

Kombo. Jeder Treffer ohne Fehlgriff erhöht die Kombo, mit einem Fenster von 2,6 Sekunden. Ab vier Treffern steigt der Multiplikator Schritt für Schritt bis ×9. Ein Fehlgriff bricht die Kette – nicht den Lauf, aber das Momentum. Genau das ist der Unterschied zwischen einem weiteren Treffer und einem lohnenden.

Kritisch. Mit wachsender Kombo werden Treffer häufiger kritisch: doppelte Punkte, weißer Blitz, Goldring und eine kurze Zeitlupe. Die Belohnung ist nicht nur Zahl, sie ist spürbar.

Mehrfach. Mehrere Schatten innerhalb von 0,3 Sekunden ergeben einen Bonus – und ein sichtbares „MEHRFACH x3“ auf dem Bildschirm. Gerade das ist in einem Spiel ohne Maus wichtig: ein Treffer, den man nicht sieht, ist ein Treffer, den man nicht wiederholt.

Gaben, die den Takt ändern#

Gabe Farbe Wirkung
Sonnenstich bernstein 5 s doppelte Punkte
Reif cyan alle Gegner fliegen deutlich langsamer
Kette mint die nächsten 6 Töte zünden durch und reißen Nachbarn mit

Reif ist die interessanteste, weil sie nichts gibt, sondern das Spiel verlangsamt. In einem Arcade-Spiel, in dem die Zeit ohnehin gegen einen läuft, ist „weniger Druck“ eine rare und starke Gabe.

Acht Kapitel und ein Endlos-Modus#

# Name Soll Zeit Neu
1 Erster Schein 14 45 s Schatten
2 Glutflug 20 45 s Funkenleiter
3 Splitternebel 22 45 s Splitter
4 Panzerlicht 24 45 s Panzerschatten
5 Dämmerung 28 48 s alles, dichter
6 Kagura 18 + Boss 55 s Großer Schatten (6 Kerzen)
7 Nebelsturm 32 48 s härteste Mischung
8 Großer Schatten 22 + Boss 60 s Endgegner (8 Kerzen)

Die Kurve beginnt mit einem Gegnertyp und einem kleinen Soll, damit der erste Treffer überhaupt landet, und endet mit dem Endgegner. Jedes Kapitel vergibt bis zu drei Sterne über Score-Schwellen: ein Stern ist das sichere Abschaffen, drei Sterne verlangen eine gehaltene Kombo. Das ist eine bewusste Abstufung – die meisten Spieler werden einen Stern holen, und wer drei will, muss die Kette spielen, nicht nur tippen.

Dahinter wartet der Endlos-Modus: 60 Sekunden, eine einzige Frage – wie weit kommst du.

Kein Fremd-Asset, kein Audio-Asset#

Bei den bisherigen Spielen habe ich als einzigen Fremdbestandteil eine Schrift verwendet. Bei KOMOREBI bleibt es bei genau einem:

Grafik ist vollständig prozedural. Gegner, Partikel, Ringe, Blitze, der animierte Dämmerungs-Hintergrund, Sterne und das Logo werden zur Laufzeit gezeichnet. Kein PNG im Projekt, das auf eine DPI verloren gehen könnte, keine Ladezeit, keine fehlenden Assets.

Sound ist vollständig prozedural. Alle 15 Effekte werden beim Start aus reinen Funktionen in AudioStreamWAV-Puffer gerendert. Kein Audio-Asset, keine Abhängigkeit, und – das ist der eigentliche Grund – dasselbe Ergebnis auf jedem Gerät.

Die Schrift ist Inter (SIL OFL 1.1). Eine einzige Ausnahme, dokumentiert in assets/fonts/README.md.

Regeln und Darstellung getrennt#

Die technische Entscheidung, die ich am meisten schätze: arena.gd weiß nichts über Punkte oder Level, game_screen.gd weiß nichts über Treffer-Geometrie. Beide kommunizieren über Signale.

Das klingt trocken, hat aber einen konkreten Effekt. arena.gd ist headless lauffähig und wird es auch – deshalb kann das Spiel sich selbst durchspielen:

godot --headless --path . -- --autoplay=6       # bis Kapitel 6
godot --headless --path . -- --autoplay=endless # Endlos-Modus
godot --headless --path . -- --notap            # Game-Over-Pfad
godot --headless --path . -- --maxsec=45        # Zeitlimit

Ein Spiel, das sich selbst durchspielen kann, braucht für Logik, Persistenz und Levelwechsel keinen Emulator. Kein Android, kein GL-Kontext, kein Gerät.

Testbarkeit als Feature#

Der zweite Teil ist der unangenehmere, der mehr gekostet hat. Ein automatisierter Gameplay-Test auf dem Emulator, der weiß, wo die Knöpfe sind.

Der naïve Ansatz wäre, Screenshots per Farb-Heuristik zu lesen. Der funktioniert nicht, weil ein Tipp zeitlich schwer zu treffen ist. Die Lösung: Das Spiel schreibt Knopf- und Gegenpositionen inklusive Skalierung nach Logcat. Der Test liest diese Werte, statt zu raten – und prüft Screenshots als zweite, unabhängige Quelle.

# ENEMY_POS meldet viermal pro Sekunde ein vollständiges Bild
# aller lebenden Figuren – Spawn-Zeilen allein sind zu alt,
# weil die Gegner driften.

Genau dieser Satz hat Stunden gekostet. Ein Test, der nur Spawn-Zeilen auswertet, prüft eine Welt, die es nicht mehr gibt.

Der Test umfasst rund 40 Prüfschritte: App-Start und Titelbildschirm inklusive Bildhelligkeit, Hauptmenü, Levelauswahl, Kapitel-Intro, HUD, Pause und Fortsetzen, Mehrere Treffer mit Bildbeleg, Kombo-Aufbau, der Nachweis dass ein Fehlgriff den Score nicht verändert, Gaben und ihre Aktivierung, Kapitelabschluss mit Sternen, Levelwechsel, Game Over durch Zeitablauf, Persistenz nach App-Neustart, Endlos-Modus – und am Ende ein Logcat-Scan auf echte Fehler, Exceptions und Script-Fehler.

Screenshots und Logcat-Auszüge landen in artifacts/.

60 statt 20 Bilder pro Sekunde#

Ein Detail, das mir mehr Zeit gekostet hat als jedes Leveldesign: der Hintergrund.

Die erste Fassung zeichnete den Dämmerungsgrund mit Geometrie – die Lichtschäfte als 48 einzelne gefüllte Streifen, alles live im Frame. Auf dem Emulator kostete dieser Aufbau rund zwei Drittel der Bildzeit.

vorher:  20 Bilder/s
nachher: 60 Bilder/s

Optisch ist es identisch. Die Lösung: der Grund wird mit 30 Hz in eine Textur gezeichnet, die Lichtschäfte liegen als ein einziges texturiertes Trapez darüber statt als 48 Flächen. Zwei Änderungen, ein Faktor drei.

Ich habe es nicht gemerkt, weil es auf dem Desktop flüssig lief. Der Emulator war die richtige Investition – nicht für den Test, sondern für die Performance.

Keine Geheimnisse im Repository#

Der Release-Key liegt lokal, nicht im Repository. signing/ steht vollständig in der .gitignore, und ./tools/build_android.sh release bricht ab, solange signing/komorebi-upload.jks fehlt:

./tools/make_release_keystore.sh    # erzeugt Key + Passwortdatei
./tools/build_android.sh release    # APK + AAB bauen und prüfen

Es wird niemals ein Debug-Key als Release ausgegeben. Das Passwort wird nur für die Dauer des Exports in export_presets.cfg eingetragen und danach wieder entfernt (tools/release_keystore.py). Kein Secret-Wert gehört in eine versionierte Datei.

Für wen das nach Overkill klingt: eine verlorene .jks bedeutet, dass die App später nicht mehr signierbar ist – sofern Play App Signing den App-Key nicht selbst verwaltet. Ein Debug-Key als Release zu nehmen, ist ein Fehler, den man erst sehr spät bemerkt.

Build, Test, Release#

Ein Skript, neun Einstiegspunkte:

./tools/build_android.sh emulator   # AVD starten
./tools/build_android.sh debug      # Debug-APK, schnelle Schleife
./tools/build_android.sh build      # Release-APK
./tools/build_android.sh aab        # App Bundle
./tools/build_android.sh release    # APK + AAB, technisch geprüft
./tools/build_android.sh install    # Release-APK auf emulator-5554
./tools/build_android.sh run        # starten, Screenshot + Logcat
./tools/build_android.sh test       # vollständiger Gameplay-Test
./tools/build_android.sh analyze    # apkanalyzer / bundletool

install, run und test verwenden standardmäßig die Release-APK, weil nur sie dem Play-Artefakt entspricht. Mit KOMOREBI_TEST_APK=debug läuft die schnellere Schleife. Diese eine Umgebungsvariable ist der Unterschied zwischen einem Test, der das auslieferbare Artefakt prüft, und einem, der etwas anderes prüft.

Was ich daraus gelernt habe#

Eine Regel ist stärker als eine Feature-Liste. „Zerstöre die Schatten, bevor das Licht erlischt“ hat mehr Entscheidungen geprägt als jede Aufzählung von Mechaniken. Als Einstieg habe ich nicht „Was braucht ein Arcade-Spiel?“, sondern „Was widerspricht dieser einen Zeile?“ gestellt. Die Antworten waren das Spiel.

Prozedural ist nicht automatisch schlank. Der Hintergrund war prozedural, korrekt gezeichnet – und kostete zwei Drittel der Bildzeit. „Machen wir es prozedural“ ist eine Entscheidung über die Herkunft der Daten, nicht über deren Laufzeit.

Ein Test, der sich nicht verhalten lässt, testet Farben. Die erste Version des Gameplay-Tests war Screenshot-Heuristik und damit wertlos. Erst als das Spiel seine eigenen Positionen meldete, wurde daraus ein Test. Das ist Aufwand im Spiel – und der teuerste Teil des ganzen Projekts.

Release-Signatur ist Teil des Produkts. Kein Key im Repository, kein Debug-Key-Fallback, kein Passwort in export_presets.cfg. Drei Zeilen Skript, ein Problem weniger für immer.

Grenzen#

Es ist kein echter Play-Upload erfolgt. Die App ist gebaut und geprüft; was fehlt, sind Dinge, die kein Code erledigt: der Eintrag in der Play Console, Store-Texte, Screenshots auf echten Geräten und – falls gewünscht – ein eigener App-Key.

Der Emulator ist nicht die Wahrheit. Der Emulator auf diesem Host beendet sich unter Last gelegentlich selbst (GL-Kontext). Der Test erkennt das als Umgebungsproblem und bricht sauber ab – aber es heißt, dass die 40 Prüfschritte ihre Aussage auf einer Umgebung treffen, die man nicht kontrolliert.

Eine Eingabefalle bleibt. Godot erzeugt aus jedem Touch zusätzlich ein emuliertes Mausereignis. Eine kurze Eingabesperre direkt nach Rundenende fängt das ab. Auf echten Geräten ist das Fenster deutlich kleiner als die Latenz zwischen den Ereignissen – aber es bleibt eine Korrektur, keine Lösung.

Der Titelbildschirm ist eine Vorschau, kein Spiel. Er zeigt eine kuratierte Auswahl der Gegnerformen, drifting statt spielbar. Absicht, aber eine Einschränkung: der erste Eindruck zeigt nicht, wie sich das Spiel anfühlt.

GLES 3 heißt GLES 3. gl_compatibility läuft überall dort, wo Godot läuft – und nicht überall dort, wo eine schnelle GPU existiert. Das ist eine bewusste Wahl für Reichweite, keine für Höchstleistung.

Für ein ähnliches Projekt – ein kompaktes Android-Spiel oder Tool, das ohne Werbung, ohne Konto und ohne Server auskommen soll – gibt es den Weg über haasarts.de.

Quelle

Dieses Projekt

KOMOREBI ist Teil meiner Referenzen. Hier geht es zum Projekt auf haasarts.de.