Devlog

Notizen aus der Entwicklung von Easter Hop — einem Browserspiel, komplett in Vanilla HTML5 Canvas.

Pixel-Avatar von Peter Jacob
Peter Jacob — Solo-Entwickler aus Hamburg
Fortlaufend seit Januar 2026 · Über mich

Umbau: Deutsch, Rechtsseiten, weniger Ballast

metacontentrecht

Der größte Umbau seit dem Rebranding, und diesmal ging es nicht um das Spiel selbst. Nach der zweiten AdSense-Ablehnung habe ich mich gründlich eingelesen — in Googles Richtlinien, in E-E-A-T, in die deutsche Rechtslage für werbefinanzierte Websites — und dann konsequent aufgeräumt.

Die Seite ist jetzt komplett auf Deutsch. Das war überfällig: Eine .de-Domain mit deutschem Impressum und englischen Texten war ein seltsamer Zwitter. Impressum und Datenschutzerklärung sind jetzt eigene, ordentliche Seiten (§ 5 DDG statt des veralteten TMG-Verweises, DSGVO-konforme Erklärung mit AdSense-Abschnitt). Die Schriften werden lokal gehostet statt von Google-Servern geladen. Die leeren Anzeigen-Platzhalter sind raus — solange keine Anzeigen freigeschaltet sind, haben graue Kästen mit Rahmen auf der Seite nichts verloren.

Außerdem habe ich beim Übersetzen alle Artikel gegen den echten Spielcode geprüft und dabei ein paar peinliche Abweichungen gefunden — im alten Technik-Text standen Physik-Konstanten, die längst nicht mehr stimmten. Jetzt zitieren alle Seiten den Code, der tatsächlich läuft. Ehrlichkeit ist billiger als Korrekturen.

Performance-Check auf alten Geräten

performancemobile

Nach der AdSense-Ablehnung hatte ich Zeit und habe sie genutzt, um das Spiel auf möglichst vielen Geräten zu testen — inklusive eines Android-Mittelklasse-Handys von 2018 aus der Schublade. Ergebnis: stabile 60 fps, auch beim Feuerwerk mit 28 gleichzeitigen Partikeln.

Dass das funktioniert, liegt an Entscheidungen aus der frühen Entwicklung: Der Partikel-Pool erzeugt während eines Laufs keinerlei neue Objekte, die Farb-Strings der Ostereier sind vorgebaut statt pro Frame zusammengesetzt, und der Delta-Deckel im Game-Loop verhindert Nachhol-Orgien nach Tab-Wechseln. Die Details stehen auf der Technik-Seite.

Eine Sache habe ich auf der Liste gelassen statt sie zu bauen: Rendering in nativer Auflösung auf Retina-Displays. Aktuell rechnet das Spiel intern in 400×600 und wird hochskaliert. Auf sehr scharfen Displays sieht man das minimal — aber es ist mir den zusätzlichen Code bisher nicht wert gewesen.

AdSense-Ablehnung — und was ich daraus mache

metacontent

Letzte Woche kam die Antwort von Google AdSense: abgelehnt, Begründung „Low Value Content“. Meine ehrliche Reaktion: verständlich. Die Seite hatte zwei Inhaltsseiten, einen Devlog mit vier Einträgen aus demselben Monat und ein Spiel. Googles Prüfsysteme haben zehntausend solcher Seiten gesehen, und die meisten davon sind dünn.

Also tue ich etwas dagegen: ein Tipps-und-Strategie-Guide, eine Über-mich-Seite, eine technische Aufschlüsselung des Spiels und ein Essay darüber, warum ich überhaupt Browserspiele baue. Außerdem fülle ich den Devlog richtig auf — es gab echte Entwicklungsphasen, die ich damals nicht dokumentiert habe, und die schreibe ich jetzt aus Notizen und der Git-Historie nach.

Ich versuche nicht, das System mit Füllmaterial auszutricksen. Das sind Texte, die es wert sind, geschrieben zu werden. Wenn dir die Technik-Seite oder das Essay etwas bringt, ist das der Punkt. Wenn Google daraufhin die Anzeigen freischaltet, ist das ein netter Bonus.

Bewerbung bei Google AdSense

metamonetarisierung

Das Spiel ist seit etwa einem Monat online und bekommt einen kleinen, stetigen Besucherstrom — hauptsächlich über einen Reddit-Post mit ein paar Upvotes und zwei, drei Verlinkungen aus Browser-Game-Verzeichnissen. Nichts Dramatisches, aber echte Menschen, die spielen. Das schien mir ein vernünftiger Zeitpunkt, eine kleine Anzeige zu versuchen.

Ich habe das AdSense-Skript eingebunden und einen Anzeigenplatz unter dem Hero-Bereich vorgesehen. Während der Prüfung zeigt der Slot nur einen Platzhalter. Die Prüfung dauert angeblich bis zu zwei Wochen. Eingereicht, jetzt heißt es warten.

#4 — Mobile-Feinschliff & globale Bestenliste

featuremobilebackend

Der lokale Highscore war fürs Alleinspielen in Ordnung, fühlte sich aber einsam an. Also gibt es jetzt eine globale Top-3-Bestenliste auf Basis von Supabase — eine Postgres-Datenbank mit REST-API. Kein eigener Server nötig; das Spiel schickt Scores direkt aus dem Browser per fetch().

Qualifiziert sich ein Score für die Top 3, wechselt das Spiel in einen Namenseingabe-Bildschirm. Der kniffligste Teil war die iOS-Tastatur: Ein Canvas kann keinen Fokus bekommen, also fokussiert das Spiel ein unsichtbares <input>-Element. Android öffnet die Tastatur beim Fokus automatisch; iOS Safari verlangt, dass der Fokus-Aufruf direkt aus einem Touch-Event stammt — deshalb hängt er im touchstart-Listener.

Außerdem endlich einen alten Grafikfehler behoben: Ostereier und Wolken zappelten vertikal. Die Ursache war, dass ihre Höhe aus dem aktuellen Pixel-Offset berechnet wurde, der sich jeden Frame ändert. Die Lösung — Position deterministisch aus einer stabilen Slot-Nummer hashen — beschreibe ich im Detail auf der Technik-Seite.

Touch-Steuerung & Mobile-Tests

mobileinput

Das Spiel konnte von Anfang an Leertaste und Mausklick, Touch war ein Nachzügler. Der touchstart-Listener selbst ist eine Zeile — aber damit sich das Ganze auf dem Handy gut anfühlt, brauchte es mehr. Das Hauptproblem: Viele Mobile-Browser verzögern Tap-Events um bis zu 300 ms, ein Erbe aus der Zeit, als Doppeltippen „Zoom“ bedeutete. Die Lösung ist touch-action: none auf dem Canvas plus preventDefault() in den Touch-Handlern.

Getestet habe ich auf einer Mischung aus alten und neuen Geräten aus dem Familien- und Freundeskreis. Auf allem Aktuellen lief das Spiel anstandslos; auf älteren Android-Geräten (2017–2019) blieb die Framerate dank Partikel-Pool und sparsamem Rendering ebenfalls stabil.

Eine überraschende Erkenntnis: Mehrere Tester sagten, Touch fühle sich besser an als die Tastatur. Irgendetwas am direkten Tippen auf die Scheibe wirkt befriedigender als ein Tastendruck. Ich habe dafür keine Erklärung.

#3 — Feuerwerk, Zäune & Rasen-Deko

gameplayvisuell

Drei sichtbare Verbesserungen in diesem Schwung. Erstens: Die braunen Mauer-Hindernisse sind jetzt richtige weiße Lattenzäune — Pfosten, zwei Querriegel, vertikale Planken und spitze Zaunlatten, die von beiden Seiten in die Lücke ragen. Der Zaunkörper ist gebrochen weiß mit Schattenriegel und einem Glanzstreifen an der Vorderkante jedes Pfostens.

Zweitens: Auf dem Rasen liegen jetzt Ostereier, die mit dem Boden vorbeiscrollen. Winzige Pixel-Ovale in vier Farbvarianten (rosa, blau, gelb, lavendel) mit weißem Querstreifen. Zusammen mit den verstreuten Hintergrundblumen wirkt die Welt endlich bewohnt.

Drittens: Alle 10 gemeisterten Zäune gibt es ein kleines Feuerwerk — 28 Partikel fächern in Ostereier-Farben vom Hasen aus in alle Richtungen, mit leichtem Aufwärtsdrall und individuellem Ausblenden. Es nutzt den vorhandenen Partikel-Pool, null neue Datenstrukturen.

#2 — Easter Hop: das Rebranding

designart

Ostern rückte näher, und ich habe die komplette visuelle Identität des Spiels umgekrempelt. Aus dem kupferfarbenen Roboter-Maulwurf wurde ein cremeweißer Osterhase — gezeichnet im selben 17×14-Pixel-Raster. Rosa Ohren, Rosenäschen, dunkle Pupillen, rote Bäckchen, schattierte Pfoten: alles über eine Farb-Zuordnungstabelle definiert, ganz ohne Bilddateien.

Die unterirdische Cyber-Höhlen-Ästhetik wich einer Frühlingswiese, die mit steigendem Score in goldenen Sommer übergeht. Zwei Paletten-Objekte (SPRING und SUMMER) definieren Himmel, Boden, Hindernis- und Partikelfarben; jeden Frame wird zwischen ihnen interpoliert. Bei Punkt 30 ist die Verwandlung komplett — der Himmel vertieft sich zu Sommerblau, das Grün wird satt, und aus rosa Blütenblättern werden Sonnenblumengelb.

Die Hindernisse wurden von Server-Racks zu Gartenzäunen. Der Hintergrund bekam scrollende Wolken (drei überlappende Rechtecke pro Wolke, Parallaxe mit 0,18-facher Spielgeschwindigkeit), verstreute Pixel-Blumen in einem Zwei-Pass-Rendering (erst alle Blütenmitten, dann alle Blätter — minimiert die Canvas-Zustandswechsel) und einen einfachen Himmelsverlauf.

#1 — Der Kern: Molt the Mole

architekturgameplay

Easter Hop begann sein Leben als „Molt the Mole“ — ein Flappy-Bird-artiger Runner, in dem ein kupferner Roboter-Maulwurf durch Server-Racks in einem unterirdischen Tunnel navigiert. Das ganze Spiel läuft in einer einzigen HTML-Datei: keine Abhängigkeiten, kein Build-Schritt, keine externen Grafiken. Alles — Sprite, Physik, Partikel, UI — wird auf einem HTML5-Canvas mit der 2D-API gerendert.

Der Game-Loop nutzt das Fixed-Timestep-Akkumulator-Muster: Die reale Zeitdifferenz wird angesammelt und in 16,67-ms-Häppchen (60 Hz) abgearbeitet. Das entkoppelt die Spiellogik von der Bildrate — die Simulation läuft auf jedem Gerät gleich schnell.

Die Spielfigur ist ein 17×14-Pixel-Sprite aus Zeichenketten, jedes Zeichen steht für eine Farbe. Beim Rendern wird das Raster als Gitter gefüllter Rechtecke gezeichnet, skaliert mit MOLE_SIZE = 3. Canvas-save()/restore() kapseln Rotation und Squash-Stretch beim Hüpfen. Die Physik ist bewusst minimal: konstante Schwerkraft, gedeckelte Fallgeschwindigkeit, ein Hüpfer setzt die Vertikalgeschwindigkeit.

Die Lücken schrumpfen und das Tempo steigt mit dem Score — eine weiche Schwierigkeitsrampe ohne Sprünge. Ein Pool aus 80 vorab angelegten Partikel-Objekten bedient Hüpf-Staub und Todes-Explosion, damit während des Spiels niemals neue Objekte entstehen und der Garbage Collector still bleibt.

Punktesystem & lokaler Highscore

featureui

Der erste Prototyp hatte gar keine Punkteanzeige — man spielte, bis man starb, und startete neu. Einen Zähler einzubauen war trivial (plus eins pro gemeistertem Zaun), aber die Frage, wo und wie er angezeigt wird, hat länger gedauert. Auf dem Canvas ist schon viel los: Hase, Zäune, Hintergrund, Partikel. Ich wollte keine schwebende Zahl, die mit dem Spielgeschehen konkurriert.

Geworden ist es eine große, halbtransparente Zahl oben in der Mitte — groß genug für einen Blick aus dem Augenwinkel, durchsichtig genug, um nichts Wichtiges zu verdecken. Gezeichnet wird sie mit der Canvas-Text-API in einem Monospace-Font mit Schlagschatten, passend zur Pixel-Ästhetik, ohne zusätzliche Assets.

Für den Rekord war localStorage die offensichtliche Wahl: beim Game Over vergleichen, gegebenenfalls überschreiben, beim Laden auslesen. Sechs Zeilen Code, funktioniert über Sitzungen hinweg, braucht keinen Server. Ich finde es immer befriedigend, wenn die richtige Antwort auch die einfache Antwort ist.

Sound: erst gescheitert, dann Musik

audioux

Ich habe ungefähr anderthalb Tage damit verbracht, Soundeffekte einzubauen — und sie wieder rausgeworfen. Die Web-Audio-API ist mächtig, aber wirklich fummelig: ein plausibles „Hüpf“-Geräusch programmatisch erzeugen, es auf Desktop und Mobile zuverlässig auslösen, die Autoplay-Sperren der Browser umschiffen, die Lautstärke so treffen, dass sie nicht nervt. Auf dem Desktop lief es.

Auf iOS Safari kam kein Ton, bevor nicht eine Nutzergeste stattgefunden hatte — das ist Absicht, Browser blockieren Autoplay-Audio. Die Standardlösung (AudioContext in einem Event-Handler initialisieren) funktionierte bei mir in etwa 70 % der Fälle; im Rest hing der Context im „suspended“-Zustand und ließ sich erst bei der nächsten Geste wecken. Unzuverlässige Soundeffekte sind schlimmer als keine, also flogen sie raus.

Stattdessen gibt es jetzt etwas viel Einfacheres: eine Hintergrundmusik-Schleife über ein normales <audio>-Element, die beim ersten Tap startet (womit die Autoplay-Regel automatisch erfüllt ist) — plus ein kleiner Umschalter oben rechts im Spiel, um sie stummzuschalten. Kein Web-Audio, keine Race Conditions, funktioniert überall. Lektion gelernt: Erst die simple Lösung ausreizen, dann zur mächtigen greifen.

Die Physik richtig hinbekommen

gameplayphysik

Die erste spielbare Version hatte zu starke Schwerkraft und zu schwachen Hüpf-Impuls. Die Figur fiel wie ein Stein und verlangte hektisches Dauertippen — das ist nicht fordernd, sondern stressig und unpräzise, ohne Raum für einen Rhythmus. Ich habe mehrere Abende nur damit verbracht, drei Konstanten zu verstellen und zu testen.

Gelandet bin ich bei GRAVITY = 0.45, FLAP_IMPULSE = -7.5 und TERMINAL_VELOCITY = 10 — ein Tap erzeugt einen klaren Bogen von etwa einer Viertelsekunde bis zum Scheitelpunkt, und am Scheitelpunkt erneut zu tippen hält die Höhe. Zwei Entscheidungen daneben waren mindestens so wichtig wie die Zahlen selbst: Ein Tap setzt die Geschwindigkeit, statt sie zu addieren (dadurch wirkt jeder Hüpfer identisch), und ein Cooldown von 5 Frames verhindert, dass Getrommel die Kontrolle übernimmt. Was das für Spieler bedeutet, steht inzwischen ausführlich auf der Tipps-Seite.

Dazu kam die Squash-Stretch-Animation: Beim Hüpfen staucht sich das Sprite kurz und federt zurück, beim Fallen streckt es sich. Rein kosmetisch — aber Animation, die die Physik unterstreicht, lässt die Physik echter wirken, als sie ist.

#0 — Der erste Prototyp

prototyparchitektur

Angefangen hat das Projekt an einem ruhigen Sonntag im Januar. Die Idee: ein Flappy-Bird-artiges Spiel in einer einzigen HTML-Datei, ohne Abhängigkeiten, ohne Build-Schritt. Ich wollte schon länger mal wieder etwas Kleines, Abschließbares bauen, und ein Ein-Knopf-Runner hat genau den richtigen Umfang: Der Kern-Loop ist an einem Wochenende fertig, aber interessant genug, um ihn wochenlang zu polieren.

Der erste Prototyp war ein weißes Rechteck, das den Bildschirm hinunterfiel und bei Leertaste sprang. Keine Sprites, keine Hindernisse, kein Score — nur der Physik-Loop. Den Game-Loop zuerst sauber zu bauen ist eine Lehre aus früheren Projekten: Wenn der Update-Render-Zyklus wackelt, wackelt alles darüber. Ich habe von Anfang an den Fixed-Timestep-Akkumulator benutzt und dafür ein paar Stunden investiert, statt ein schnelles setInterval hinzupfuschen, das ich später bereut hätte.

Am Ende von Tag eins: ein springendes Rechteck, zwei wandernde Hindernisse mit Lücke dazwischen und eine Kollisionsabfrage, die den Lauf beendet. Nicht hübsch. Aber ein Spiel — man konnte es spielen, es hatte ein Ziel, und man konnte scheitern. Das ist das Fundament, auf dem alles andere steht.