Dieser Nonsens-Beitrag ist der Entwicklung eines Spiels für den alten ZX Spectrum-Computer in C gewidmet. Werfen wir einen Blick auf den hübschen Kerl:
Die Produktion begann 1982 und wurde bis 1992 produziert. Technische Eigenschaften der Maschine: 8-Bit-Z80-Prozessor, 16-128 KB Speicher und weitere Erweiterungen, wie der AY-3-8910-Soundchip.
Im Rahmen des Wettbewerbs Yandex Retro Games Battle 2019 für diese Maschine habe ich ein Spiel geschrieben namens Interceptor 2020. Da ich keine Zeit hatte, die Assemblersprache für den Z80 zu lernen, beschloss ich, sie in C zu entwickeln. Als Toolchain habe ich ein fertiges Set gewählt – z88dk, das C-Compiler und viele Hilfsbibliotheken enthält, um die Implementierung von Anwendungen für das Spectrum zu beschleunigen. Es unterstützt auch viele andere Z80-Geräte, wie z. B. Taschenrechner von MSX und Texas Instruments.
Als nächstes beschreibe ich meinen oberflächlichen Flug über die Computerarchitektur, die z88dk-Toolchain, und zeige, wie ich es geschafft habe, den OOP-Ansatz zu implementieren und Designmuster zu verwenden.
Installationsfunktionen
Die Installation von z88dk sollte gemäß der Anleitung aus dem Repository erfolgen, für Ubuntu-Benutzer möchte ich jedoch eine Funktion anmerken – Wenn Sie bereits Compiler für Z80 aus Deb-Paketen installiert haben, sollten Sie diese entfernen, da z88dk aufgrund der Inkompatibilität der Toolchain-Compiler-Versionen standardmäßig auf sie zugreift.< /p>
Hallo Welt
Hello World zu schreiben ist sehr einfach:
#include
void main()
{
printf("Hello World");
}
Es ist noch einfacher, eine Tap-Datei zu kompilieren:
zcc +zx -lndos -create-app -o helloworld helloworld.c
Verwenden Sie zum Ausführen einen beliebigen ZX Spectrum-Emulator mit Tap-Datei-Unterstützung, zum Beispiel online:
http://jsspeccy.zxdemo.org/
Zeichnen Sie im Vollbildmodus auf das Bild
tl;dr Bilder werden in Kacheln gezeichnet, Kacheln mit einer Größe von 8×8 Pixeln, die Kacheln selbst sind in die Schriftart Spectrum eingebaut, dann wird das Bild als Linie aus den Indizes gedruckt.
Die Sprite- und Kachelausgabebibliothek SP1 gibt Kacheln mithilfe von UDG aus. Das Bild wird in eine Reihe einzelner UDGs (Kacheln) übersetzt und dann mithilfe von Indizes auf dem Bildschirm zusammengesetzt. Beachten Sie, dass UDG zum Anzeigen von Text verwendet wird. Wenn Ihr Bild einen sehr großen Satz von Kacheln enthält (z. B. mehr als 128 Kacheln), müssen Sie über die Grenzen des Satzes hinausgehen und das Standardspektrum löschen Schriftart. Um diese Einschränkung zu umgehen, habe ich eine Basis von 128 – 255 durch Vereinfachung der Bilder, wobei die ursprüngliche Schriftart erhalten bleibt. Über die Vereinfachung der Bilder unten.
Um Vollbildbilder zu zeichnen, müssen Sie sich mit drei Dienstprogrammen ausrüsten:
Gimp
img2spec
png2c-z88dk
Für echte ZX-Männer, echte Retro-Krieger gibt es eine Möglichkeit: Öffnen Sie einen Grafikeditor mit der Spectrum-Palette, kennen Sie die Funktionen der Bildausgabe, bereiten Sie sie manuell vor und laden Sie sie mit png2c-z88dk oder png2scr hoch.< /p>
Der einfachere Weg – Nehmen Sie ein 32-Bit-Bild, ändern Sie die Anzahl der Farben in Gimp auf 3-4, bearbeiten Sie es leicht und importieren Sie es dann in img2spec, um nicht manuell mit Farbbeschränkungen zu arbeiten. Exportieren Sie PNG und konvertieren Sie es mit PNG2C in ein C-Array. z88dk.
Sie sollten bedenken, dass für einen erfolgreichen Export jede Kachel nicht mehr als zwei Farben enthalten darf.
Als Ergebnis erhalten Sie eine h-Datei, die die Anzahl der eindeutigen Kacheln enthält. Wenn es mehr als ~128 sind, vereinfachen Sie das Bild in Gimp (erhöhen Sie die Wiederholbarkeit) und führen Sie den Exportvorgang über ein neues aus .
Nach dem Export laden Sie buchstäblich die „Schriftart“ aus den Kacheln und drucken den „Text“ aus den Kachelindizes auf den Bildschirm. Unten ist ein Beispiel aus der Render-„Klasse“:
// грузим шрифт в память
unsigned char *pt = fullscreenImage->tiles;
for (i = 0; i < fullscreenImage->tilesLength; i++, pt += 8) {
sp1_TileEntry(fullscreenImage->tilesBase + i, pt);
}
// ставим курсор в 0,0
sp1_SetPrintPos(&ps0, 0, 0);
// печатаем строку
sp1_PrintString(&ps0, fullscreenImage->ptiles);
Sprites auf dem Bildschirm zeichnen
Als nächstes beschreibe ich eine Methode zum Zeichnen von Sprites mit 16 x 16 Pixeln auf dem Bildschirm. Ich bin nicht zur Animation und zum Ändern der Farben gekommen, weil… Es ist trivial, dass mir schon zu diesem Zeitpunkt, wie ich annehme, der Speicher ausgegangen ist. Daher enthält das Spiel nur transparente monochrome Sprites.
Wir zeichnen ein monochromes PNG-Bild 16×16 in Gimp, dann übersetzen wir es mit png2sp1sprite in eine ASM-Assembly-Datei, im C-Code deklarieren wir Arrays aus der Assembly-Datei und fügen die Datei in der Assembly-Phase hinzu.< /p>
Nach der Deklaration der Sprite-Ressource muss diese an der gewünschten Position zum Bildschirm hinzugefügt werden. Nachfolgend finden Sie ein Beispiel für den Code für die „Klasse“ des Spielobjekts:
struct sp1_ss *bubble_sprite = sp1_CreateSpr(SP1_DRAW_MASK2LB, SP1_TYPE_2BYTE, 3, 0, 0);
sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2, SP1_TYPE_2BYTE, col2-col1, 0);
sp1_AddColSpr(bubble_sprite, SP1_DRAW_MASK2RB, SP1_TYPE_2BYTE, 0, 0);
sp1_IterateSprChar(bubble_sprite, initialiseColour);
Anhand der Namen der Funktionen können Sie die Bedeutung von – Weisen Sie dem Sprite Speicher zu, fügen Sie zwei 8×8 Spalten hinzu und fügen Sie eine Farbe für das Sprite hinzu.
Die Position des Sprites wird in jedem Frame angezeigt:
sp1_MoveSprPix(gameObject->gameObjectSprite, Renderer_fullScreenRect, gameObject->sprite_col, gameObject->x, gameObject->y);
OOP emulieren
In C gibt es keine Syntax für OOP. Was sollten Sie tun, wenn Sie es trotzdem wirklich wollen? Sie müssen Ihren Geist verbinden und sich darüber im Klaren sein, dass es so etwas wie OOP-Ausrüstung nicht gibt; letztendlich kommt es auf eine der Maschinenarchitekturen an, in denen es einfach kein Konzept eines Objekts und andere damit verbundene Abstraktionen gibt.< /p>
Diese Tatsache hat mich sehr lange daran gehindert zu verstehen, warum OOP überhaupt benötigt wird, warum es notwendig ist, es zu verwenden, wenn am Ende alles auf Maschinencode hinausläuft.
Nachdem ich jedoch in der Produktentwicklung gearbeitet hatte, entdeckte ich die Vorzüge dieses Programmierparadigmas, vor allem natürlich die Entwicklungsflexibilität, Code-Schutzmechanismen mit dem richtigen Ansatz, die Reduzierung der Entropie und die Vereinfachung der Teamarbeit. Alle oben genannten Vorteile basieren auf drei Säulen: Polymorphismus, Kapselung, Vererbung.
Bemerkenswert ist auch die Vereinfachung der Lösung von Problemen im Zusammenhang mit der Anwendungsarchitektur, da 80 % der Architekturprobleme im letzten Jahrhundert von Informatikern gelöst und in der Literatur zu Entwurfsmustern beschrieben wurden. Als Nächstes beschreibe ich Möglichkeiten, C eine OOP-ähnliche Syntax hinzuzufügen.
Es ist bequemer, C-Strukturen als Grundlage für die Speicherung von Daten einer Klasseninstanz zu verwenden. Natürlich können Sie einen Bytepuffer verwenden und Ihre eigene Struktur für Klassen und Methoden erstellen, aber warum das Rad neu erfinden? Schließlich erfinden wir die Syntax bereits neu.
Klassendaten
Beispiel für GameObject-„Klassen“-Datenfelder:
struct GameObjectStruct {
struct sp1_ss *gameObjectSprite;
unsigned char *sprite_col;
unsigned char x;
unsigned char y;
unsigned char referenceCount;
unsigned char beforeHideX;
unsigned char beforeHideY;
};
typedef struct GameObjectStruct GameObject;
Speichern Sie unsere Klasse als „GameObject.h“, fügen Sie „GameObject.h“ an der richtigen Stelle ein und verwenden Sie sie.
Klassenmethoden
Unter Berücksichtigung der Erfahrung der Entwickler der Objective-C-Sprache besteht die Signatur einer Klassenmethode aus Funktionen in einem globalen Bereich. Das erste Argument ist immer die Datenstruktur, gefolgt von den Methodenargumenten. Unten finden Sie ein Beispiel für eine „Methode“ der „Klasse“ GameObject:
void GameObject_hide(GameObject *gameObject) {
gameObject->beforeHideX = gameObject->x;
gameObject->beforeHideY = gameObject->y;
gameObject->y = 200;
}
Der Methodenaufruf sieht so aus:
GameObject_hide(gameObject);
Konstruktoren und Destruktoren werden auf die gleiche Weise implementiert. Es ist möglich, einen Konstruktor als Allokator und Feldinitialisierer zu implementieren, aber ich bevorzuge es, die Objektzuweisung und -initialisierung separat zu steuern.
Mit dem Gedächtnis arbeiten
Manuelle Speicherverwaltung des Formulars mit malloc und frei in neue und gelöschte Makros eingebunden, passend zu C++:
#define new(X) (X*)malloc(sizeof(X))
#define delete(X) free(X)
Für Objekte, die von mehreren Klassen gleichzeitig verwendet werden, wird eine halbmanuelle Speicherverwaltung basierend auf Referenzzählung implementiert, nach dem Vorbild und der Ähnlichkeit des alten Objective-C Runtime ARC-Mechanismus:
void GameObject_retain(GameObject *gameObject) {
gameObject->referenceCount++;
}
void GameObject_release(GameObject *gameObject) {
gameObject->referenceCount--;
if (gameObject->referenceCount < 1) { sp1_MoveSprAbs(gameObject->gameObjectSprite, &Renderer_fullScreenRect, NULL, 0, 34, 0, 0);
sp1_DeleteSpr(gameObject->gameObjectSprite);
delete(gameObject);
}
}
Daher muss jede Klasse die Verwendung eines gemeinsam genutzten Objekts mithilfe von „Retain“ deklarieren und das Eigentum durch „Release“ freigeben. Die moderne Version von ARC verwendet automatische Aufbewahrungs-/Freigabeaufrufe.
Ton!
Der Spectrum verfügt über einen Hochtöner, der 1-Bit-Musik wiedergeben kann; damalige Komponisten konnten bis zu 4 Tonkanäle gleichzeitig wiedergeben.
Spectrum 128k enthält einen separaten Soundchip AY-3-8910, der Tracker-Musik abspielen kann.
Für die Nutzung des Hochtöners im z88dk wird eine Bibliothek angeboten
Was noch gelernt werden muss
Ich war daran interessiert, das Spectrum kennenzulernen, das Spiel mit dem z88dk zu implementieren und viele interessante Dinge zu lernen. Ich muss noch viel lernen, zum Beispiel den Z80-Assembler, da er mir ermöglicht, die volle Leistung des Spectrum zu nutzen, mit Speicherbänken zu arbeiten und mit dem Soundchip AY-3-8910 zu arbeiten. Ich hoffe, nächstes Jahr am Wettbewerb teilnehmen zu können!
Links
https://rgb.yandex
https://vk.com/sinc_lair
https://www.z88dk.org/forum/
Quellcode
https://gitlab.com/demensdeum/ zx-projects/tree/master/interceptor2020
