Ein Spiel für Game Boy erstellen

Bild

Vor ein paar Wochen habe ich beschlossen, an einem Spiel für Game Boy zu arbeiten, dessen Erstellung mir große Freude bereitete. Sein Arbeitsname ist Aqua and Ashes. Das Spiel hat Open Source und ist auf GitHub veröffentlicht .

Wie kam ich auf diese Idee?


Ich habe kürzlich einen Praktikumsjob bekommen und ein Backend in PHP und Python für die Website meiner Universität erstellt. Dies ist eine gute und interessante Arbeit, für die ich sehr dankbar bin. Aber ... gleichzeitig hat mich all dieser hochrangige Webentwicklungscode mit einem unersättlichen Wunsch infiziert. Und es war der Wunsch nach Arbeit auf niedriger Ebene mit Bits.

Ich erhielt eine wöchentliche itch.io-Übersicht über Game Jams in der Mail, die den Beginn von Mini Jam 4 ankündigte. Es war ein 48-Stunden-Jam (eigentlich ein bisschen größer), bei dem die Einschränkung darin bestand, Grafiken im Stil von Game Boy zu erstellen. Meine erste logische Reaktion war, ein Homebrew-Spiel für Game Boy zu erstellen. Das Thema der Marmelade war "Jahreszeiten" und "Flamme".

Nachdem ich ein wenig über die Handlung und die Mechanik nachgedacht hatte, die in 48 Stunden implementiert werden können und in die Grenzen des Themas passen, kam ich zu einem Klon einer neuen Interpretation des Levels aus dem SNES-Spiel Tiny Toon Adventures: Buster Busts Loose! Von 1993, in dem der Spieler in der Rolle des Baster American Football spielt .


Mir hat es immer gefallen, wie die Macher dieses Levels einen unglaublich komplexen Sport betrieben, alle Tricks, Positionen und strategischen Elemente beseitigt haben, als Ergebnis eines äußerst interessanten und einfachen Spiels. Offensichtlich wird eine derart vereinfachte Sichtweise des amerikanischen Fußballs Madden nicht ersetzen, ebenso wie NBA Jam (eine ähnliche Idee: nur 4 Spieler auf einem viel kleineren Feld mit einem einfacheren Gameplay als in einem regulären Spiel) die 2K-Serie nicht ersetzen wird. Diese Idee hat jedoch einen gewissen Reiz, und die Verkaufszahlen von NBA Jam bestätigen dies.

Wie hängt das alles mit meiner Idee zusammen? Ich habe beschlossen, dieses Fußballlevel zu nehmen und es neu zu gestalten, damit es dem Original ähnlich bleibt und gleichzeitig frisch ist. Erstens habe ich das Spiel auf nur vier Spieler reduziert - einen Verteidiger und einen Angreifer. Dies geschah hauptsächlich aufgrund der Einschränkungen der Hardware, aber gleichzeitig kann ich ein bisschen mit einer intelligenteren KI experimentieren, ohne auf das Prinzip "links laufen und manchmal springen" beim Spielen auf SNES zu beschränken.

Aus Gründen der Übereinstimmung mit dem Thema werde ich die Tore durch brennende Säulen oder Lagerfeuer oder ähnliches ersetzen (ich habe mich noch nicht entschieden) und einen Fußball mit Fackeln und Wassereimern. Der Gewinner wird das Team sein, das beide Lagerfeuer kontrolliert, und um dieses einfache Konzept herum können Sie leicht eine Handlung erstellen. Die Jahreszeiten werden ebenfalls berücksichtigt: Ich habe beschlossen, dass sich die Jahreszeiten in jeder Runde ändern, damit das Feuerteam im Sommer und das Feuerteam im Winter einen Vorteil erhalten. Dieser Vorteil sieht aus wie Hindernisse auf dem Spielfeld, die nur die gegnerische Mannschaft stören.

Natürlich wurden bei der Bildung von zwei Teams zwei Tiere benötigt, die Feuer lieben und nicht mögen. Zuerst dachte ich an Feuerameisen und einige Wasserwanzen, Gottesanbeterinnen und dergleichen, aber nachdem ich das Problem untersucht hatte, fand ich im Winter keine aktiven Insekten und ersetzte sie durch Polarfüchse und Geckos. Polarfüchse mögen Schnee, Geckos liegen gern in der Sonne, also scheint alles logisch. Am Ende ist es nur ein Spiel für Game Boy.

Falls dies immer noch nicht klar ist, war das Spiel am Ende des Staus noch nicht zu Ende. Jedenfalls hat es Spaß gemacht.

Game Boy Training


Zuerst müssen Sie die Anforderungen bestimmen. Ich habe mich entschieden, für DMG zu schreiben (interner Name für Game Boy-Modell, kurz für Dot Matrix Game). Hauptsächlich um die Anforderungen eines Game Jam zu erfüllen, aber auch weil ich es wirklich wollte. Persönlich hatte ich noch nie Spiele für DMG (obwohl es mehrere Spiele für Game Boy Color gibt), aber ich finde, dass die 2-Bit-Ästhetik eine sehr schöne und interessante Einschränkung für Experimente ist. Vielleicht werde ich zusätzliche Farben für SGB und CGB hinzufügen, aber bisher habe ich nicht darüber nachgedacht.

Ich habe mich auch für eine Kassette mit 32K ROM + ohne RAM entschieden, nur für den Fall, dass ich eine physische Kopie des Spiels erstellen möchte. CatSkull, das mehrere Game Boy-Spiele wie Sheep it Up! Veröffentlicht hat, bietet sehr günstige 32-Kilobyte-Blitzpatronen an , die perfekt für mich sind. Dies ist eine weitere zusätzliche Einschränkung, aber ich glaube nicht, dass ich in naher Zukunft das 32K-Volumen mit einem so einfachen Spiel überwinden kann. Das Schwierigste wird mit der Grafik sein, und wenn alles völlig schlecht ist, werde ich versuchen, es zu komprimieren.

Was die Arbeit von Game Boy betrifft, so ist alles ziemlich kompliziert. Um ehrlich zu sein, war Game Boy von allen Retro-Konsolen, mit denen ich arbeiten musste, die angenehmste. Ich habe mit einem hervorragenden Tutorial (zumindest zum ersten Mal, weil es nie abgeschlossen wurde) des Autors von AssemblyDigest begonnen. Ich wusste, dass es am besten ist, in ASM zu schreiben, wie schmerzhaft es manchmal auch sein mag, da die Hardware nicht für C ausgelegt ist, und ich war mir nicht sicher, ob der im Tutorial erwähnte coole Sprach-Wiz langfristig geeignet ist. Außerdem mache ich das hauptsächlich, weil ich mit ASM arbeiten kann.

Überprüfen Sie das Commit 8c0a4ea

Das erste, was zu tun war, war, den Game Boy zum Booten zu bringen. Wenn das Nintendo-Logo bei einem Versatz von $104 nicht gefunden wird und der Rest des Headers nicht richtig konfiguriert ist, geht das Game Boy-Gerät davon aus, dass die Kassette falsch eingesetzt ist, und lehnt das Laden ab. Die Lösung dieses Problems ist sehr einfach, da bereits viele Tutorials darüber geschrieben wurden. So habe ich das Überschriftenproblem gelöst. Es gibt nichts, was besondere Aufmerksamkeit verdient.

Es wird schwieriger sein, nach dem Laden sinnvolle Aktionen auszuführen. Es ist sehr einfach, das System in einen unendlichen Beschäftigungszyklus zu versetzen, in dem immer wieder eine Codezeile ausgeführt wird. Die Codeausführung beginnt mit dem Label main (wo der Sprung zur Adresse $100 anzeigt), daher müssen Sie dort einfachen Code einfügen. Zum Beispiel:

 main: .loop: halt jr .loop 

und es tut absolut nichts anderes, als auf den Start des Interrupts zu warten, wonach es zum .loop Label zurückkehrt. (Im Folgenden werde ich die detaillierte Beschreibung von ASM weglassen. Wenn Sie verwirrt sind, lesen Sie die von mir verwendete Assembler-Dokumentation .) Sie sind vielleicht neugierig, warum ich einfach nicht zum Hauptetikett zurückkehre. Dies geschieht, weil ich möchte, dass alles vor dem .loop Label die Initialisierung des Programms ist und alles, nachdem es in jedem Frame passiert. Somit muss ich den Zyklus des Ladens von Daten von der Kassette nicht umgehen und den Speicher in jedem Frame löschen.

Machen wir noch einen Schritt. Das von mir verwendete RGBDS-Assembler-Paket enthält einen Bildkonverter. Da ich zu diesem Zeitpunkt noch keine Ressourcen für das Spiel gezeichnet habe, habe ich beschlossen, die monochrome Schaltfläche auf meiner Info-Seite als Test-Bitmap zu verwenden. Mit RGBGFX habe ich es in das Game Boy-Format konvertiert und mit dem Assembler-Befehl .incbin nach der main eingefügt.

Bild

Um es auf dem Bildschirm anzuzeigen, benötige ich Folgendes:

  1. LCD aus
  2. Palette einstellen
  3. Bildlaufposition einstellen
  4. Videospeicher löschen (VRAM)
  5. Laden Sie Kachelgrafiken in VRAM
  6. Laden Sie die VRAM-Kachel-Hintergrundkarte herunter
  7. Schalten Sie das LCD wieder ein

LCD aus


Für Anfänger wird dies zum schwerwiegendsten Hindernis. Beim ersten Game Boy ist es unmöglich, jederzeit einfach Daten in VRAM zu schreiben. Es muss auf den Moment gewartet werden, in dem das System nichts zeichnet. Bei der Nachahmung des Phosphorglühens in alten CRT-Fernsehern wird das Intervall zwischen den einzelnen Bildern bei geöffnetem VRAM als Vertical-Blank oder VBlank bezeichnet (bei CRT ist dies ein Impuls zum Löschen des Kinescope-Strahls während eines Rückwärtsscans). (Es gibt auch HBlank zwischen jeder Zeile des Displays, aber es ist sehr kurz.) Wir können dieses Problem jedoch umgehen, indem wir den LCD-Bildschirm ausschalten, dh wir können im VRAM aufzeichnen, unabhängig davon, wo sich die „Phosphorspur“ des CRT-Bildschirms befindet.

Wenn Sie verwirrt sind, sollte Ihnen diese Bewertung viel erklären . Darin wird die Frage aus der Sicht von SNES betrachtet. Vergessen Sie also nicht, dass es keinen Elektronenstrahl gibt und die Zahlen unterschiedlich sind, aber in allem anderen ist sie durchaus anwendbar. Im Wesentlichen müssen wir das FBlank-Flag setzen.

Der Trick des Game Boy besteht jedoch darin, dass Sie das LCD nur während VBlank ausschalten können. Das heißt, wir müssen auf VBlank warten. Verwenden Sie dazu Interrupts. Interrupts sind Signale, dass der Game Boy Hardware an die CPU sendet. Wenn der Interrupt-Handler gesetzt ist, stoppt der Prozessor seine Arbeit und ruft den Handler auf. Game Boy unterstützt fünf Interrupts, von denen einer beim Start von VBlank startet.

Interrupts können auf zwei verschiedene Arten behandelt werden. Die erste und häufigste ist die Aufgabe eines Interrupt-Handlers , der wie oben erläutert funktioniert. Wir können jedoch einen bestimmten Interrupt aktivieren und alle Handler deaktivieren, indem wir das Aktivierungsflag für diesen Interrupt setzen und den di Opcode verwenden. Es tut normalerweise nichts, hat aber den Nebeneffekt, dass der HALT-Opcode beendet wird, wodurch die CPU gestoppt wird, bevor ein Interrupt auftritt. (Dies geschieht auch, wenn die Handler eingeschaltet sind, wodurch wir den HALT-Zyklus main .) Wenn Sie interessiert sind, erstellen wir im Laufe der Zeit auch einen VBlank-Handler, der jedoch häufig von bestimmten Werten an bestimmten Adressen abhängt. Da bisher nichts im RAM eingestellt wurde, kann ein Versuch, den VBlank-Handler aufzurufen, zu einem Systemabsturz führen.

Um die Werte festzulegen, müssen wir Befehle an die Game Boy-Hardwareregister senden. Es gibt spezielle Speicheradressen, die in direktem Zusammenhang mit verschiedenen Teilen des Geräts stehen, in unserem Fall der CPU, mit denen Sie die Funktionsweise ändern können. Wir sind besonders an den Adressen $FFFF (Interrupt-Aktivierungsbitfeld), $FF0F (aktiviertes, aber nicht behandeltes Interrupt-Bitfeld) und $FF40 (LCD-Steuerung) $FF40 . Eine Liste dieser Register finden Sie auf den Seiten, die dem Abschnitt "Dokumentation" der Liste "Awesome Game Boy Development" zugeordnet sind.

Um das LCD auszuschalten, schalten wir nur den VBlank-Interrupt ein, weisen $FFFF Wert $01 , führen HALT aus, bis die Bedingung $FF0F == $01 erfüllt ist, und weisen dann Bit 7 der Adresse $FF40 zu.

Einstellen der Palette und der Bildlaufposition


Das ist einfach zu machen. Nachdem das LCD ausgeschaltet ist, müssen wir uns keine Sorgen mehr um VBlank machen. Um die Bildlaufposition einzustellen, reicht es aus, die X- und Y-Register auf 0 zu setzen. Mit der Palette ist alles etwas schwieriger. In Game Boy können Sie Schattierungen von der ersten bis zur vierten Grafik einer der 4 Graustufen (oder Sumpfgrün, wenn Sie möchten) zuweisen, was für Übergänge und dergleichen nützlich ist. Ich habe einen einfachen Verlauf als Palette festgelegt, der als Liste der Bits %11100100 .

VRAM löschen und Kachelgrafiken laden


Beim Start bestehen alle Grafikdaten und die Hintergrundkarte nur aus einem scrollenden Nintendo-Logo, das beim Systemstart angezeigt wird. Wenn ich Sprites einschalte (sie sind standardmäßig deaktiviert), werden sie über den Bildschirm verteilt. Sie müssen den Videospeicher löschen, um von vorne zu beginnen.

Dazu benötige ich eine Funktion wie memset von C. (Ich benötige auch ein analoges memcpy um die Grafikdaten zu kopieren.) Die memset Funktion setzt das angegebene Speicherfragment auf ein bestimmtes Byte. Es wird mir leicht fallen, dies selbst zu implementieren, aber das AssemblyDigest-Tutorial verfügt bereits über diese Funktionen, sodass ich sie verwende.

Zu diesem Zeitpunkt kann ich VRAM mit memset indem ich $00 schreibe (obwohl beim ersten Commit der ebenfalls geeignete Wert $FF wurde) und dann die Kachelgrafiken mit memcpy in VRAM laden. Insbesondere muss ich es an die Adresse $9000 kopieren, da dies Kacheln sind, die nur für Hintergrundgrafiken verwendet werden. (Die Adressen $8000-$87FF werden nur für Sprite-Kacheln verwendet, und die Adressen $8800-$8FFF sind für beide Typen gleich.)

Kachelkarteneinstellung


Game Boy hat eine Hintergrundebene, die in 8x8-Kacheln unterteilt ist. Die Hintergrundebene selbst benötigt ungefähr 32 x 32 Kacheln, dh sie hat eine Gesamtgröße von 256 x 256. (Zum Vergleich: Der Konsolenbildschirm hat eine Auflösung von 160 x 144.) Ich musste die Kacheln, aus denen mein Bild besteht, Zeile für Zeile manuell angeben. Glücklicherweise wurden alle Kacheln in der richtigen Reihenfolge angeordnet, sodass ich nur jede Zeile mit Werten von N*11 bis N*11 + 10 füllen musste, wobei N die Zeilennummer ist und die verbleibenden 22 Kachelelemente $FF ausfüllen.

LCD einschalten


Hier müssen wir nicht auf VBlank warten, da sich der Bildschirm erst nach VBlank einschaltet, also habe ich einfach wieder in das LCD-Steuerregister geschrieben. Ich habe auch Hintergrund- und Sprite-Ebenen eingefügt und die korrekten Adressen der Kachelkarte und der Kachelgrafiken angegeben. Danach habe ich folgende Ergebnisse erhalten. Ich habe auch die Interrupt-Handler mit dem ei Opcode wieder eingeschaltet.

Um es noch interessanter zu machen, habe ich an dieser Stelle einen sehr einfachen Interrupt-Handler für VBlank geschrieben. Durch Hinzufügen des Übergangs-Opcodes $40 kann ich dem Handler jede Funktion geben, die ich benötige. In diesem Fall habe ich eine einfache Funktion geschrieben, die den Bildschirm nach oben und unten scrollt.

Hier sind die fertigen Ergebnisse. [Ergänzung: Ich habe gerade festgestellt, dass das GIF nicht korrekt geloopt ist, sondern das Bild ständig übertragen muss.]


Bisher nichts besonders überraschendes, aber es ist trotzdem cool, dass ich theoretisch meinen alten Game Boy Color bekommen und sehen kann, wie mein eigener Code darauf ausgeführt wird.

Spaß mit karierten Laken


Um etwas auf den Bildschirm zu zeichnen, brauche ich natürlich eine Art Sprite. Nachdem ich die PPU (Picture Processing Unit) der Game Boy-Konsole studiert hatte, entschied ich mich, mich auf 8x8- oder 8x16-Sprites zu konzentrieren. Wahrscheinlich brauche ich die letzte Option, aber um die Größe zu spüren, habe ich schnell einen Screenshot des Spiels im Maßstab 1: 8 auf kariertes Papier gekritzelt.


Ich wollte den oberen Bildschirmrand unter dem HUD belassen. Es schien mir, dass es natürlicher aussehen wird als von unten, denn wenn es oben ist, können die Charaktere es vorübergehend tun, wenn sie das HUD vorübergehend blockieren müssen, wie in Super Mario Bros. Dieses Spiel wird keine komplexe Plattform haben, und in der Tat auch das Level-Design, so dass ich keine sehr allgemeine Sicht auf das Feld zeigen muss. Die Position der Zeichen auf dem Bildschirm und möglicherweise Hindernisse, die von Zeit zu Zeit auftreten, sind völlig ausreichend. Daher kann ich mir ziemlich große Sprites leisten.

Wenn also ein Quadrat eine 8x8-Kachel wäre, würde ein Sprite nicht ausreichen, egal welche Größe ich wähle. Dies gilt insbesondere, da es mit Ausnahme von Sprüngen fast keine vertikale Bewegung im Spiel gibt. Also habe ich beschlossen, Sprites aus vier 8x16-Sprites zu erstellen. Die Ausnahme war der Schwanz des Fuchses, der zwei 8x16 Sprites besetzt. Nach einfachen Berechnungen wurde klar, dass zwei Füchse und zwei Geckos 20 der 40 Sprites besetzen werden, dh es wird möglich sein, viele weitere Sprites hinzuzufügen. (8x8-Sprites würden schnell mein Limit überschreiten, was ich in den frühen Entwicklungsstadien nicht tun möchte.)

Im Moment muss ich nur Sprites zeichnen. Unten finden Sie grobe Skizzen auf kariertem Papier. Ich habe ein wartendes Sprite, ein "denkendes" Sprite, um zu entscheiden, ob ich bestehen oder rennen möchte, wie in einem SNES-Spiel ... und das war's. Ich hatte auch vor, Sprites aus laufenden Charakteren, springenden Charakteren und Charakteren zu machen, die Gegner greifen. Aber für den Anfang habe ich nur wartende und denkende Sprites gezeichnet, um nicht zu komplizieren. Den Rest habe ich immer noch nicht gemacht, ich muss das machen.


Ja, ich weiß, ich zeichne nicht sehr gut. Perspektive ist eine schwierige Sache. (Ja, und dieses Gesicht des Polarfuchses ist schrecklich.) Aber es passt perfekt zu mir. Das Charakter-Design hat keine besonderen Merkmale, ist aber für Game-Jam geeignet. Natürlich habe ich echte Geckos und Polarfüchse als Referenz verwendet. Ist es nicht wahrnehmbar?



Sie können es nicht sagen. (Fürs Protokoll: Nachdem ich mir diese Bilder noch einmal angesehen habe, habe ich festgestellt, dass es einen großen Unterschied zwischen Geckos und Eidechsen gibt. Ich weiß nicht, was ich damit anfangen soll, außer mich für dumm zu halten ...) Ich denke, Sie können sich vorstellen, dass dies die Quelle der Inspiration ist Blaze the Cat aus der Sonic-Spieleserie diente als Kopf des Fuchses.

Anfangs wollte ich, dass die Verteidiger und Stürmer in jeder Mannschaft unterschiedliche Geschlechter haben, und es war einfacher, zwischen ihnen zu unterscheiden. (Ich wollte die Spieler auch das Geschlecht ihres Charakters auswählen lassen.) Dies würde jedoch viel mehr Zeichnen erfordern. Also habe ich mich für männliche Geckos und weibliche Füchse entschieden.

Und schließlich habe ich einen Begrüßungsbildschirm gezeichnet, weil auf einem Stück kariertem Papier Platz dafür war.


Ja, Action-Posen sind immer noch alles andere als ideal. Der Polarfuchs sollte mehr verärgert sein und rennen, und der Gecko sollte bedrohlich aussehen. Defender Fox im Hintergrund - ein lustiger Hinweis auf die Kunst auf der Doom-Box.

Digitalisierung von Sprites


Dann fing ich an, Papierzeichnungen in Sprites umzuwandeln. Dafür habe ich das GraphicsGale-Programm verwendet, das kürzlich kostenlos gemacht wurde. (Ich weiß, dass Sie Asesprite verwenden können, aber ich bevorzuge GraphicsGale.) Die Arbeit an Sprites erwies sich als viel komplizierter als erwartet. Jedes dieser Quadrate aus den oben gezeigten Sprites benötigt bis zu 4 Pixel in einem 2x2-Raster. Und diese Quadrate hatten oft VIEL mehr Details als 4 Pixel. Deshalb musste ich viele Details der Skizzen loswerden. Manchmal war es sogar schwierig, sich an eine einfache Form zu halten, weil man einen für Augen oder Nase akzeptablen Ort verlassen musste. Aber es scheint mir, dass alles gut aussieht, auch wenn das Sprite völlig anders geworden ist.


Die Augen des Fuchses verloren ihre Mandelform und verwandelten sich in eine zwei Pixel hohe Linie. Die Augen des Geckos haben ihre Rundheit beibehalten. Der Kopf des Geckos musste vergrößert werden, um breite Schultern loszuwerden, und alle Biegungen, die der Fuchs hätte haben können, wurden deutlich geglättet. Aber ehrlich gesagt sind all diese einfachen Änderungen nicht so schlimm. Manchmal konnte ich kaum wählen, welche der Variationen besser ist.


GraphicsGale bietet außerdem praktische Funktionen für Ebenen und Animationen. Dies bedeutet, dass ich den Schwanz des Fuchses getrennt von ihrem Körper animieren kann. Dies hilft sehr, wertvollen VRAM-Platz zu sparen, da ich den Schwanz nicht in jedem Frame duplizieren muss. Außerdem bedeutete dies, dass Sie mit dem Schwanz mit variabler Geschwindigkeit wedeln, im Stehen des Charakters langsamer und beim Laufen schneller werden konnten. Dies macht die Programmierung jedoch etwas komplizierter. Trotzdem werde ich diese Aufgabe übernehmen. Ich habe mich für 4 Frames Animation entschieden, weil das genug ist.

Möglicherweise stellen Sie fest, dass der Polarfuchs die drei hellsten Graustufen verwendet, während der Gecko die drei dunkelsten Graustufen verwendet. In GameBoy ist dies akzeptabel, da ein Sprite zwar nur drei Farben enthalten kann, Sie in der Konsole jedoch zwei Paletten angeben können. Ich habe es so gemacht, dass Palette 0 für Füchse und Palette 1 für Geckos verwendet wird. Damit ist der gesamte verfügbare Palettensatz beendet, aber ich glaube nicht, dass ich andere brauchen werde.

Ich musste mich auch um den Hintergrund kümmern. Ich habe mich nicht um seine Skizzen gekümmert, weil ich geplant hatte, dass es eine feste Farbe oder ein einfaches geometrisches Muster sein würde. Ich habe den Bildschirmschoner noch nicht digitalisiert, weil ich nicht genug Zeit hatte.

Laden von Sprites ins Spiel


Überprüfen Sie dies mit dem be99d97- Commit.

Nachdem jeder einzelne Frame von Charaktergrafiken gespeichert wurde, war es möglich, sie in das GameBoy-Format zu konvertieren. Es stellte sich heraus, dass es in RGBDS ein sehr praktisches Dienstprogramm namens RGBGFX gibt. Es kann mit dem Befehl rgbgfx -h -o output.bin input.png und erstellt eine Reihe von Kacheln, die mit GameBoy kompatibel sind. (Der Schalter -h gibt einen Kachelmodus an, der mit 8x16 kompatibel ist, sodass die Konvertierung von oben nach unten und nicht von links nach rechts durchgeführt wird.) Er bietet jedoch keine Bindungen und kann keine doppelten Kacheln verfolgen, wenn jedes Bild ein separates Bild ist. Aber wir werden dieses Problem für später belassen.

Nachdem Sie die .bin-Ausgabedateien generiert haben, fügen Sie sie einfach mit incbin "output.bin" im Assembler incbin "output.bin" . Um alles zusammenzuhalten, habe ich eine gemeinsame Datei „gfxinclude.z80“ erstellt, die alle hinzuzufügenden Grafiken enthält.

Es war jedoch sehr langweilig, die Grafiken jedes Mal manuell neu zu generieren, wenn sich etwas änderte. Also habe ich die Datei build.bat bearbeitet und die Zeile for %%f in (gfx/*.png) do rgbds\rgbgfx -h -o gfx/bin/%%f.bin gfx/%%f , die jede Datei konvertiert. png im Ordner gfx / in die bin-Datei und speichert sie in gfx / bin. Es hat mein Leben sehr vereinfacht.

Um Hintergrundgrafiken zu erstellen, habe ich viel fauler vorgegangen. RGBASM hat eine dw ` -Richtlinie. Es folgt eine Zeile mit 8 Werten von 0 bis 4, die einer Zeile mit Pixeldaten entsprechen. Da Hintergrund-Sprites sehr einfach waren, stellte sich heraus, dass es einfacher war, ein einfaches geometrisches Muster zu kopieren und einzufügen, um ein festes, gestreiftes oder Schachbrettmuster zu erstellen. Hier sieht es zum Beispiel aus wie ein Landplättchen.

 bg_dirt: dw `00110011 dw `00000000 dw `01100110 dw `00000000 dw `11001100 dw `00000000 dw `10011001 dw `00000000 

Er schafft eine Reihe von verschobenen Streifen mit der Illusion der Perspektive. Dies ist ein einfacher, aber kluger Ansatz. Mit Gras war es etwas komplizierter. Ursprünglich war es eine Gruppe horizontaler Linien mit einer Höhe von 2 Pixel, aber ich habe manuell einige Pixel hinzugefügt, die ein wenig Rauschen hinzufügen, wodurch das Gras besser aussieht:

 bg_grass: dw `12121112 dw `12121212 dw `22112211 dw `11121212 dw `22112211 dw `21212121 dw `12121212 dw `12211222 

Grafik-Rendering


In GameBoy werden Sprites in einem Bereich namens OAM oder Object Attribute Memory gespeichert. Es enthält nur Attribute (Richtung, Palette und Priorität) sowie die Kachelnummer.Es genügte mir, diesen Speicherbereich zu füllen, um Sprites auf dem Bildschirm anzuzeigen.

Obwohl es kleine Funktionen gibt. Zunächst müssen Sie die Grafiken aus dem ROM in den VRAM laden. GameBoy kann nur Kacheln rendern, die in einem speziellen Speicherbereich namens VRAM gespeichert sind. Glücklicherweise reicht es zum Kopieren vom ROM in den VRAM aus, dies memcpyin der Initialisierungsphase des Programms durchzuführen . Es stellte sich heraus, dass ich mit nur 6 Zeichen-Sprites und 4 Tail-Kacheln bereits ein Viertel der VRAM-Fläche für Sprites belegt habe. (VRAM ist normalerweise in Bereiche mit Hintergrund und Sprites unterteilt, und 128 Bytes sind ihnen gemeinsam.)

Darüber hinaus ist der Zugriff auf OAM nur während VBlank möglich. Ich begann damit, dass VBlank auf Sprite-Berechnungen wartete, aber ich stieß auf Probleme, weil die Sprite-Berechnungen die gesamte von VBlank zugewiesene Zeit dauerten und es unmöglich war, sie zu beenden. Die Lösung besteht darin, in einen separaten Speicherbereich außerhalb von VBlank zu schreiben und diese während VBlank einfach in OAM zu kopieren.

Wie sich herausstellte, verfügt GameBoy über ein spezielles Hardware-Kopierverfahren, eine Art DMA (Direct Memory Access, Direct Access to Memory), das genau das tut. Durch Schreiben in ein bestimmtes Register und Aufrufen des Besetztzyklus in HiRAM (da ROM während DMA nicht verfügbar ist) können Sie Daten viel schneller aus dem RAM in das OAM kopieren als mit der Funktionmemcpy. Bei Interesse finden Sie hier saftige Details .

Zu diesem Zeitpunkt konnte ich nur eine Prozedur erstellen, die bestimmt, was schließlich in den DMA geschrieben wird. Dazu musste ich den Status von Objekten an einem anderen Ort speichern. Zumindest war Folgendes erforderlich:

  1. Typ (Gecko, Polarfuchs oder Tragegegenstand eines der Teams)
  2. Richtung
  3. X Position
  4. Y-Position
  5. Animationsrahmen
  6. Animations-Timer

In der ersten, sehr ungepflegten Entscheidung habe ich den Typ des Objekts überprüft und abhängig davon zu einer Prozedur gewechselt, die diesen Objekttyp spritisch zeichnet. Das Polarfuchs-Verfahren nahm beispielsweise je nach Richtung eine Position in X ein, addierte oder subtrahierte 16, fügte zwei Schwanz-Sprites hinzu und bewegte sich dann im Haupt-Sprite auf und ab.

Hier ist ein Screenshot davon, wie das Sprite beim Rendern auf dem Bildschirm in VRAM aussah. Der linke Teil besteht aus einzelnen Sprites, Hexadezimalzahlen daneben, von oben nach unten - vertikale und horizontale Position, Kachel- und Attributflags. Rechts sehen Sie, wie alles nach der Montage aussah.


Mit der Schwanzanimation war es etwas komplizierter. In der ersten Lösung habe ich einfach den Animations-Timer in jedem Frame inkrementiert und ein logisches Inkrement andmit einem Wert durchgeführt %11, um die Frame-Nummer zu erhalten. Dann können Sie einfach 4 * die Frame-Nummer (jedes Animationsframe besteht aus 4 Kacheln) zur ersten Endkachel im VRAM hinzufügen, um 4 verschiedene Frames im VRAM zu speichern. Es funktionierte (besonders das mit der Schwanzkachelsuche), aber der Schwanz wedelte wahnsinnig schnell und ich musste einen Weg finden, ihn zu verlangsamen.

Zweitens bessere Entscheidungen, führte ich in jedem Rahmen erhöht den globalen Timer, und wenn der Wert der Transaktionen andmit ihnenund der von mir gewählte Grad von zwei war 0, das Inkrement des Objekt-Timers wurde durchgeführt. Somit kann jedes einzelne Objekt seinen Animations-Timer mit jeder benötigten Geschwindigkeit herunterzählen. Dies funktionierte perfekt und erlaubte mir, den Schwanz auf ein vernünftiges Niveau zu verlangsamen.

Schwierigkeiten


Aber wenn alles so einfach wäre. Vergessen Sie nicht, dass ich all dies im Code verwaltet habe, indem ich für jedes Objekt meine eigene Unterprozedur verwendet habe. Wenn Sie fortfahren müssen, müssen Sie dies in jedem Frame tun. Ich musste angeben, wie ich zum nächsten Sprite übergehen soll und aus welcher Kachel es besteht, indem ich die Register manuell manipuliere.

Es war ein völlig instabiles System. Um einen Frame zu zeichnen, war es notwendig, eine ausreichend große Anzahl von Registern und CPU-Zeit zu jonglieren. Es war fast unmöglich, Unterstützung für andere Mitarbeiter hinzuzufügen, und selbst wenn es mir gelingen würde, wäre die Systemunterstützung sehr schmerzhaft. Glauben Sie mir, es war echtes Chaos.Ich brauchte ein System, in dem der Code zum Rendern von Sprites verallgemeinert und unkompliziert ist, damit keine Bedingungen, Manipulationen von Registern und mathematischen Operatoren miteinander verwoben werden.

Wie habe ich das behoben? Ich werde im nächsten Teil des Artikels darüber sprechen.

Bild

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


All Articles