WideNES-Projekt - Gehen Sie über die Grenzen des NES-Bildschirms hinaus

Bild

Mitte der 1980er Jahre war das Nintendo Entertainment System (NES) ein Muss. Der beste Sound, die beste Grafik und die besten Spiele unter allen Konsolen dieser Zeit - die Konsole erweiterte die Grenzen des Möglichen. Bisher wurden Projekte wie Super Mario Bros. , The Legend of Zelda und Metroid gelten als einige der besten Spiele aller Zeiten.

Mehr als 30 Jahre nach der Veröffentlichung von NES fühlen sich klassische Spiele großartig an, was nicht über die Hardware gesagt werden kann, auf der sie gearbeitet haben. Mit einer Auflösung von nur 256 x 240 konnte die NES-Konsole den Spielen nicht genügend Speicherplatz bieten. Trotzdem gelang es den furchtlosen Entwicklern, sich in die erstaunlichen, unvergesslichen Welten der NES-Spiele einzufügen: die labyrinthartigen Dungeons von The Legend of Zelda , weite Räume des Planeten in Metroid , helle Level von Super Mario Bros. . Aufgrund von NES-Hardwarebeschränkungen konnten Spieler jedoch niemals über 256 x 240 hinausgehen ...

Bis vor kurzem.

Ich präsentiere Ihnen das wideNES- Projekt - eine neue Art, NES-Klassiker zu spielen!



wideNES ist eine neue Technologie zum automatischen und interaktiven Markieren von NES-Spielen in Echtzeit .

Wenn sich die Spieler im Level bewegen, zeichnet wideNES den Bildschirm auf und erstellt nach und nach eine Karte des erkundeten Teils der Welt. In den folgenden Levels synchronisiert wideNES das Gameplay auf dem Bildschirm mit der generierten Karte, sodass die Spieler im Wesentlichen mehr sehen können, indem sie über die Grenzen des NES-Bildschirms hinausschauen! Das Beste ist, dass die Art und Weise, wie Sie wideNES-Spiele markieren, völlig universell ist , sodass eine Vielzahl von NES-Spielen ohne Konfiguration mit wideNES arbeiten können!

Aber wie funktioniert das alles?



Wenn Sie überprüfen möchten, wie wideNES funktioniert, bevor Sie den Artikel lesen, dann bitte! ANESE ist der NES-Emulator, den ich geschrieben habe, und derzeit ist es der einzige Emulator, der wideNES implementiert. Es ist jedoch zu warnen, dass ANESE nicht der beste NES-Emulator der Welt ist, sowohl was die Benutzeroberfläche als auch die Emulationsgenauigkeit betrifft. Die meisten Funktionen (einschließlich der Aufnahme von wideNES) sind nur über die Befehlszeile verfügbar. Obwohl viele beliebte Spiele einwandfrei funktionieren, verhalten sich einige andere möglicherweise unerwartet.



Wie wideNES funktioniert


Bevor Sie sich mit den Details befassen, ist es wichtig, kurz zu erklären, wie NES Grafiken rendert.

Pixelübertragung mit PPU


Das Herzstück von NES ist der ehrwürdige MOS 6502-Prozessor. In den späten 70er und frühen 80er Jahren wurden 6502 überall eingesetzt und arbeiteten in legendären Maschinen wie Commodore 64, Apple II und vielen anderen. Es war billig, leicht zu programmieren und leistungsfähig genug , um gefährlich zu sein.

Ergänzt wurde 6502 in der NES-Konsole durch einen leistungsstarken Grafik-Coprozessor namens Picture Processing Unit (PPU). Im Vergleich zu einfachen Videokoprozessoren, die auf älteren Systemen verwendet werden, ist PPU eine enorme Verbesserung in Bezug auf die Benutzerfreundlichkeit. Beispielsweise wurde fünf Jahre vor der Veröffentlichung von NES der Atari 2600-Prozessor 6502 verwendet, um für jede Rasterzeile grafische Anweisungen an den Coprozessor zu übertragen, so dass der Prozessor nur sehr wenig Zeit für die Ausführung der Spielelogik hatte. Zum Vergleich: PPU benötigte nur ein paar Befehle pro Frame , und dies gab 6502 genug Zeit, um ein interessantes und innovatives Gameplay zu erstellen.

PPU ist ein erstaunlicher Chip, seine Art, Grafiken zu rendern, ist fast nichts anderes als die Arbeit moderner GPUs, und eine vollständige Reihe von Artikeln wird benötigt , um seine Funktionen vollständig zu erklären. Da wideNES nur eine kleine Teilmenge der PPU-Funktionen verwendet, reicht es aus, sie nur kurz zu betrachten:

  • Auflösung: 256 x 240 Pixel, 60 Hz
  • Es funktioniert unabhängig von der CPU
    • Kommuniziert mit der CPU über E / A mit Speicherzuordnung (Adressbereich 0x2000 - 0x2007)
  • 2 Rendering-Ebenen: Sprite- Ebene und Hintergrundebene
    • Sprite-Schicht
      • Jedes einzelne Sprite kann an einer beliebigen Stelle auf dem Bildschirm platziert werden.
      • Ideal zum Bewegen von Objekten: Spieler, Feinde, Muscheln
      • Bis zu 64 Sprites mit 8 x 8 Pixeln
    • Hintergrundschicht
      • An ein Gitter gebunden
      • Ideal für statische Elemente: Plattformen, große Hindernisse, Dekorationen
      • Der Videospeicher reicht aus, um 64 x 30 Kacheln mit einer Größe von 8 x 8 Pixel zu speichern
        • Echte interne Auflösung 512 x 240 mit einem Ansichtsfenster von 256 x 240
        • Unterstützt das Scrollen von Hardware , um das 256x240-Ansichtsfenster zu ändern
          • Das PPUSCROLL-Register (Adresse 0x2005) steuert die Verschiebung des Ansichtsfensters in X / Y.

Nachdem wir uns mit dieser sehr kurzen Übersicht befasst haben, kommen wir zum interessantesten: Wie funktioniert wideNES?

Hauptidee


Am Ende jedes Frames sendet die CPU die Änderungsinformationen an die PPU. Dazu gehören neue Sprite-Positionen, neue Level-Daten und, was für wideNES von entscheidender Bedeutung ist, neue Ansichtsfenster-Offsets . Da wideNES im Emulator funktioniert, ist es für uns sehr einfach, die in das PPUSCROLL-Register geschriebenen Werte zu verfolgen, was bedeutet, dass es unglaublich einfach ist, zu berechnen, wie viel sich der Bildschirm zwischen zwei Frames bewegt hat!

Hmm, was passiert, wenn anstelle jedes neuen Frames direkt über dem alten Frame neue Frames über dem vorherigen Frame gezeichnet, aber auf den aktuellen Bildlaufwert verschoben werden? Dann bleibt im Laufe der Zeit ein immer größerer Teil des Levels auf dem Bildschirm und erstellt nach und nach ein vollständiges Bild des Levels!

Um zu überprüfen, ob diese Idee von Wert ist, habe ich schnell die erste Implementierung skizziert.

Kompilieren ...
Starten ...
Laden Sie Super Mario Bros. herunter . ...

Voila!


Es hat funktioniert!

So'ne Art…



Ein anderer Ansatz: Warum nicht Ebenen direkt aus ROM-Dateien extrahieren?


Ohne die Details der Implementierung zu berücksichtigen, wird deutlich, dass diese Technik eine schwerwiegende Einschränkung aufweist: Eine vollständige Spielkarte kann nur gesammelt werden, wenn der Spieler das gesamte Spiel unabhängig erforscht.

Was wäre, wenn es eine Möglichkeit gäbe, Levels aus rohen NES-ROMs zu extrahieren ?!

Kann eine solche Technik überhaupt existieren?

Nun, höchstwahrscheinlich nicht.

Wenn Sie zwei Spiele für NES spielen, können Sie garantieren, dass beide nur eines gemeinsam haben: Beide funktionieren für NES. Alles andere kann ganz anders sein! Eine solche Nichtübereinstimmung ist eine echte Katastrophe, da NES-Spiele im Wesentlichen unendlich viele Optionen zum Speichern von Level-Daten bieten!

Einige Leute haben vollständige Levels durch Reverse Engineering extrahiert, indem sie die Leveldaten einiger Spiele gespeichert haben (manchmal mit der Erstellung von Karteneditoren mit vollem Funktionsumfang!). Dies ist jedoch eine schwierige Aufgabe, die viel Arbeit, Ausdauer und Intelligenz erfordert.

Um Level-Daten aus dem ROM zu extrahieren, muss bestimmt werden, welche Teile des ROM Code sind (keine Daten), und dies ist schwierig, da das Auffinden des gesamten Codes in einer Binärdatei einem Stoppproblem entspricht !

WideNES verwendet einen viel einfacheren Ansatz: Anstatt zu erraten, wie das Spiel die Level-Daten in den ROM gepackt hat, startet wideNES einfach das Spiel und verfolgt die Ausgabe!



Scrollen über 255 hinaus


NES ist ein 8-Bit-System, dh das PPUSCROLL-Register kann nur 8-Bit-Werte empfangen. Dies begrenzt den maximalen Bildlaufversatz auf 255 Pixel, dh die maximale 8-Bit-Zahl. Es ist kein Zufall, dass die NES-Bildschirmauflösung 240 x 256 Pixel beträgt, d. H. Eine Verschiebung um 255 Pixel reicht gerade aus, um den gesamten Bildschirm zu scrollen.

Aber was passiert, wenn Sie weiter als 255 scrollen?

Erstens setzen Spiele das PPUSCROLL-Register auf 0 zurück. Dies erklärt, warum SMB an den Anfang gebracht wird, wenn Mario sich zu weit nach rechts bewegt.

Um die 8-Bit-PPUSCROLL-Einschränkungen zu kompensieren, aktualisieren die Spiele ein weiteres PPU-Register: PPUCTRL (Adresse 0x2000). Die unteren 2 Bits von PPUCTRL setzen den „Startpunkt“ der aktuellen Szene in Vollbildschritten. Wenn Sie beispielsweise einen Wert von 1 schreiben, wird das Ansichtsfenster um 256 Pixel nach rechts verschoben, bei einem Wert von 2 wird das Ansichtsfenster um 240 Pixel nach unten verschoben. Der PPUCTRL-Offset wird mit dem PPUSCROLL-Register auf den Stapel verschoben, mit dem Sie den Bildschirm horizontal innerhalb von 512 Pixel oder vertikal innerhalb von 480 Pixel scrollen können.

Aber bauen, gibt es nur genug Videospeicher für Bildschirme mit zwei Ebenen? Was passiert, wenn das Ansichtsfenster zu weit nach rechts rollt und über den VRAM hinausgeht? Um diesen Fall zu behandeln, implementiert PPU die Faltung: Alle Teile des Ansichtsfensters außerhalb des ausgewählten Videospeichers werden einfach auf die gegenüberliegende Kante des Videospeichers reduziert.

Eine solche Faltung in Kombination mit intelligenter Manipulation von PPUSCROLL- und PPUCTRL-Registern ermöglicht es NES-Spielen, die Illusion von unendlich großen / weiten Welten zu erzeugen! Dank des langsamen Ladens eines Teils des Levels außerhalb des Sichtfensters und des schrittweisen Scrollens in dieses Level merken die Spieler nie, dass sie im VRAM tatsächlich „im Kreis laufen“!

Eine hervorragende Illustration aus dem nesdev-Wiki zeigt, wie Super Mario Bros. Verwendet diese Eigenschaften, um Ebenen zu erstellen, die länger als zwei Bildschirme sind:


Kehren wir zu der Frage zurück, die wir diskutieren: Wie geht wideNES mit dem Scrollen über 256 hinaus um?

Ehrlich gesagt ignoriert wideNES das PPUCTRL-Register vollständig und verfolgt nur den PPUSCROLL-Unterschied zwischen Frames!

Wenn PPUSCROLL unerwartet auf ungefähr 256 springt, was normalerweise bedeutet, dass sich der Charakter des Spielers auf dem Bildschirm nach links / oben bewegt hat, und wenn er unerwartet auf ungefähr 0 springt, bedeutet dies normalerweise, dass sich der Spieler auf dem Bildschirm nach rechts / unten bewegt hat.

Obwohl diese Heuristik einfach aussehen mag - und es ist -, funktioniert sie tatsächlich großartig!

Nach der Implementierung dieser Heuristik hat Super Mario Bros. , Metroid und viele andere Spiele haben fast perfekt funktioniert!

Ich war begeistert und habe einen weiteren NES-Klassiker hochgeladen - Super Mario Bros. 3 ...


Hmm ... nicht sehr hübsch.

Statische Bildschirmelemente ignorieren


Viele Spiele haben statische UI-Elemente an den Rändern des Bildschirms. Bei SMB3 ist dies die linke Spalte und die Statusleiste befindet sich am unteren Rand des Status.

Standardmäßig werden wideNES-Samples mit 16-Pixel-Schritten von den Bildschirmrändern abgetastet, dh alle statischen Elemente an den Rändern werden abgetastet! Nicht gut!

Um dieses Problem zu umgehen, implementiert wideNES Regeln und Heuristiken, die versuchen, statische Bildschirmelemente automatisch zu erkennen und zu maskieren.

Im Allgemeinen verwenden NES-Spiele drei verschiedene Arten von statischen Bildschirmelementen: HUDs, Masken und Statusleisten.

HUD - kein Problem


Wenn ein Spiel ein HUD über einem Level auferlegt, besteht das HUD wahrscheinlich aus mehreren Sprites. Beispiel: HUD in Metroid .

Glücklicherweise verursachen solche HUDs keine Probleme, da wideNES derzeit die Sprite-Ebene einfach ignoriert. Großartig!

Masken - nirgendwo einfacher


Die PPU verfügt über eine Funktion, mit der Spiele die 8 Pixel ganz links auf der Hintergrundebene maskieren können. Sie wird durch Setzen des zweiten Bits des Registers (Adresse 0x2001) aktiviert. Viele Spiele verwenden diese Funktion, aber zu erklären, warum sie dies tun, würde den Rahmen dieses Artikels sprengen.

Das Erkennen der enthaltenen Maske ist unglaublich einfach: wideNES verfolgt nur den PPUMASK-Wert und ignoriert die 8 Pixel ganz links, wenn das zweite Bit im Register gesetzt ist!

Es scheint, dass die Implementierung dieser einfachen Regel das Problem mit SMB3 behoben hat :


... gut oder fast beseitigt.

Statusleisten sind am schwierigsten


Aufgrund der Einschränkungen der PPU zu einem bestimmten Zeitpunkt auf dem Bildschirm können nicht mehr als 64 Sprites angezeigt werden. Darüber hinaus können zu jeder Zeit in jeder Rasterzeile nicht mehr als 8 Sprites vorhanden sein. Diese Einschränkung verhindert, dass Entwickler komplexe HUDs aus Sprites erstellen, und zwingt sie, Teile der Hintergrundebene zum Anzeigen von Informationen zu verwenden.

Zusätzlich zu Masken gibt es in PPU keine einfache Möglichkeit, die Hintergrundebene in den Spielbereich und den Statusbereich zu unterteilen. Daher gingen die Entwickler zu Tricks über, was zu einer Reihe unorthodoxer Methoden führte, um Status-Panels zu erstellen ...

WideNES verwendet verschiedene Heuristiken, um verschiedene Arten von Statusanzeigen zu erkennen. Um jedoch Zeit zu sparen, werde ich nur eine der interessantesten betrachten: die IRQ-Verfolgung im mittleren Frame.

Mid-Frame-IRQ-Tracking


Im Gegensatz zu modernen GPUs mit großen internen Frame-Puffern haben PPUs im Allgemeinen keinen Frame-Puffer! Um Platz zu sparen, speichert PPU Szenen als Raster aus 64 x 32 Kacheln mit 8 x 8 Pixeln. Anstatt die Pixeldaten vorab zu berechnen, werden die Kacheln als Zeiger auf den CHR-Speicher (Zeichenspeicher) gespeichert, der alle Pixeldaten enthält.

Seit der Entwicklung von NES in den 80er Jahren wurde PPU ohne Berücksichtigung moderner Anzeigetechnologien erstellt. Anstatt das gesamte Bild gleichzeitig zu rendern, gibt die PPU das NTSC-Videosignal aus, das auf einem CRT-Bildschirm angezeigt werden soll, auf dem das Video Pixel für Pixel , Zeile für Zeile , von oben nach unten, von oben nach unten, von links nach rechts angezeigt wird.

Warum ist das alles so wichtig?

Da PPU Frames Zeile für Zeile von oben nach unten rendert, können Sie PPU-Anweisungen an Mid-Frame senden, um Videoeffekte zu erstellen, die mit keinem anderen Ansatz möglich sind! Diese Effekte können entweder einfach (z. B. Ändern der Palette) oder recht komplex (z. B. Sie haben es erraten und Statusleisten erstellt!) Sein.

Um zu erklären, wie ein PPU-Schreibvorgang im mittleren Frame Statusleisten erstellen kann, habe ich einen unformatierten PPU- und CHR-Speicher-Video-Slice-Dump für einen einzelnen SMB3- Frame aufgezeichnet :


Alles sieht gut aus, nichts Besonderes ... aber schauen Sie einfach in die Statusleiste! Sie ist völlig verzerrt!

Schauen Sie sich jetzt dieselbe Rohkippe an, die jedoch nach Zeile 196 erstellt wurde ...


Ja, das Level sieht schrecklich aus, aber die Statusleiste sieht gut aus!

Was ist hier los?

SMB3 stellt einen Timer ein, der IRQ (Interrupt) genau nach dem Rendern der 195-Rasterzeile auslöst. Er übergibt die folgenden Anweisungen an den IRQ-Handler:

  • Setzen Sie PPUSCROLL auf (0,0) (damit die Statusleiste an Ort und Stelle bleibt)
  • Wir ersetzen die Kachelkarte im CHR-Speicher (wir ordnen die Grafiken der Statusleiste an).

Da der Rest der Ebene bereits gerendert ist, aktualisiert die PPU den Frame nicht erneut. Stattdessen wird das Rendern mit diesen Optionen fortgesetzt und eine schöne unverzerrte Statusleiste angezeigt!

Kehren wir zu wideNES zurück: Wenn Sie alle IRQs in der Mitte des Frames beobachten und sich an die Rasterlinie erinnern, auf der sie aufgetreten sind, kann wideNES alle nachfolgenden Rasterzeilen im Datensatz ignorieren! Wenn IRQ in der Rasterzeile über 240/2 auftritt, werden alle vorherigen Zeilen ignoriert, da eine frühzeitige Unterbrechung der Rasterzeile bedeutet, dass sich die Statusleiste möglicherweise oben auf dem Bildschirm befindet.

Nach der Implementierung dieser Heuristik hat Super Mario Bros. 3 perfekt verdient!




Ich habe kurz über die Möglichkeit nachgedacht, eine Computer-Vision-Bibliothek wie OpenCV zum Erkennen von Statusanzeigen (oder anderen meist statischen Bereichen des Bildschirms) zu verwenden, aber aus diesem Grund habe ich beschlossen, sie aufzugeben. Die Verwendung einer riesigen, komplexen und undurchsichtigen Computer-Vision-Bibliothek widerspricht den Idealen von wideNES, bei denen ich versuche, kompakte, einfache und transparente Regeln und Heuristiken zu verwenden, um Ergebnisse zu erzielen.



Szenenerkennung


Mit Ausnahme einiger prominenter Beispiele (z. B. Metroid ) bestehen Spiele für NES normalerweise nicht innerhalb eines riesigen, untrennbaren Levels. Im Gegenteil, die meisten NES-Spiele sind in viele kleine unabhängige „Szenen“ unterteilt, zwischen denen sich Türen oder Übergangsbildschirme befinden.

Da wideNES nicht das Konzept von "Szenen" hat, passieren beim Szenenwechsel schlimme Dinge ...

Hier ist zum Beispiel der erste Übergang von der Castlevania- Szene, in der Simon Belmont Draculas Schloss betritt:


Wow, alles ist schlecht! wideNES hat den letzten Teil des Levels mit dem ersten Bildschirm eines neuen Levels komplett neu geschrieben!

Offensichtlich benötigt wideNES eine Möglichkeit, Szenenänderungen zu erkennen. Aber welches?

Perceptual Hashing!

Im Gegensatz zu kryptografischen Hash-Funktionen, die dazu neigen, ähnliche Eingabedaten gleichmäßig über den Ausgabeinformationsraum zu verteilen, versuchen Wahrnehmungs- Hash-Funktionen, ähnliche Eingabedaten im Ausgabedatenraum „nahe beieinander“ zu halten. Wahrnehmungs-Hashes sind daher ideal, um ähnliche Bilder zu erkennen!

Wahrnehmungs-Hash-Funktionen können unglaublich komplex sein. Einige von ihnen können ähnliche Bilder erkennen, wenn eines von ihnen gedreht, skaliert, gedehnt und die Farben darin geändert wurden. Glücklicherweise erfordert wideNES keine komplexen Hash-Funktionen, da garantiert wird, dass jeder Frame dieselbe Größe hat. Daher verwendet wideNES den einfachsten der vorhandenen Wahrnehmungs-Hashes: Summieren aller Pixel auf dem Bildschirm!

Es ist einfach, aber es funktioniert ziemlich gut!

Sehen Sie sich beispielsweise an, wie Übergänge zwischen Szenen hervorstechen, wenn Sie den Wahrnehmungs-Hash über die Zeit in The Legend of Zelda zeichnen:


Derzeit verwendet wideNES einen festen Schwellenwert zwischen Wahrnehmungs-Hash-Werten, um den Übergang zwischen Szenen abzuschließen, aber das Ergebnis ist alles andere als ideal. Verschiedene Spiele verwenden unterschiedliche Paletten, und es gibt viele Fälle, in denen wideNES glaubt, dass ein Übergang stattgefunden hat, dies jedoch nicht der Fall war. Im Idealfall sollte wideNES einen dynamischen Schwellenwert verwenden, aber bisher reicht der feste aus.

Nach der Implementierung dieser neuen Heuristik erkennt wideNES erfolgreich Simons Eingang von Castlevania zum Schloss und erstellt dementsprechend eine neue Leinwand.


Und mit dieser Entscheidung haben wir das letzte große Teil des wideNES-Puzzles umgesetzt.

Nachdem ich die einfachste Serialisierung implementiert hatte, konnte ich endlich das Spiel für NES ausführen, in mehreren Levels spielen und automatisch Levelkarten erstellen!

Was erwartet wideNES in Zukunft?


wideNES besteht aus zwei separaten Teilen: dem wideNES- Kernel , der genau die Regeln / Heuristiken darstellt, die der Technologie zugrunde liegen, und der spezifischen Implementierung von wideNES im ANESE-Emulator.

WideNES- Kernverbesserung


Erstens neigt wideNES dazu, Übergänge zwischen Szenen zu aggressiv zu erkennen. Die Anzahl der falsch positiven Ergebnisse kann minimiert werden, indem ein geeigneterer Wahrnehmungs-Hashing-Algorithmus verwendet wird oder indem zwischen Wahrnehmungs-Hashes auf dynamische Schwellenwerte umgeschaltet wird.

Zusätzliche Arbeiten sind erforderlich, um statische Bildschirmelemente zu erkennen. , Megaman IV IRQ , , - wideNES . , - .

NES «» . The Legend of Zelda , PPUSCROLL, — PPUADDR. Zelda — , wideNES Zelda . «» , .

- «» . , Super Mario Bros. Level 1, , , wideNES Level 1: A, , , B, , , . Level 1 , wideNES A, , B «».

, wideNES . , .

wideNES ANESE


wideNES NES ANESE. ANESE — : CLI, UI ! «».

UI, ANESE wideNES . ANESE — , !

— . , ANESE , wideNES — . wideNES , !

ANESE wideNES , PC 60fps! ANESE wideNES . ANESE, wideNES , .

Fazit


wideNES, . , wideNES , . wideNES, wideNES .

wideNES , , wideNES. wideNES , , !

wideNES ! ANESE , Super Mario Bros. , The Legend of Zelda Metroid , -!

Source: https://habr.com/ru/post/de421555/


All Articles