Zurück

Sechs Handheld-Spiele für den ESP32 und den XIAO ESP32-S3, spielbar im Browser

Ein 2,4-Zoll-ILI9341-Display, ein D-Pad aus vier Tasten und zwei weitere Tasten für A und B: Das ist eine Handheld-Konsole, und ein ESP32 ist mehr als genug, um sie zu betreiben. Wir haben fünf Spiele für dieses Layout gebaut und dann ein sechstes, Skythread, für einen größeren Handheld mit Analogstick, und alle sechs in Velxios Beispielgalerie aufgenommen. Jedes davon ist ein Arduino-Sketch, und jedes läuft in deinem Browser: Run drücken und spielen, kein Board, kein Display, kein Löten.

SpielBoardArt des SpielsBildrate in Velxio
ESP32 DoomESP32 DevKitEgo-Shooteretwa 18 FPS
Sky DuelSeeed XIAO ESP32-S3Luftkampf in der Seitenansichtetwa 29 FPS
XIAO QuestSeeed XIAO ESP32-S3Top-down-RPGetwa 32 FPS
Gear HopSeeed XIAO ESP32-S3Side-Scrolling-Jump-and-Runetwa 27 FPS
Circuit DefenseSeeed XIAO ESP32-S3Tower Defenseetwa 31 FPS
SkythreadSeeed XIAO ESP32-S3 Sense3D-Flug über eine generierte Landschaftetwa 20 FPS

Das erste, Doom, kam zuerst und läuft auf dem klassischen ESP32. Die nächsten vier laufen auf dem Seeed Studio XIAO ESP32-S3, dem daumengroßen Board unseres Partners Seeed, und sie teilen sich eine kleine Engine. Das letzte, Skythread, läuft auf dem XIAO ESP32-S3 Sense, demselben Chip mit 8 MB PSRAM, den es braucht, um eine ganze Landschaft im Speicher zu halten. Dieser Artikel erklärt, wie jedes Spiel funktioniert und wie es intern arbeitet.

So spielst du sie in Velxio

  1. Öffne ein beliebiges Spiel aus der Tabelle oben. Der Sketch, die Bibliotheken und die Schaltung werden zusammen geladen.
  2. Drücke Run. Velxio kompiliert den Sketch und startet ihn. Die erste Kompilierung eines Sketches dauert bis zu einer Minute; danach kommt sie aus dem Build-Cache.
  3. Jedes Spiel beginnt mit einer Attract-Mode-Demo, die sich selbst spielt. Drücke eine beliebige Taste oder einen beliebigen Button, um die Steuerung zu übernehmen.
  4. Spiele mit der Tastatur: Die Pfeiltasten sind das D-Pad, Z ist A und X ist B. Jeder Button auf der Zeichenfläche zeigt seine Taste in einem kleinen Tastenkappen-Symbol. Du kannst die Buttons auch mit der Maus anklicken und gedrückt halten, um sie gedrückt zu lassen. Skythread hat einen Analogstick und mehr Buttons; seine Steuerung steht in seinem eigenen Abschnitt weiter unten.
  5. Öffne den Serial-Monitor, um das Log des Spiels zu beobachten: eine STAT-Zeile mit der Bildrate und eine EVT-Zeile für jeden Kill, jedes Level, jedes Pickup und jeden Tod. Skythread gibt alle zwei Sekunden eine eigene Zeile aus, mit der Bildrate, der Zeit, die jedes Frame zum Zeichnen und Senden gebraucht hat, und dem Punktestand.

Jeder Drucktaster in Velxio lässt sich über die Tastatur ansteuern, nicht nur diese. Klicke mit der rechten Maustaste auf einen Button und verwende die Karte Keyboard control: Klicke sie an, drücke eine Taste, und von da an drückt das Gedrückthalten dieser Taste den Button, während die Simulation läuft.

Der Simulator führt die Firmware selbst aus, kompiliert für den echten Chip: Velxio emuliert den ESP32 und den ESP32-S3 Befehl für Befehl in deinem Browser und zeichnet alles, was der Sketch an das ILI9341 sendet.

Der Handheld

Der XIAO ESP32-S3 Handheld in Velxio: vier schwarze Buttons als D-Pad links, der XIAO in der Mitte, das ILI9341-Display rechts, das Gear Hop zeigt, und ein blauer B- und ein roter A-Button unter dem Display

Der XIAO hat nur 11 GPIO-Pins, und ein Display und sechs Buttons brauchen zwölf. Der Trick ist, dass das Display zwei seiner üblichen Leitungen nicht braucht: Es wird nie etwas von ihm zurückgelesen, also bleibt MISO unverbunden, und sein Reset-Pin ist auf 3,3 V gelegt, sodass es zusammen mit dem Board zurückgesetzt wird. Das reicht genau aus.

XIAO-PinGeht zuXIAO-PinGeht zu
D8Display SCKD0D-Pad UP
D10Display MOSID1D-Pad DOWN
D7Display CSD2D-Pad LEFT
D9Display D/CD3D-Pad RIGHT
3V3Display VCC, LED und RSTD4A
GNDDisplay GND, alle ButtonsD5B

Jeder Button verbindet seinen Pin mit Masse. Der Sketch aktiviert die internen Pull-up-Widerstände des Chips mit pinMode(pin, INPUT_PULLUP), sodass ein Pin HIGH liest, wenn der Button nicht gedrückt ist, und LOW, wenn er gedrückt wird. Es werden keine Widerstände benötigt, weder auf der Zeichenfläche noch auf einem echten Breadboard. Doom verwendet dieselbe Idee auf dem größeren ESP32 DevKit mit eigenen Pins (sie sind in seinem Abschnitt weiter unten aufgeführt).

Warum der XIAO ESP32-S3

Die vier XIAO-Spiele zeichnen das ganze Bild zuerst in den Speicher und senden es dann in einem Durchgang an das Display. Ein Bild von 320 x 240 Pixeln mit zwei Bytes pro Pixel sind 153.600 Bytes, und es muss in einem zusammenhängenden Block im RAM liegen.

Darin unterscheiden sich die beiden Chips. Wir haben den größten freien Block von einem Sketch auf jedem der beiden ausgeben lassen: 110.592 Bytes auf dem klassischen ESP32 und 278.528 Bytes auf dem ESP32-S3. Der S3 kann das ganze Frame mit Reserve halten; der klassische ESP32 kann das nicht, weshalb Doom sein Bild stattdessen Spalte für Spalte zeichnet (mehr dazu weiter unten).

Der S3 ist im Simulator auch der schnellere der beiden. Mit demselben Frame-Buffer-Benchmark auf beiden Boards lief Velxios Browser-Engine den ESP32 mit 81 bis 90 Millionen Instruktionen pro Sekunde und den ESP32-S3 mit 96 bis 106 Millionen, und der S3 schaffte 33 volle Frames pro Sekunde gegenüber 31 beim ESP32.

Eine Engine für vier Spiele

Die vier XIAO-Sketches beginnen mit denselben 200 Zeilen: der Engine. Sie verwaltet den Frame-Buffer, die Buttons, eine Handvoll Zeichenfunktionen und die Frame-Schleife.

Ein Frame der gemeinsamen Engine: die sechs Buttons lesen, das Spiel aktualisieren, in den 150 KB großen Frame-Buffer im RAM zeichnen, ihn in einem einzigen SPI-Burst von 153.600 Bytes an das Display senden, Statistiken melden und das nächste Frame beginnen

Das Zeichnen in den RAM bedeutet, dass die Reihenfolge keine Rolle spielt. Ein Spiel kann den Himmel malen, dann die Hügel, dann die Flugzeuge, dann die Explosionen darüber, und das Display sieht immer nur das fertige Bild, sodass nichts flackert. Das Senden sind drei Aufrufe der Adafruit-ILI9341-Bibliothek:

static void present() {
  tft.startWrite();
  tft.setAddrWindow(0, 0, SCR_W, SCR_H);  // the whole 320 x 240 screen
  tft.writePixels(fb, SCR_W * SCR_H);     // 153,600 bytes in one burst
  tft.endWrite();
}

Bei 40 MHz brauchen diese 153.600 Bytes etwa 31 ms auf den SPI-Leitungen, sodass allein die Übertragung die Obergrenze nahe 30 Frames pro Sekunde setzt. Der Rest des Frames, die Spiellogik und das Zeichnen, muss in das passen, was übrig bleibt, weshalb die Zeichenfunktionen einfach sind: gefüllte Rechtecke, Linien, Kreise, Text in der 5 x 7-Schrift, die mit Adafruit GFX geliefert wird, und Sprites.

Die Sprites sind als Text geschrieben, ein Zeichen pro Pixel, mit . für transparente Pixel und einer Palette mit 128 Einträgen für den Rest. Das ist der Roboter aus Gear Hop:

"........a.......",
"....mmmmmmmm....",
"...mMMMMMMMMm...",
"...mMllMMllMm...",   // l = the eyes, light cyan
"...mMllMMllMm...",
"...mMMMMMMMMm...",
"....mmmrrmmm....",   // r = the red mouth

Nichts in diesen Spielen stammt aus einer Bilddatei. Die Kacheln der Karte, die Texturen und die Effekte werden beim Start des Boards vom Code in den RAM gezeichnet, sodass jeder Sketch reiner Text ist, den du im Editor lesen und ändern kannst.

Sky Duel: ein Luftkampf mit Flugmodell

Du fliegst einen Doppeldecker über eine Welt, die vier Bildschirme breit ist und sich umschließt: fliegst du über den rechten Rand hinaus, kommst du links wieder herein. Ein rotes Geschwader jagt dich, Flakgeschütze und Lastwagen warten am Boden, und jede Welle bringt ein weiteres feindliches Flugzeug.

Spielanleitung. Richte das Steuerkreuz dorthin, wo du fliegen willst, Diagonalen eingeschlossen; das Flugzeug dreht sich in diese Richtung. Halte A für die Maschinengewehre gedrückt und tippe B, um eine Bombe abzuwerfen (sechs pro Welle). Bomben sind der einzige Weg, die Bodenziele zu zerstören. Das Radar oben zeigt die ganze Welt, damit du die Flugzeuge findest, die außerhalb des Bildschirms sind.

So funktioniert es. Das Flugzeug bewegt sich nicht mit fester Geschwindigkeit. Jedes Frame führt der Sketch ein kleines Flugmodell aus:

Das Flugmodell von Sky Duel, einmal pro Frame: zum Steuerkreuz mit 3,2 Radiant pro Sekunde lenken, die Geschwindigkeit mit dem Steigwinkel ändern, unter 50 Pixel pro Sekunde überziehen, sodass die Nase absinkt, entlang des Kurses bewegen, wobei sich die Welt alle 1280 Pixel umschließt, und beim Berühren des Bodens abgeschossen werden

Die Geschwindigkeit driftet zu einem Reiseflugwert, aber Steigen kostet Geschwindigkeit und Sturzflug bringt welche, proportional zum Winkel. Ziehst du zu lange senkrecht nach oben, fällt das Flugzeug unter die Überziehgeschwindigkeit: die Nase sinkt von selbst zum Boden, und du musst stürzen, um wieder zu fliegen. Die feindlichen Piloten fliegen dasselbe Modell, mit demselben Überziehen, und das macht es zu einem Luftkampf statt zu einer Schießbude.

Das Flugzeug ist ein 16 x 16 Sprite, das nach rechts zeigt. Beim Start dreht der Sketch es in 32 Richtungen und speichert alle, wobei die nach links zeigenden gespiegelt werden, damit das Flugzeug nicht kopfüber fliegt. Ein Flugzeug zu zeichnen ist dann ein Tabellenzugriff, auf dem Bildschirm zweifach vergrößert. Die Feinde steuern auf einen Punkt kurz vor dir zu, feuern, wenn du vor ihren Kanonen bist, und brechen nach einem nahen Vorbeiflug ab, damit sie dich nicht rammen.

XIAO Quest: ein kleines RPG

Ein Held, ein Dorf, ein Wald und eine Höhle. Der Älteste am Brunnen erzählt dir, dass der Schattenkönig den Kristall des Dorfes in die alte Höhle gebracht hat, und gibt dir den Schlüssel für das Tor, das den Weg versperrt. Im Wald und in der Höhle greifen Monster an, und du kämpfst rundenbasiert gegen sie.

Spielanleitung. Gehe mit dem Steuerkreuz. A spricht mit Leuten, liest Schilder, öffnet Truhen und bestätigt; B zeigt deine Werte und Gegenstände. Der Brunnen heilt dich, der Händler verkauft Tränke für 10 Gold, und drei Truhen enthalten zwei Tränke, ein Eisenschwert und einen Silberring. Wähle im Kampf mit dem Steuerkreuz und bestätige mit A: angreifen, Feuer wirken (3 MP, doppelter Schaden, ignoriert Verteidigung), einen Trank trinken oder fliehen.

So funktioniert es. Das Spiel ist eine kleine Zustandsmaschine. Jeder Zustand hat seine eigene Eingabeverarbeitung und seine eigene Zeichnung, und diese Ereignisse bewegen es von einem zum anderen:

Die Zustände von XIAO Quest: die Demo startet das Spiel bei beliebiger Taste; das Gehen führt bei A in der Nähe von jemandem zum Dialog und auf der letzten Seite zurück; ein Schritt im Wald oder in der Höhle hat eine Eins-zu-neun-Chance auf eine Begegnung, die nach 0,6 Sekunden zum Kampf wird; ein gewonnener oder geflohener Kampf kehrt zum Gehen zurück, ein verlorener führt zum Besiegt-Bildschirm und zurück ins Dorf; das Gespräch mit dem Schattenkönig startet den Bosskampf, und ihn zu besiegen führt zum Ende

Die drei Karten sind 20 x 14 Kacheln von 16 Pixeln, im Sketch als Strings geschrieben, ein Zeichen pro Kachel: # ist ein Baum, ~ Wasser, = ein Weg, K das verschlossene Tor. Der Held bewegt sich eine Kachel auf einmal und gleitet über 0,16 Sekunden zwischen den Kacheln, sodass die Bewegung flüssig wirkt, die Kollisionen aber einfach bleiben: ein Schritt ist erlaubt oder nicht.

Kämpfe verwenden kleine Ganzzahlformeln. Ein Angriff macht deinen Angriffswert plus einen Wurf von 0 bis 3, minus die Verteidigung des Gegners. Erfahrung bringt Stufen, und jede Stufe erhöht Gesundheit, Magie, Angriff und Verteidigung. Die Höhle ist dunkel: nach dem Zeichnen der Karte dämpft der Sketch jedes Pixel zwischen 50 und 70 Pixeln vom Helden auf halbe Helligkeit und alles Weiter entfernte auf ein Viertel, sodass du eine Laterne trägst. Die Entfernung jedes Pixels zu messen senkte die Bildrate von etwa 27 auf 16. Sie stattdessen einmal pro Zeile als hellen Abschnitt, gedimmten Abschnitt und dunklen Rest zu berechnen bringt sie auf etwa 22 zurück.

Gear Hop: ein Platformer

Ein kleiner Roboter rennt durch zwei Level und sammelt Zahnräder. Käfer laufen auf dem Boden, Drohnen schweben in der Luft, Stacheln säumen manche Böden, und die breitesten Lücken lassen sich nur auf beweglichen Plattformen überqueren. Erreiche die Flagge am Ende jedes Levels.

Spielanleitung. Gehe mit LINKS und RECHTS, halte B zum Rennen, drücke A zum Springen. Halte A länger für einen höheren Sprung. Lande auf einem Gegner, um ihn zu stampfen, und springe von unten in einen ?-Block, um ein Zahnrad herauszuschlagen. Du hast drei Herzen; Stacheln und Gegner kosten eines, und in eine Grube zu fallen ebenfalls.

So funktioniert es. Die Level sind 128 x 14 Kacheln, von einem kleinen Skript erzeugt und als Strings in den Sketch eingefügt. Der Roboter ist eine 10 x 14 Pixel große Box, und jedes Frame bewegt er sich in zwei Schritten: zuerst seitwärts, aus jeder Wand herausgeschoben, in der er landet; dann senkrecht, auf dem Boden, auf einem Einwegbrett oder auf einer beweglichen Plattform landend oder mit dem Kopf an einen Block stoßend. Die beiden Achsen getrennt zu behandeln ist es, was den Roboter daran hindert, an Wänden zu kleben oder durch Ecken zu rutschen.

Drei kleine Regeln lassen das Springen sich richtig anfühlen. Ein Sprung, der bis zu 0,12 Sekunden vor der Landung gedrückt wird, wird beim Aufsetzen noch ausgeführt (ein Sprungpuffer). Ein Sprung, der bis zu 0,08 Sekunden nach dem Verlassen einer Kante gedrückt wird, zählt noch (Coyote-Zeit). Und A früh loszulassen kürzt die Aufwärtsgeschwindigkeit, wodurch das Gedrückthalten der Taste einen höheren Sprung ergibt. Zwei Hügelschichten scrollen mit einem Fünftel und mit unter der Hälfte der Kamerageschwindigkeit, was der Szene Tiefe gibt.

Circuit Defense: Tower Defense auf einer Platine

Käfer krabbeln über eine Platine auf den Chip zu. Du baust elektronische Bauteile auf der Platine, um sie aufzuhalten: eine LED feuert einen schnellen Laser auf einen Käfer, ein Kondensator entlädt sich in jeden Käfer um ihn herum, und eine Spule verlangsamt Käfer. Jeder Käfer, der den Chip erreicht, kostet ihn einen Gesundheitspunkt, und die Wellen werden immer stärker.

So wird gespielt. Bewege den Cursor mit dem D-Pad. B wählt das Bauteil, A baut es, und A auf einem bereits gebauten Bauteil wertet es auf, bis Stufe 3. Zerstörte Käfer zahlen Münzen, und jede geschaffte Welle zahlt einen Bonus. Der Kreis um den Cursor zeigt die Reichweite des Bauteils, das du gerade bauen willst.

So funktioniert es. Die Käfer folgen keiner festen Strecke. Sie laufen über die offene Platine, und du kannst sie mit dem lenken, was du baust: die Kupferleiterbahn auf der Platine ist immer die Route, die sie nehmen werden. Dahinter steckt eine Breitensuche, die nach jedem Bau erneut läuft:

Das Distanzfeld, dem die Käfer folgen: jede Zelle enthält ihre Anzahl an Schritten zum Chip, berechnet um eine Mauer aus Türmen herum, und die kürzeste Route vom Eingang führt um die Mauer herum und hinunter zum Chip

Die Suche beginnt bei den Zellen neben dem Chip, nummeriert sie mit 0 und breitet sich nach außen aus: jeder freie Nachbar einer nummerierten Zelle bekommt diese Nummer plus eins. Wenn sie fertig ist, enthält jede Zelle ihre Distanz zum Chip, Mauern aus Türmen eingeschlossen. Ein Käfer auf einer Zelle geht einfach zu einem beliebigen Nachbarn mit einer kleineren Nummer. Es gibt keine Routenplanung pro Käfer: eine Suche, 260 Zellen, dient allen.

Dieselben Zahlen machen die eine Regel des Spiels leicht durchsetzbar: du darfst den Pfad verlängern, aber niemals schließen. Wenn du baust, setzt der Sketch das Bauteil ab, führt die Suche erneut aus und schaut sich die Eingangszelle an. Hat sie keine Nummer mehr, kämen die Käfer nicht durch: das Bauteil wird entfernt und das Spiel meldet THAT BLOCKS THE PATH.

ESP32 Doom: ein Ego-Shooter

Das älteste der fünf läuft auf dem klassischen ESP32 DevKit. Du läufst durch ein Labyrinth aus texturierten Wänden, schießt auf Roboter, die dich verfolgen, und auf Schleime, die Säure spucken, öffnest Türen, sammelst Medkits und Munition auf und räumst Welle um Welle ab.

So wird gespielt. Das D-Pad läuft und dreht, A feuert (gedrückt halten für Dauerfeuer), B öffnet die Tür vor dir, und B zusammen mit LINKS oder RECHTS lässt dich seitwärts ausweichen. Die Pins unterscheiden sich von den XIAO-Spielen: UP auf GPIO 32, DOWN 33, LEFT 25, RIGHT 15, A auf 22, B auf 21, und das Display am VSPI-Bus des ESP32 (SCK 18, MOSI 23, MISO 19, CS 5, D/C 2, RST 4).

So funktioniert es. Doom ist ein Raycaster, die Technik von Wolfenstein 3D (1992): für jede Spalte des Bildschirms schickt er einen Strahl vom Spieler in eine 20 x 20 Rasterkarte und stoppt ihn an der ersten Wand. Die Distanz bestimmt, wie hoch das Wandstück gezeichnet wird. Da der klassische ESP32 keinen Platz für ein vollständiges Bild hat, baut der Sketch jeweils eine Spalte in einem 200-Pixel-Puffer auf, mit Boden, Decke, Wand, Gegnern, Waffe und Effekten bereits zusammengesetzt, und schickt sie als einen einzigen Burst:

Die Schritte für eine Doom-Spalte: den Strahl werfen, Decke und Boden füllen, das Wandstück zeichnen, Sprites hinzufügen, Fadenkreuz und Waffe hinzufügen, Tönungen und Text anwenden, dann die Spalte über SPI senden. 320 Spalten ergeben 128.000 Bytes pro Bild

Roboter laufen geradewegs auf dich zu und kratzen für 5 Gesundheit pro Sekunde; Schleime halten Abstand und spucken Säure, die 7 kostet. Ein Schuss trifft den nächsten Gegner unter dem Fadenkreuz, solange keine Wand näher ist. Jeder erledigte Gegner bringt die nächste Welle mit zwei mehr von ihnen, ein wenig schneller und ein wenig zäher.

Die Doom-Karte: ein 20 x 20 Raster mit einem Raum aus rotem Backstein und einem Tech-Raum oben, einer offenen Halle in der Mitte und einem Bereich mit Bronzewänden unten hinter zwei Türen, mit nummerierten Gegner-Spawnpunkten, Medkits und Munitionskisten

Skythread: 3D-Flug über eine generierte Welt

Doom spielt in 3D in Innenräumen. Skythread spielt in 3D im Freien: Du fliegst einen Deltaflügel-Gleiter tief über offenes Land, entlang von Flusstälern, an Bergen vorbei und über Seen, mit Nebel zum Horizont hin und einem Himmel, der von Tag über goldene Stunde bis zur Nacht wechselt. Ringe hängen in der Luft vor dir, und die Uhr läuft. Du startest mit 45 Sekunden, und jeder Ring fügt ein paar weitere hinzu. Jeder Ring in Folge erhöht die Combo, bis zum Fünffachen der Punkte, und durch die Mitte eines Rings zu fliegen bringt ein PERFECT. Verfehlst du einen Ring, beginnt die Combo von vorn; berührst du den Boden, prallst du von ihm ab, langsamer und drei Sekunden ärmer. Wie die anderen startet es mit einer Demo, die sich selbst fliegt.

Der Handheld. Skythread wurde für den Handheld geschrieben, auf dem unsere Metal Gear Solid- und Castlevania-Ports laufen, nicht für das D-Pad-Layout der anderen vier: ein Seeed Studio XIAO ESP32-S3 Sense, dasselbe ILI9341-Display, ein zweiachsiger Analogstick, zwei Tasten an eigenen Pins und sechs weitere an einem einzigen Pin. In Velxio öffnet sich das Beispiel im Layout dieses Handhelds: der Stick links, der Bildschirm in der Mitte, die Tasten rechts und unter dem Bildschirm.

XIAO-PinGeht zuXIAO-PinGeht zu
D8Display SCKD0Stick X (links und rechts)
D10Display MOSID1Stick Y (oben und unten)
D9Display MISOD2sechs Tasten, über eine Widerstandsleiter
D3Display CSD5FIRE
D6Display D/CD7BRAKE
D4Display RST3V3, GNDStrom für das Display, den Stick und die Leiter

So wird gespielt. Lenke mit dem Stick: Klicke ihn auf der Zeichenfläche an und benutze die Pfeiltasten, oder halte die Maus auf einem seiner Pfeile. Zieh nach hinten (unten), um zu steigen, drück nach vorn, um zu sinken, und nach links oder rechts, um zu rollen und zu wenden. FIRE (Z) ist ein Boost, der den blauen Balken leert, und startet auch ein Spiel. BRAKE (X) bremst dich für enge Kurven ab. START (Enter) startet und pausiert, CROSS (C) startet ebenfalls ein Spiel, SELECT (Q) kehrt zur Demo zurück, CIRCLE (T) ändert die Tageszeit, TRIANGLE (W) baut eine neue Welt, und L1 (F) zeigt die Bildrate.

Sechs Tasten an einem Pin. Der XIAO hat 11 Pins, und das Display, der Stick und acht Tasten würden 16 benötigen. Der Trick für sechs der Tasten ist eine Widerstandsleiter. Ein 10-kΩ-Widerstand zieht D2 auf 3,3 V hoch, und jede Taste verbindet D2 über einen anderen Widerstand mit Masse, sodass jede einen anderen Spannungsteiler bildet. Der Pin liest 3,3 V × R / (R + 10 kΩ):

TasteÜber Masse mitErwartetIn Velxio gemessen
CIRCLE0 Ω0 V0 mV
CROSS2,2 kΩ0,595 V612 mV
TRIANGLE4,7 kΩ1,055 V1065 mV
L110 kΩ1,650 V1651 mV
START22 kΩ2,269 V2258 mV
SELECT47 kΩ2,721 V2665 mV

Der Sketch liest D2 mit analogReadMilliVolts() und wählt die Taste, deren Spannung am nächsten liegt, mit Schwellenwerten auf halbem Weg zwischen den Nachbarn (300, 824, 1350, 1960, 2496 und 2960 mV). Velxio löst die Leiter als echte Schaltung: Die letzte Spalte ist das, was der Sketch auf dem seriellen Monitor ausgab, als wir jede Taste auf der Zeichenfläche drückten. Eine Leiter liest immer nur eine Taste auf einmal, da zwei gleichzeitig gedrückte wie eine einzelne Taste mit kleinerem Widerstand aussehen. Deshalb behält das Spiel FIRE und BRAKE, die beiden, die man beim Fliegen hält, an eigenen Pins. Es ignoriert die Leiter außerdem, bis sie einmal losgelassen wurde, sodass ein unverbundener D2 niemals Phantomtasten drücken kann.

So funktioniert es. Die Welt wird beim Start des Boards generiert: eine 512 x 512 große Karte der Landschaft, erzeugt mit Diamond-Square-Rauschen, wobei jede Zelle zwei Bytes belegt, eines für die Höhe und eines für eine Farbe aus der Palette. Mit einer halb so großen Kopie für die Ferne und zwei Framebuffern sind das etwa 940 KB, weshalb Skythread die Sense und ihren PSRAM braucht. In Velxio dauert es 3 Sekunden simulierte Zeit, die Welt zu bauen, und das erste Bild erreicht den Bildschirm 3,4 Sekunden nach dem Einschalten.

Das Zeichnen nutzt das Spalten-Raycasting der Voxel-Flugspiele der frühen 1990er. Für jede Spalte des Bildschirms läuft der Sketch einen Strahl von der Kamera über die Karte, von nah nach fern, bis zu 160 Schritte und 340 Zellen weit; nach 110 Zellen liest er stattdessen die halb so große Karte. Bei jedem Schritt wandelt er die Geländehöhe dort in eine Zeile auf dem Bildschirm um. Ist diese Zeile höher als alles, was bisher in der Spalte gezeichnet wurde, füllt er die Lücke mit der Farbe dieser Zelle und merkt sich das neue Oberkante:

Wie Skythread eine Bildschirmspalte zeichnet: an der Kamera beginnen, entlang des Strahls schreiten und Höhe und Farbe lesen, die Höhe auf eine Bildschirmzeile projizieren, und wenn diese Zeile höher ist als die bisherige Oberkante, die Lücke füllen und die Oberkante anheben; wiederholen, bis die Spalte voll ist oder der Strahl über 340 Zellen hinaus ist, dann zur nächsten Spalte wechseln

Diese gemerkte Oberkante pro Spalte heißt y-Buffer. Naher Boden wird immer zuerst gezeichnet, sodass ferner Boden nur darüber erscheinen kann und kein Pixel jemals zweimal gemalt wird. Der Nebel besteht aus 20 Paletten, jede ein Stück weiter zur Himmelsfarbe hin gemischt, eine pro Entfernungsband. Das Rollen ist eine Scherung: Der Horizont jeder Spalte bewegt sich nach oben oder unten im Verhältnis zu ihrem Abstand von der Bildschirmmitte, was wie ein Rollen aussieht. Die Ringe, der Schatten des Gleiters und die Sonne werden gegen denselben y-Buffer in ihrer eigenen Entfernung geprüft, sodass ein Berg vor einem Ring diesen verdeckt.

Das Bild wird Spalte für Spalte gespeichert, bereits in der Byte-Reihenfolge, die der ILI9341 erwartet. In der eigenen Hochformat-Adressierung des Panels ist eine Spalte des Landschaftsbildes eine seiner Zeilen, sodass das ganze Bild in einem einzigen Burst hinausgeht. Mit zwei Framebuffern arbeiten die beiden Kerne des Chips gleichzeitig: Eine Aufgabe auf Kern 0 sendet ein Bild über SPI mit 40 MHz an den Bildschirm, während Kern 1 das nächste zeichnet.

Der Sketch überwacht außerdem seine eigene Geschwindigkeit. Wenn das Zeichnen eines Bildes im Durchschnitt mehr als 22 ms dauert, wirft er 160 statt 320 Strahlen und zeichnet jede Spalte zwei Pixel breit, und unter 7 ms kehrt er zur vollen Breite zurück. Ab 45 ms, einem Fall, den seine Kommentare für einen langsamen Host wie einen Browser-Simulator reservieren, hört er auf, die beiden Kerne zu überlappen, und wartet, bis jedes Bild fertig gesendet ist, bevor er das nächste zeichnet, sodass das simulierte Panel immer ganze Bilder erhält. In Velxio lief er die ganze Zeit in diesem Modus, mit 160 Spalten, und zeichnete jedes Bild in 33 bis 50 ms simulierter Zeit.

Bildraten

Wir haben jedes Spiel auf velxio.dev in einem Headless-Chrome-Browser auf unserem Server gemessen, jeweils etwa eine Minute lang gespielt.

SpielVom Sketch gemeldet (Median, Bereich)Bilder pro Sekunde in Echtzeit
ESP32 Doom17,6 (13 bis 24)16,1
Sky Duel28,9 (23 bis 38)27,2
XIAO Quest32,2 (21 bis 39)30,8
Gear Hop27,0 (21 bis 35)23,4
Circuit Defense31,3 (23 bis 40)29,4
Skythread20,4 (16 bis 23)17,7

Zwei Zahlen pro Spiel. Die erste ist das, was das Spiel selbst auf dem seriellen Monitor meldet, aus dem eigenen millis() des Chips: die Bildrate des simulierten Boards. Die zweite ist, wie viele Bilder der Browser tatsächlich pro Sekunde Echtzeit angezeigt hat. Sie unterscheiden sich, wenn der Computer, der die Simulation ausführt, den simulierten Chip nicht auf voller Geschwindigkeit halten kann; die simulierte Zeit läuft dann etwas langsamer als die Uhr an der Wand. Deine eigenen Zahlen hängen von deinem Computer ab.

Die vier 2D-XIAO-Spiele laufen mit fast der doppelten Bildrate von Doom. Ein Teil davon ist die Arbeit: Ein Raycaster wirft 320 Strahlen und berechnet Boden und Decke Pixel für Pixel. Ein Teil davon ist die Übertragung: ein Burst pro Bild statt 320 kleiner, jeder mit seinem eigenen Adressfenster. Skythread, das einzige, das eine 3D-Landschaft zeichnet, hält etwa 20 Bilder pro Sekunde, indem es 160 statt 320 Strahlen wirft, der Kompromiss, den sein eigener Code eingeht, wann immer ein Bild mehr als 22 ms zum Zeichnen braucht.

Mach sie zu deinen eigenen

Jedes Spiel ist ein Sketch, und der Code ist in Blöcke gegliedert: die Engine, die Grafik, der Spielzustand, das Update und das Zeichnen. Einige Änderungen, die eine Minute dauern:

Drücke nach einer Änderung auf Run, und Velxio kompiliert den geänderten Sketch, sodass du eine Zahl ändern und das Ergebnis wenige Sekunden später sehen kannst. Wenn es so funktioniert, wie du es magst, läuft derselbe Sketch auf einem echten XIAO ESP32-S3 mit einem ILI9341 und sechs wie oben verdrahteten Tasten, und Skythread auf einem XIAO ESP32-S3 Sense mit dem Stick und den Tasten aus seinem Abschnitt.

Die Beispielgalerie öffnen


Teilen auf:
David Montero

Geschrieben von

David Montero

Creator of Velxio, the open-source circuit and Arduino simulator.

GitHub velxio.dev

Ähnliche Artikel


Nächster Artikel
Analoge Elektronik im Browser: sieben Schaltungen zum Ausführen und Messen