Puzzle 001

1. Schritt

C64 Studio

Wie erwähnt, betrachten wir den ersten Level zunächst als eigenes Spiel und wir entwickeln ihn linear. Wir starten mit einem einfachen Titelbild und einer simplen Joystickabfrage.

Das Spiel soll uns, wie wir es von sovielen gewohnt sind, mit einen Titelbild begrüßen. Da wir noch keine eigenen Zeichensätze oder hochauflösenden Grafiken beherrschen, beschränken wir uns zunächst einfach auf den Standard-Zeichensatz aus dem Char-ROM.

Falls noch nicht geschehen, wäre es jetzt an der Zeit das C64 Studio zu öffnen.

Wie ihr eurer Projekt ab jetzt mit dem C64 Studio verwaltet, bleibt euch überlassen.
Ihr könnt z. B.

  • Jeden Level als einzelne Solution mit nur einem Projekt anlegen.
  • Für alle Level eine Solution mit einem einzelnen Projekten einsetzen.
  • Eine Solutionen für das gesamte Spiel, mit jeweils einem Projekt je Level verwenden.

Ich verwende übrigens die letztgenannte Struktur, die ihr daher auch in den jeweiligen Downloads und auf Screenshots findet.

Solution anlegen

Wenn ihr eine Solution erzeugt, dann wird übrigens auch immer ein Projekt mit dem selben Namen angelegt. Da ich dieses LOVE-Projekt aktuell nicht benötige, entferne ich es am Ende aus der Solution.

Character Screen Editor

Fügt anschließend einen „Character Screen“ zum Projekt hinzu und nennt diesen „Title.charscreen“.

Character Screen hinzufügen

Es sollte sich automatisch der Character Screen Editor öffnen. Falls dies nicht der Fall ist, klickt einfach doppelt auf die eben hinzugefügte Datei „Title.charscreen“.

Das Titelbild

Auf der linken Seite findet ihr den „Bildschirm“ des C64 mit 40 Zeichen x 25 Zeilen. Dort könnt ihr jetzt mit der linken Maustaste das gewählte Zeichen aus dem Zeichensatz (s. rechts) platzieren. Die Zeichenfarbe könnt direkt unterhalb der Zeichenübersicht auswählen. Spielt ruhig etwas mit den angebotenen Optionen auf der rechten Seite, um euch an den Editor zu gewöhnen. Entwerft dann das Titelbild. Ihr solltet dazu den Mode auf HiRes stellen und auch wieder 40×25 als Größe einstellen! Wir verwenden den Standardmodus und können so je Zeichen nur eine Farbe festlegen. Vergesst nich eure Arbeit zu speichern.

Eurer Titel könnte dann z. B. so aussehen:

Titelbild

Fangen wir jetzt mit dem Programm an. Fügt dazu eine ASM-Datei mit dem Namen „Puzzle.asm“ zum Projekt hinzu und macht sie zur Sicherheit zum aktiven Element.

Eine ASM-Datei hinzufügen.
Das Programm

Wir legen zu Beginn einige Konstanten fest, wer mag kann natürlich auf die Konstanten verzichten und die jeweiligen Werte direkt im Source eingeben. Ich finde es aber einfacher den Source zu verstehen, wenn man auf etwas wie cmp #JOY_FIRE statt cmp #$16 stößt. Man kann direkt erkennen, worum es an der jeweiligen Stelle geht. Außerdem verwende ich ab jetzt auch immer die !to-Anweisung, um den Namen der .prg-Datei über den Quellcode festzulegen.

Anschließend folgt wieder unsere BASIC-Startzeile, ich habe sie hiermal etwas anders implementiert. Hinter main befindet sich das bisher sehr kurze Hauptprogramm 😉 .

Wie angedroht, setze ich voraus, dass ihr die Mnemonics kennt und werde hier jetzt nicht mehr jede Zeile einzeln besprechen. In den ersten Sourcen kommentiere ich aber noch relativ viel und erkläre die Funktion dann auch nochmal im Beitrag.
Sind Punkte unklar, umständlich, verquast oder gar falsch erklärt, dann scheut bitte nicht davor zurück, mir dies mitzuteilen (z. B. über den Feedback-Button ganz links).

Machen wir weiter und hangeln uns durch die einzelnen Funktionen. Beginnen wir mit showScreen_Title immerhin ist dies die einzige, die das Hauptprogramm zur Zeit aufruft.

Mini-Exkurs: Lokale Label
Als erstes fällt euch evtl. die !zone-Anweisung auf, die habe ich bisher kaum verwendet. Damit grenzt ihr u. a. die Sichtbarkeit von sog. lokalen Labeln ein. Ein solches Label beginnt mit einem . wie z. B. bei .wait weiter unten in der Funktion. Lokale Label erlauben es euch, häufig benötigte Label (z. B. .loop, .wait oder .skip) immer gleich benennen zu können. Wenn wir eine neue Zone starten, können wir dort z. B. auch wieder den Labelnamen .wait verwenden, ohne dass es zu einem Fehler bei der Erstellung kommt. Ohne diese Möglichkeit, müsstet ihr euch jedes Mal einen eindeutigen Labelnamen ausdenken, was häufig in wait, wait2 oder gar wait34a endet. Daher findet ihr nahezu bei jeder Unterroutine eine !zone-Anweisung. Da die Label nur lokal innerhalb einer Zone sichtbar sind, könnt ihr diese natürlich nicht aus anderen Zonen anspringen. Falls ihr dies benötigt, müsst ihr doch wieder ein normales Label verwenden.

Die Funktion showScreen_Title dient dazu, unseren Titelbildschirm anzuzeigen und auf die Wahl des Joysticks zu warten. Das ist schon etwas ungewöhnlich, die Spiele von damals haben meistens vorausgesetzt, dass sich der Joystick im vorgegebenen Port befand, sie ermöglichten häufig keine automatische Erkennung.

Zunächst wird die Farbe schwarz in den Akku geholt und dann in die entsprechenden VIC-Register für Rahmen- und Hintergrundfarbe geschrieben. Wer andere Farben möchte, muss das hier ändern.
Anschließend löschen wir zur Sicherheit den aktuell gewählten Joystick, indem wir INPUT_NONE nach inputDevice schreiben.
Dann holen wir die Adresse unseres Titelbildes ins X- und Y-Register und rufen das Unterprogramm drawScreen auf, um das Bild auf dem Bildschirm anzuzeigen.
Zurück von der Anzeige, warten wir dann solange, bis sich der Spieler für einen Joystick entschieden hat.

Die Funktion drawScreen ermöglicht es uns, ein beliebiges mit dem Char Screen Designer erstelltes „Bild“ auszugeben. Quelle und Ziel sind dabei über die Zero-Page frei einstellbar, hier ist das Ziel aber fest, da es über Konstanten gesetzt wird. Da wir hier in unserem Spiel zum ersten Mal auf die Zero-Page zugreifen, möchte ich die Gelegenheit nutzen und darauf hinweisen, dass wir jetzt intensiven Gebrauch von der Zero-Page machen. Wie ihr bei den Mnemonics seht, sind die Befehle mit Zero-Page-Adressierung kürzer und schneller, als ihre absoluten Gegenstücke. Die erste Hälfte der Zero-Page wird hauptsächlich von BASIC benutzt (mit Ausnahme der „berühmten“ Adressen $00 & $01). Wir beginnen bei $02 mit unserer Belegung der Zero-Page. Es ist absehbar, dass ein Rücksprung zum BASIC irgendwann in einem Fehler endet, immerhin zerstören wir die dazugehörigen Zero-Page-Bereiche. Aber die wenigen freien Bytes auf der Zero-Page werden uns auf Dauer nicht reichen.

Nun wieder zur Funktion…

Zunächst speichern wir die im X- und Y-Register übergebene Quell-Adresse auf der Zero-Page.
Da der Screen 40*25 Zeichen hat, müssen wir zur übergebenen Startadresse der Farben 1000 addieren, um den Beginn der Zeichen zu erhalten, der Screen Designer speichert zunächst die 1000 Farbinformationen, danach kommen die Zeichen. Wir kopieren daher X (hier befindet sich das LSB) in den Akku, löschen das Carry-Flag und addieren das LSB von 1000, also $E8. Das Ergebnis fürs LSB merken wir uns direkt auf der Zero-Page. Dann kopieren wir Y (das MSB) in den Akku und addieren hierzu das MSB von 1000, was $03 entspricht. Kam es bei der ersten Addition (die mit dem LSB) zu einem Übertrag, dann wird dieser dank adc mit addiert. Auch das errechnete MSB wird in die Zero-Page geschrieben.
Sobald wir die Adresse für die Zeichen haben, setzt wir das Ziel für Zeichen und Farben zurück. Diese werden in der Kopierschleife verändert, also müssen wir erstmal wieder für die richtigen Ausgangswerte sorgen. Hier könnte man ansetzen, wenn man unterschiedliche Zieladressen verwenden möchte, wir benutzen einfach die Standardwerte, für die wir zu Beginn als Konstanten festgelegt haben.
Bevor das eigentliche Kopieren beginnt, müssen wir das Y-Register auf null setzten (warum sehen wir gleich) und tragen ins X-Register die Anzahl der zu kopierenden Pages ein. Wir benötigen vier Pages, da wir 1000 BYTES kopieren wollen (3*256 + 232 BYTES). Da wir später mit bpl prüfen, nehmen wir hier eine 3 (die Null hat zwar kein Vorzeichen, wird beim bpl aber als positiv gewertet – sie ist ja auch nicht negativ).
Hinter dem lokalen Label .pageLoop (in unserer Hauptschleife wird für jede weitere Page hierhin zurückgesprungen) sehen jetzt den Grund, weshalb eben Y = 0 gesetzt werden musste. Beim Schleifenanfang prüfen wir, ob wir bei der letzten Page angelangt sind. Wir zählen X zwar 3, 2, 1, 0 runter, kopieren aber in der Reihenfolge Page 1, 2, 3 und 4. Da jede Page 256 Zeichen beinhaltet und wir mit Y = 0 beginnen, um diese 256 Zeichen zu kopieren, müssen wir bei der letzten Page darauf achten, dass ja nach 1000 Zeichen Schluß ist. Also kopieren wir beim letzten Durchlauf nur noch 232 Zeichen. Dazu kopieren wir zunächst das Zeichen und die Farbe von Y = 0 (die beiden lda / sta Befehle direkt hinter bne .loop) und setzten dann mit ldy #$ff-$18 Y für den Rest 231.
Die hauptsächliche Kopierarbeit wird bei .loop erledigt. Dort holen wir uns erstmal das Zeichen von der Quell-Adresse und speichern es an der Ziel-Adresse, das Gleiche machen wir für die Farbe. Wir verwenden hier die Y-nach-indizierte Adressierung. Y wird dabei für die Pages 1 bis 3 immer von 0 herunter gezählt. Da das dey vor der bne Prüfung geschieht, kippt beim ersten Mal Y von #$00 auf #$ff und wir kopieren somit 256 Zeichen. Dadurch wird Y auch automatisch wieder auf Null gestellt und wir müssen das für die nächsten Pages nicht manuell erledigen.
Wurden alle Bytes der Page kopiert, dann erhöhen wir die Quell- und Ziel-Adressen für Zeichen und Farben auf der Zero-Page, so dass die nächste Page verwendet wird. Dazu addieren wir einfach 1 zu allen MSBs der betroffenen Adressen.
Zum Schluß wird noch X um eins verringert und solangen es nicht negativ ist, kopieren wir die nächste Seite.

Nach der Beendigung dieser etwas größeren Funktion, laden wir wieder in showScreen_Title und warten auf die Wahl des Joysticks.

Jetzt ziehe ich die kleine Funktion joystickInput vor, die erst gleich von der nächsten Funktion verwendet wird.

Wie ihr seht, ist diese Funktion wirklich sehr kurz, wir holen einfach den aktuellen Wert des CIA1_<Port> – Registers in den Akku und speichern ihn unter inputState. Etwas Ähnliches kann man bei der Tastatur-Matrix sehen, dort finden wir auch die entsprechenden Werte für die Joystickpositionen. Die obige Routine hat noch ein kleines Problem, man kann auch mit der Tastatur den Feuerknopf auslösen. Drückt ihr z. B. SPACE wird das auch als Feuer erkannt. Darum kümmern wir uns aber in einem späteren Beitrag, sobald wir den Joystick komplett abfragen, für den Moment soll das so reichen.

Den Grund weshalb ich eben CIA1_<Port> geschrieben haben, sehen wir sofort in der nächsten Funktion:

In der Funktion selectInputDevice prüfen wir, ob der Spieler den Feuerknopf von Joystick-1 oder 2 gedrückt hat. Um festzulegen, mit welchem Joystick er spielen möchte. Wurde der Feuerknopf betätigt, dann speichern wir den gewählten Joystick in inputDevice und warten bis der Feuerknopf losgelassen wurde.
Schauen wir uns die Prüfung mal für den Joystick in Port-2 an:
Wir holen zunächst das LSB vom CIA1_A in den Akku und speichern diesen Wert in der Funktion joystickInput direkt hinter dem lda-Befehl.
So etwas gilt heute als Todsünde, bzw. ist nicht mehr machbar: Ein Programm, das sich selbst verändert. Da die heutigen Prozessoren einen Cache besitzen, kann man diese Technik kaum noch anwenden, außerdem unterscheidet z. B. Windows zwischen Programm- und Anwendungsspeicher. Als letztes wissen wir unter Windows auch gar nicht mehr wo und wie unserer Programm im Speicher liegt.
Aber auf dem C64 ist alles anders und wir können diese Technik verwenden. Wir ändern hier also abhängig von der Wahl des Spielers die Adresse des benötigten CIA1-Ports um den Joystick abzufragen. Nach dem Ändern springen wir zu joystickInput und prüfen dann, ob der Feuerknopf gedrückt wurde. Da joystickInput den aktuellen Status in den Akku geholt hat, bevor dieser unter inputDevice abgelegt wurde, müssen wir zur Prüfung nur noch ein and #JOY_FIRE einsetzen. Ist das Ergebnis null, ist der Feuerknopf vom Joystick in Port-2 nicht gedrückt und wir springen zu Prüfung von Port-1. Ist er aber gedrückt, dann speichern wir den gewählten Port zur Sicherheit unter inputDevice und warten abschließend solange, bis der Knopf wieder losgelassen wurde. Jetzt ist in joystickInput hinter dem lda auch dauerhauft der richtige CIA1-Port für die weiteren Abfragen gespeichert.

Unser Programm wird nach der Joystick-Wahl wieder zurück zum BASIC springen. Das ist wie erwähnt nicht ganz unproblematisch, da wir die Zero-Page geändert haben, sollte hier aber noch funktionieren.

Zum Schluß folgen noch unsere Speicherstellen für den gewählten Joystick, die letzte Eingabe und unserer Titelbild.

Neu sollte für uns nur noch die Zeile !media "Title.charscreen",charcolor sein. Hiermit weisen wir den Assembler des C64 Studios an, hinter dem Label screenTitle den Inhalt der Datei „Title.charscreen“ einzufügen. Wir könnten auch die Daten vom direkt als Bytes einfügen oder in eine .bin-Datei exportieren und dann einbinden. Das hat aber den Nachteil, dass wir bei jeder Änderung die Werte neu exportieren müssen.

Ich möchte nochmal darauf hinweisen, dass ich auch erst wieder mit der Programmierung des C64 begonnen habe, daher kann (und wird) es früher oder später passieren, dass etwas was auf den ersten Blick klappt, sich später als unpraktisch oder gar falsch herausstellt und somit geändert werden muss.

Laßt mich das mal an zwei Beispielen aus diesem Abschnitt erklären.

  • Die Joystickabfrage wird von uns manuell vorgenommen. Wir müssen also daran denken, zur Funktion zu springen. Evtl. wäre es besser, diese in relmäßigen Abständen automatisch auszuführen, erst recht, wenn man z. B. feststellen muss, wie lange ein Knopf bereits gedrückt ist. Dazu könnte man einen Interrupt verwenden, aber das kennen wir ja noch nicht.
  • Unser Titelbild verschlingt horrende viel Speicher. Wenn wir so weiter machen reichen die 64KB bald nicht mehr aus. Jedes Bild benötigt 2*40*25 = 2000 Bytes, also knapp 2KB! Schauen wir uns aber das Bild mal an, dann sehen wir, dass das „Nichts“ überwiegt. Wir müssen uns also überlegen, wie wir den Speicherbedarf reduzieren können. Das soll uns beim Puzzle-Spiel aber noch nicht beschäftigen. Neben dem Startbild brauchen wir ja nur noch unser eigentliches Spielfeld.

So das war der erste Schritt, wie angekündigt, könnt ihr das Programm jetzt herunterladen.

Im ZIP findet ihr das Projekt, eine .prg und .d64 Datei.

OK, ich gebe es ja zu, das ist jetzt nicht sonderlich spektakulär gewesen, aber irgendwo müssen wir ja beginnen. Im nächsten Beitrag verwenden wir dann die ersten Sprites.


Schrott!!Naja...Geht so...Ganz gut...SUPER! (10 Bewertungen | Ø 4,80 von 5 | 96,00%)

Loading...


ZurückWeiter

3 Gedanken zu „Puzzle 001“

  1. c64Studio-Version
    —————
    Um das komfortablere c64Studio zu verwenden klickt man auf
    WINDOW|CharScreenEditor
    Im Karteireiter PROJECT wählt man in der ExportData-ComboBox CharactersThenColors und
    malt etwas in die schwarze TextBox im Karteireiter Screen.
    Dann klickt man wieder unter Projekt auf AsAssembly und kopiert die Hex-Zeichen in der TextBox darunter. Diese fügt man unter dem Label screentitel: in puzzle.asm ein und kommentiert ;!bin „title.sdd“,1,1 aus.

    Nun kann man den Textscreen auch unter dem leistungsfähigeren c64Studio bewundern und diesen tollen Programmierkurs fortsetzen.

  2. showScreen_Title müsste es nach main: heissen, also mit großem „S“ und bei der Benutzung von c64Studio nach einfügen von !byte statt byte usw. ist mir aufgefallen, dass „title.ssd“ nicht mehr richtig dargestellt wird. Der Screeneditor von c64Studio speichert die Entwürfe auch nicht richtig. Könnte hierfür noch eine Anpassung hier veröffentlicht werden?
    lda CIA_1A habe ich bei eigenen Versuchen über die (Zeropage),Y geladen damit kein selbstmodifizierender Code benutzt wird oder wie könnte man die Konstante CIA_1A per adc ändern und die wieder indirekt referenzieren also wieder lda CIA_1A aber nun mit $DC01?

    1. Alle Beispiele sind mit dem oben links angezeigten Assembler erstellt. Eine Anpassung an andere Assembler wird es nicht geben. Wer das C64 Studio verwenden will (das ziehe ich mittlerweile ja auch vor), der möge einfach einen Blick auf Vom CBM prg Studio zum C64 Studio werfen. Beim Einsatz anderer Assembler (z. B. Kickass, Turbo Assembler usw.) ist jeder auf sich gestellt. Das mit showScreen_Title ist zwar unschön, da es beim CBM prg Studio erlaubt ist, stellt das aber kein Problem dar (habe es trotzdem angepasst).

      Wenn du für die Joystickabfrage keinen sich selbstverändernden Code möchtest, musst du dir eben anders behelfen. Entweder (wie von dir vorgeschlagen) durch andere Adressierungsarten, durch eine zusätzliche Variable im Speicher, ob Joystick 1 oder 2 genommen werden soll und entsprechende Prüfungen oder man macht es so, wie fast alle Spiele und lässt nur einen Joystick zu.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Protected by WP Anti Spam