Projects Case Study

Mini Mysteries: ein Detektivspiel ohne Zeitdruck

20 Detektivfälle in fünf Kapiteln für Kinder von 6 bis 12 Jahren. Keine Timer, keine Leben, falsche Tipps nie fatal. Kotlin, Jetpack Compose, 67 Unit-Tests.

Beitragsgrafik: dunkles Raster mit dem Titel des Spiels und dem Icon von Detektiv Milo
Eigene Grafik – Haas Arts

Beobachten ist die Fähigkeit#

Ein Detektivspiel für Kinder steht und fällt mit einer einzigen Frage: Was macht dieses Spiel, wenn das Kind nicht weiterkommt?

Nicht: Es zeigt einen Hinweis und zählt einen Fehlversuch. Sondern erklärt es freundlich, welcher Hinweis noch fehlt, und lässt es erneut versuchen. Der Unterschied zwischen diesen beiden Varianten ist der Unterschied zwischen einem Spiel, das ein Kind fünf Minuten spielt, und einem, das es eine Woche lang spielt.

Also stand am Anfang kein Level, sondern eine Liste von Dingen, die nicht drin sein werden:

  • keine Timer, keine Countdowns
  • keine Leben, keine Energie
  • keine Wartezeiten, keine täglichen Belohnungen
  • keine künstliche Frustration
  • kein falscher Tipp, der irgendetwas kostet

Milo löst durch Hinsehen#

Die Figur ist Milo, ein kleiner Detektiv mit Hut, Mantel und Lupe. Kein Superheld, kein Kämpfer. Milo löst Probleme ausschließlich durch genaues Hinsehen.

Die Kernschleife:

Szene untersuchen → Gegenstände antippen → Hinweise sammeln
   → im Notizbuch ansehen → Beweise kombinieren
   → Aussagen vergleichen → Schlussfolgerung treffen
   → Fall lösen → Abzeichen und Sammelobjekt erhalten

Jede Szene wird vollständig aus Code gezeichnet. Tippt man auf ein Objekt, öffnet sich eine kurze Beschreibung, und passt ein Hinweis dazu, landet er automatisch im Notizbuch. Falsche Tipps kosten nichts.

20 Fälle, fünf Kapitel#

Kapitel Fälle Ton
🏠 Das Zuhause 5 verschwundene Gegenstände, Familienmissverständnisse
🏫 Die Schule 5 Widersprüche in Aussagen, kleine Gaunereien
🌳 Der Park 4 Fußabdrücke, Wind, Wasser
🏛 Das Museum 3 Dinge, die jemand genauer anschauen wollte
🎉 Das geheimnisvolle Fest 3 Luftballons, Musikbox, Kerzen

Die inhaltliche Linie ist streng: keine Gewalt, keine Waffen, kein Tod, keine Entführungen, keine Kriminalfälle, keine Horror-Ästhetik. Die „Täter" sind am Ende immer Katzen, Hunde, Mäuse, Luftballons, ein Roboterstaubsauger, der Wind, ein Kleinkind oder ein freundliches Missverständnis.

Jede Auflösung ist so gebaut, dass niemand dafür beschimpft wird. Das ist nicht nur Anstand, sondern Gestaltungsauftrag: Ein Fall, in dem ein Kind am Ende ermahnt wird, ist ein Fall, den ein Kind nicht fertigspielt.

Fälle sind Daten, nicht Code#

Die Architekturentscheidung, die das Projekt am längsten getragen hat: Ein Fall ist ausschließlich Daten. Für die Engine muss nie etwas geändert werden.

case("park_05", Chapter.PARK, 5, "Der verschwundene Regenschirm") {
    teaser = "Der Regenschirm steht nicht mehr im Spind."
    scene("Sonnenschutz", Backdrop.PARK) {
        prop("spind", PropShape.LOCKER, 0.3f, 0.6f, 0.2f, 0.3f, z = 2)
    }
    hotspot("nass", "Nasser Abdruck", evidence = "ev_nass")
    combo("c_a_b", "ev_nass", "ev_x", "ev_ergebnis")
    conclusion(
        prompt = "Wo ist der Regenschirm?",
        required = listOf("ev_nass", "ev_ergebnis"),
        correct = "a",
        options = listOf(opt("a", "…", "…"), opt("b", "…", "…")),
    )
    hint(Difficulty.JUNIOR, 1, "…", highlight = "nass")
}

Eine kleine DSL, damit Fälle nur Daten sind. Und weil die Schwierigkeitsgrade über difficulties = setOf(...) am Hotspot hängen, bekommt der Junior-Modus automatisch weniger relevante Gegenstände – ohne dass ein zweiter Fall geschrieben werden muss.

Der Validator ist das Gegenstück dazu. CaseValidator.validate(fall) prüft Verweise, Rechtecke, Antwortlisten, Hinweisziele und Belohnungen. Der Test CaseLibraryTest lässt alle 20 Fälle durchlaufen. Ein neuer Fall mit kaputtem Verweis fällt damit sofort auf.

Schwierigkeitsgrade, die sich nicht wie Zusatzinhalte anfühlen#

Jeder Fall hat Hinweise für beide Grade, und die unterscheiden sich in der Substanz:

Junior-Modus Detektiv-Modus
Hinweisstufe 1 Bereich hervorheben nur Richtung
Hinweisstufe 2 kleine Hilfe kleine Hilfe
Hinweisstufe 3 konkreter Hinweis konkreter Hinweis
Interaktive Objekte alle gefiltert

Dazu kommt ein bewusst kleines Detail: Im Junior-Modus bekommen noch nicht untersuchte Objekte einen dezenten, langsam pulsierenden Ring. Im Detektiv-Modus gibt es den nicht. „Interaktive Objekte gut erkennbar" ist eine Anforderung, keine Dekoration.

Barrierefreiheit, die aus dem Spielprinzip folgt#

Viele Spuren sind winzig gezeichnet – ein Faden, ein Löchlein in der Wand. Beim Antippen wird die Fläche deshalb auf mindestens 0,14 × 0,18 der Szenengröße vergrößert, auf einem Telefon im Querformat rund 44 × 34 dp. Überlappungen gewinnt das Objekt, dessen Mitte näher liegt.

Weiter im Detail:

  • Schriftgrößen folgen der Systemeinstellung, Kontraste sind dunkel auf hell
  • Information wird nie nur über Farbe vermittelt: geprüfte Objekte bekommen ein grünes Symbol, Hervorhebungen zusätzlich einen pulsierenden Rahmen, Sterne eine Form
  • „Weniger Bewegung" schaltet alle Endlos-Animationen tatsächlich ab
  • Ton, Musik und Haptik sind einzeln schaltbar
  • der Elternbereich ist durch eine Rechenaufgabe geschützt

Eine vollständige Bedienung rein über Screenreader ist bewusst nicht umgesetzt. Das Spiel ist ein Beobachtungsspiel; die Szene trägt eine Kurzbeschreibung mit Namen und Anzahl der untersuchbaren Gegenstände.

Offline, diesmal mit einer einzigen Berechtigung#

Das Manifest enthält genau eine Permission: android.permission.VIBRATE – eine normale Berechtigung für das optionale haptische Feedback, die bei der Installation automatisch gewährt wird und keinen Dialog zeigt. Damit kann die App technisch keine Netzwerkverbindung öffnen, auch wenn eine Abhängigkeit es versuchen würde.

Der Fortschritt liegt über DataStore im privaten App-Daten, allowBackup="false" verhindert Cloud-Backup und Geräte-Übertragung. Die App prüft nicht einmal, ob Internet verfügbar ist – es gibt keinen Code dafür.

67 Unit-Tests, 11 UI-Tests#

Datei Tests Inhalt
CaseLibraryTest 10 20 Fälle, 5 Kapitel, Datenintegrität, Belohnungen, IDs
CaseEngineTest 24 Untersuchen, Kombinieren, Hinweisstufen, Sterne, jeder Fall in beiden Graden lösbar
StorageTest 14 JSON-Roundtrip, kaputte Daten, vier Profile, Zurücksetzen, Spielzeit
GameViewModelTest 15 Navigation, vollständiges Spielen, Profilwechsel, Speichern
ParentGateTest, ProfilesTest 4 Rechenaufgaben, Profil-Hilfsfunktionen

Der interessanteste Test ist der letzte Teil von CaseEngineTest: jeder Fall muss in beiden Graden vollständig lösbar sein. Das schließt den Fehler aus, dass ein Hinweisziel existiert, aber nicht erreichbar ist.

Auf dem echten Gerät (SM-G975F, Android 12) sind 11 instrumentierte UI-Tests grün geworden. Dabei gefunden und behoben: zu hohe Inhalte auf niedrigen Landscape-Fenstern (360 dp Höhe) sowie eine Endlos-Animation, die Compose-Testläufe blockierte.

Was ich dabei gelernt habe#

Ein Spiel für Kinder ist eine Aussage über Erwartungen. Alles, was nicht auf dieser Liste steht, muss man bewusst weglassen – und das ist mehr Arbeit als das Hinzufügen.

Datengetriebene Fälle skalieren besser als handgeschriebene Logik. Bei 20 Fällen ist das noch lösbar, aber der Validator macht den Unterschied sichtbar: Die häufigste Fehlerquelle in Spielen mit many cases sind nicht die Algorithmen, sondern die kaputten Verweise.

Landscape ist eine Produktentscheidung. Szenen brauchen Breite; im Hochformat wäre die Szene so klein, dass die Spuren ihre Vergrößerung bräuchten.

Ein Release-Buildtype mit Development-Signatur ist eine gute Idee. assembleRelease läuft so lokal ohne Geheimnisse durch. Für die Veröffentlichung wird der signingConfig auf einen eigenen Keystore umgestellt – im Repository liegt bewusst kein privater Schlüssel.

Grenzen#

Die Oberfläche ist nur auf Deutsch ausgelegt, die Falldaten müssten für weitere Sprachen zusätzlich angelegt werden. Eine vollständige Bedienung über Screenreader fehlt. Die Spielzeit wird beim Wechsel in den Hintergrund gespeichert; bei einem erzwungenen Beenden fehlt die laufende Sitzung.

Und es gibt keine Export- oder Importfunktion für Profile. Das ist Absicht – keine Datenübertragung – heißt aber auch: Ein Gerätewechsel erfordert erneutes Spielen.

Die Referenz auf haasarts.de zeigt das Projekt im Zusammenhang. Ähnliche Projekte – ruhig, offline, kindgerecht – gerne über den Kontakt.

Quelle

Dieses Projekt

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