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.
| Spiel | Board | Art des Spiels | Bildrate in Velxio |
|---|---|---|---|
| ESP32 Doom | ESP32 DevKit | Ego-Shooter | etwa 18 FPS |
| Sky Duel | Seeed XIAO ESP32-S3 | Luftkampf in der Seitenansicht | etwa 29 FPS |
| XIAO Quest | Seeed XIAO ESP32-S3 | Top-down-RPG | etwa 32 FPS |
| Gear Hop | Seeed XIAO ESP32-S3 | Side-Scrolling-Jump-and-Run | etwa 27 FPS |
| Circuit Defense | Seeed XIAO ESP32-S3 | Tower Defense | etwa 31 FPS |
| Skythread | Seeed XIAO ESP32-S3 Sense | 3D-Flug über eine generierte Landschaft | etwa 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
- Öffne ein beliebiges Spiel aus der Tabelle oben. Der Sketch, die Bibliotheken und die Schaltung werden zusammen geladen.
- 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.
- 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.
- 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.
- Öffne den Serial-Monitor, um das Log des Spiels zu beobachten: eine
STAT-Zeile mit der Bildrate und eineEVT-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 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-Pin | Geht zu | XIAO-Pin | Geht zu | |
|---|---|---|---|---|
| D8 | Display SCK | D0 | D-Pad UP | |
| D10 | Display MOSI | D1 | D-Pad DOWN | |
| D7 | Display CS | D2 | D-Pad LEFT | |
| D9 | Display D/C | D3 | D-Pad RIGHT | |
| 3V3 | Display VCC, LED und RST | D4 | A | |
| GND | Display GND, alle Buttons | D5 | B |
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.

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:

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 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:

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:
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.
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-Pin | Geht zu | XIAO-Pin | Geht zu | |
|---|---|---|---|---|
| D8 | Display SCK | D0 | Stick X (links und rechts) | |
| D10 | Display MOSI | D1 | Stick Y (oben und unten) | |
| D9 | Display MISO | D2 | sechs Tasten, über eine Widerstandsleiter | |
| D3 | Display CS | D5 | FIRE | |
| D6 | Display D/C | D7 | BRAKE | |
| D4 | Display RST | 3V3, GND | Strom 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 mit | Erwartet | In Velxio gemessen |
|---|---|---|---|
| CIRCLE | 0 Ω | 0 V | 0 mV |
| CROSS | 2,2 kΩ | 0,595 V | 612 mV |
| TRIANGLE | 4,7 kΩ | 1,055 V | 1065 mV |
| L1 | 10 kΩ | 1,650 V | 1651 mV |
| START | 22 kΩ | 2,269 V | 2258 mV |
| SELECT | 47 kΩ | 2,721 V | 2665 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:

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.
| Spiel | Vom Sketch gemeldet (Median, Bereich) | Bilder pro Sekunde in Echtzeit |
|---|---|---|
| ESP32 Doom | 17,6 (13 bis 24) | 16,1 |
| Sky Duel | 28,9 (23 bis 38) | 27,2 |
| XIAO Quest | 32,2 (21 bis 39) | 30,8 |
| Gear Hop | 27,0 (21 bis 35) | 23,4 |
| Circuit Defense | 31,3 (23 bis 40) | 29,4 |
| Skythread | 20,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:
- Sky Duel: Ändere die Reisegeschwindigkeit (125) oder die Überziehgeschwindigkeit (50) in
fly()und spüre, wie sich das Flugzeug verändert. - XIAO Quest: Die Karten sind die Strings in
MAP_V,MAP_FundMAP_C; die Werte der Monster stehen in der TabelleFOES. - Gear Hop: Die Sprungkraft ist
-335inmovePlayer(), die Schwerkraft ist950; probier den Mond aus. - Circuit Defense: Füge einen vierten Turm zu
TW_NAME,TW_COSTundupdateTowers()hinzu. - Doom:
MAP_SRCsind 20 Strings mit je 20 Ziffern; jede Ziffer ist eine Wandtextur, und eine 9 ist eine Tür. - Skythread:
WORLD_SEEDoben im Sketch bestimmt die Welt, in die du startest, und mitPITCH_PULL_TO_CLIMBauf 0 steigt der Stick, wenn du ihn nach vorne drückst.
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.