Sprite-Scroller

Eine Laufschrift mit Sprites

C64 Studio, AMCE & TASM

Ja, ich weiß, bisher habe ich das Scrolling noch nicht angerührt und auch dieses Mal drücke ich mich davor und zeige euch stattdessen, wie ihr eine Laufschrift mit Sprites erzeugen könnt.

In Demos seht ihr häufig Laufschriften, die größer als die normalen 8×8 Pixel sind. Erzeugen könnt ihr solche Texte z. B., indem ihr den Zeichensatz so ändert, dass für einen Buchstaben mehrere Zeichen verwendet werden oder mit dem hier vorgestellten Sprite-Scroller. Dieser erlaubt es außerdem die Laufschrift in den Rahmen zu verschieben.

Ihr solltet bereits wissen, wie man auf das Char-ROM zugreift. Dieses wird u. a. in VIC-II: Eigener Zeichensatz beschrieben, schaut es euch bei Bedarf einfach mal an.

Die Idee

Um jetzt eine Laufschrift mit Hilfe von Sprites zu erzeugen, brauchen wir natürlich erstmal ein „Sprite-Laufband“. Dazu positionieren wir einfach sieben Sprites nahtlos nebeneinander. Diese Sprites werden in ihrer Breite verdoppelt und füllen somit den sichtbaren Bereich (der ist bekanntlich 320 Pixel breit) vollständig aus (7 * 24 * 2 = 7 * 48 = 336 Pixel). Dann holen wir uns das Aussehen des jeweiligen Zeichens aus dem Char-ROM und schieben es von rechts nach links durch die Sprites, so scrollt dann der Text über den Bildschirm.

Fangen wir doch einfach mal an, dann wird alles etwas klarer. Ich möchte im Vorwege noch darauf hinweisen, dass man den selben Effekt auch über andere Wege erreichen kann. Hier geht es aber wieder ums Verständnis und nicht um die optimale Lösung.

Das „Laufband“ vorbereiten

Die Initialisierung sollte kein Problem sein. Wir setzen sieben, in der Breite verdoppelte, Sprites, am oberen Bildschirmrand lückenlos aneinander.

Um die VIC-II-Probleme mit dem RAM-Bereich ab $1000 zu umgehen (schaut ggf. nochmal bei VIC-II: Speicherbereiche festlegen nach), legen wir die Spritedaten dieses Mal einfach vor unserem Hauptprogramm ab! Im Quellcode definieren wir nur acht Label sprite0...7, um leichter auf die einzelnen Spritedaten zugreifen zu können. Den Datenbereich füllen wir erst später! Die SYS-Anweisung zum Programmstart ändern wir dementsprechend auf 2018 SYS 2624, sodass direkt zur ersten Anweisung bei main gesprungen wird.

Dann richten wir hinter main als erstes wieder unseren Rasterzeilen-Interrupt ein.

Alles kalter Kaffee, daher spare ich mir weitere Worte und wir fahren direkt mit der Einrichtung der Sprites fort.

Der Block sieht zwar sehr umfangreich aus, er ist aber nur so lang, da wir hier immer die Werte für sieben Sprites setzen. Als erstes ermitteln wir den Beginn der Spritedaten für die Sprite-Pointer. Dann werden die Y- und X-Position gesetzt, dabei beachten wir, dass Sprite 5 und 6 eine X-Position haben, die über 255 liegt. Außerdem wird noch die Farbe aller Sprites auf gelb geändert. Zum Schluß verdoppeln wir die sieben Sprites in Breite (wichtig!) und Höhe (das ist eigentlich egal, ihr könnt auch darauf verzichten) und schalten sie sichtbar.

Das Programm bleibt zum Schluß einfach in einer Endlosschleife hängen.

Der Rasterzeileninterrupt ist aktuell nur ein Platzhalter, dort wird bisher nur der IRQ bestätigt und die Routine dann gleich wieder verlassen.

Dann fehlt abschließend nur noch die Routine clearSprites. Damit wir beim ersten Start etwas sehen, lasst uns doch erstmal alle ungeraden Sprites mit #$00 tatsächlich löschen und alle geraden mit #$ff füllen, damit diese zur Kontrolle sichtbar sind.

Jetzt sollte das Programm ausführbar sein. Da wir eben jedes zweite Sprite mit $ff statt $00 initialisiert haben, können wir nun die Position unseres Laufbandes kontrollieren…

Das Laufband (Sprite 0, 2, 4 und 6 sind gelb).
Das Laufband (Sprite 0, 2, 4 und 6 sind gelb).

Da ist also unser Laufband. Die Sprites 0, 2, 4 und 6 werden wie geplant, direkt als gelbe Kästen angezeigt. Dies dient nur zur Kontrolle für unsere nächsten Schritte, später löschen wir natürlich alle Sprites mit $00!

Die Spritedaten durchreichen

Wir wollen gleich ins unsichtbare Sprite-7 ein neues Zeichen aufnehmen und dann diese Spritedaten durch alle Sprites schieben. Durch dieses Verschieben erhalten wir dann unser Scrolling. Diese Funktion können wir jetzt schon einbauen und testen. Wir verschieben einfach einen Teil der gelben Blöcke. Ein Standard-Zeichen im Char-ROM ist bekanntlich 8*8 Pixel groß, ein HiRes-Sprite bietet uns aber Platz für 24*21 Pixel. Wir müssen für unsere Laufschrift also nur die ersten acht Zeilen des Sprites beachten. Da ein HiRes-Sprite 24 Pixel breit ist, passen dort drei Zeichen hinein, das müssen wir beim verschieben beachten. Die Routine zum shiften ist sehr simpel.

Wie ihr seht, initialisieren wir unseren Schleifenzähler (X-Register) mit den eben ermittelten 8 Zeilen und 3 Bytes je Zeile. Da unsere Schleife läuft, solange die Prüfung positiv ist, rechnen wir aber nur 3*7! Dann löschen wir das Carry-Flag und rotieren nun einfach Zeile für Zeile alle Spritedaten durch. Dabei verschieben wir also die Daten jeweils um ein Bit, wobei das herausfallende Bit übers Carry-Flag zum nächsten Datenbyte weitergereicht wird. Mit dem verschieben beginnen wir im letzten Byte (bildlich ganz rechts) und verschieben es dann rückwärts durch alle Sprites. Sobald die Daten für Sprite-0 verschoben wurden, nehmen wir uns die nächste Zeile vor, bis alle 8 abgearbeitet wurden. Da wir immer drei Bytes aufeinmal verschieben, verringern wir das X-Register am Ende auch dreimal. Wurde alles abgearbeitet, geht es zurück zum Aufrufer.

Fügt jetzt direkt hinter dem Label rasterIrq einfach den Aufruf der neuen Funktion jsr shiftAll ;alle Spritedaten 'shiften' ein und startet das Programm erneut.

Ungefähr das obere Drittel der Sprites wandert nach links.
Ungefähr das obere Drittel der Sprites wandert nach links.

Wie ihr seht, wandert der obere Teil der Sprites nach links aus dem Bildschirm. Sobald die Teil-Blöcke verschwunden sind, kommt aber erstmal nichts Neues mehr nach. Sorgen wir im kommenden Schritt also dafür, dass wir den ersten Buchstaben unserer Laufschrift einfügen.

Zeichen in die Spritedaten kopieren

Wir wollen nun das erste Zeichen unseres Textes ins Laufband einfügen und durchscrollen lassen. Fügt die kommenden Zeilen bitte direkt hinter dem Aufruf jsr shiftAll von eben ein.

Die ersten beiden Zeilen (gelbe Markierung) und das gleich kommende Label exit benötigen wir nur für unseren nächsten Test. Diese müssen später wieder gelöscht werden. Da uns noch die Synchronisation fehlt, wollen wir erstmal nur das erste Zeichen unserer Laufschrift ausgeben!

Danach erhöhen wir einfach den Zeiger aufs nächste Zeichen infotextPos um eins und holen den Wert ins X-Register. Dann lesen wir das benötigte Zeichen in den Akku ein und prüfen, ob es $00 ist. Sollte dies der Fall sein, wurde das Textende erreicht und wir stellen den Wert zurück auf das erste Zeichen, damit die Laufschrift wieder von vorne beginnt, haben wir keine $00 geht es direkt bei getChar weiter.

Bei getChar geben wir das zulesende Zeichen erstmal in der linken oberen Ecke des Bildschirms aus. Das dient nur der Kontrolle und Fehlersuche und kommt später wieder raus. Wir sichern unser Zeichen dann im X-Register und legen auf der Zero-Page ZPHELPADR die Startadresse des Char-ROMs $d000 ab. Um den Anfang unseres Zeichens zu finden, addieren wir dann solange 8 (für Bytes je Zeichen) auf diese Adresse, bis wir beim gesuchten Zeichen angekommen sind. Dabei dient das X-Register als Schleifenzähler für nextChar, in X haben wir uns ja eben unser Zeichen gemerkt.

Jetzt wissen wir, wo das Zeichen zufinden ist, ZPHELPADR zeigt direkt darauf und wir müssen es nur noch in die Daten von Sprite-7 kopieren.

Wie immer beim Zugriff auf das Char-ROM, müssen wir den E/A-Bereich abschalten. Dies machen wir hier auch gleich zu Beginn. Dann kopieren wir mit acht fast identischen Anweisungen, die Daten des gesuchten Zeichens vom Char-ROM nach Sprite-7. Dabei legen wir die Daten wirklich ganz rechts im Sprite ab, das ist nicht zwingend notwendig, ich mache es hier aber so. Zum Schluß wird dann der E/A-Bereich wieder aktiviert und das Programm läuft einfach weiter. Das Label exit ist, wie oben erwähnt, nur für diesen Test wichtig und fliegt gleich wieder raus.

Jetzt benötigen wir natürlich noch die neue Konstante

und unsere Variablen für den Text der Laufschrift und den Zeiger aufs nächste Zeichen

Wie ihr seht, wird infotextPos mit $ff initialisiert, dies ist wichtig, damit wir zu Beginn mit dem ersten Zeichen loslegen. Wie im Source zu erkennen, wird der Zeiger aufs nächste Zeichen immer erst erhöht und dann aufs Zeichen zugegriffen.

Der nächste Test sollte jetzt von ganz rechts das erste Zeichen der Laufschrift durchschieben, hier bei mir das D.

Da kommt ein „D“…
Da kommt ein „D“…

Auch die Testausgabe oben links in der Ecke zeigt das D.

Wenn ihr den Anfangsbuchstaben eurer Laufschrift ändert, sollte dieser nun über den Bildschirm wandern. Nur das dürft ihr nicht verwenden, da dieses Zeichen die Nr. $00 hat und für unser Textende steht! Werft zur Not nochmal einen Blick aufs Char-ROM.

Jetzt müssen wir nur noch dafür sorgen, dass der Text komplett durch die Sprites gejagd wird.

Den kompletten Text durchschieben

Wir haben es fast geschafft, jetzt müssen wir nur noch dafür sorgen, dass der gesamte Text durchläuft. Um zu erkennen, wann wir den nächsten Buchstaben nachschieben müssen, brauchen wir jetzt noch einen weiteren Zähler. Fügt zu den Variablen von eben noch die folgende hinzu.

Hier zählen wir mit, um wieviele Pixel die Spritedaten bereits verschoben wurden. Nach jeweils 8 Pixeln, holen wir uns das nächste Zeichen. Es ist wichtig, dass diese Variable mit initialisiert wird!

Löscht nun direkt hinter rasterIrq diese Zeilen

und ersetzt sie durch

Wir verringern jetzt also unseren Zähler für das Verschieben, direkt beim Auftreten des Raster-IRQs. Solange dieser positiv ist, geht es bei shiftAll weiter, sonst setzen wir den Zähler zurück und laufen automatisch zu getChar weiter.

Kopiert jetzt noch unsere shiftAll-Routine an die Stelle vom überflüssigen Label exit. Vergesst nicht den rts-Befehl am Ende von shiftAll zu löschen!

Jetzt läuft das Programm also nach dem Kopieren des nächsten Zeichens automatisch zum Verschieben weiter.

Das Programm sollte jetzt wieder startbar sein:

Der Text hat das Laufen gelernt.
Der Text hat das Laufen gelernt.

Bereinigt jetzt noch das Programm (in clearSprite ALLE Sprites mit $00 initialisieren und die Zeile sta $0400 löschen), dann habt ihr auch eine saubere Laufschrift.

So wollten wir es haben.
So wollten wir es haben.


Wie im Text der Laufschrift bereits erwähnt, könnt ihr nun mit den Sprites machen was ihr wollt, u. a. auch im Rahmen anzeigen. Ihr könnt auch eigene Zeichensätze verwenden oder durch entsprechende Änderungen auf Multicolor umschalten. Wollt ihr größere Grafiken bewegen, dann müssen statt der bisherigen 8 Zeilen einfach mehr verschoben werden.

Natürlich sind auch massig Optimierungen möglich, z. B. ist es nicht so toll, immer live auf das Char-ROM zuzugreifen. Auch muss nicht zwingend pixelweise verschoben werden. Es gibt also auch noch andere Ansätze, um eine solche Laufschrift zu erzeugen. Experimentiert ein Wenig und verbessert das Programm.

Ich habe hier immer so getan, als wäre Sprite-7 für das neue Zeichen wichtig. In Wirklichkeit steht euch Sprite-7 noch voll zur Verfügung. Für das neue Zeichen wird nur ein freier Speicherblock im RAM benötigt und dieser ist vollkommen unabhängig von den Sprites. Also könnt ihr Sprite-7 z. B. um eure Laufschrift tanzen lassen.

Ich verabschiede mich jetzt und wünsche euch viel Spass bei euren Projekten, allerdings nicht, ohne euch eine etwas erweiterte Fassung

Oberer und unterer Rahmen offen + Sprite-Scroller
Oberer und unterer Rahmen offen + Sprite-Scroller

zum Download anzubieten.

Gruss, Jörn


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

Loading...


Zurück

9 Gedanken zu „Sprite-Scroller“

  1. Hey, super schön erklärt – leider bekomme ich das Listing unter ACME nicht zum laufen. Hast Du es mal versucht oder weisst, warum es hakt?

    Danke, Esshahn

      1. Danke für den umgewandelten Code. Inzwischen habe ich ihn auch selbst konvertiert und zum laufen bekommen.
        Ich habe aber noch weitere Fragen und hoffe Du kannst mir helfen:

        1. Dein Scroller läuft nur bis 256 Zeichen und wrapt dann, wie müsste man den Code anpassen damit er mehr Zeichen anzeigt?

        2. Gibt es irgendwo noch Möglichkeiten Cycles zu sparen? Hier wurde ja eine Möglichkeit angesprochen, aber mir fehlt die Erfahrung, das umzusetzen.

        Bin Dir für Antwort sehr dankbar!

          1. Eine Möglichkeit wäre, dass du dafür sorgst, dass beim Überlauf von infotextpos, die Adresse bei lda infotext,X um 256 erhöht und infotextpos wieder auf 0 gesetzt wird. Sobald du dann das Null-BYTE im Text findest, muss die Adresse bei lda infotext,X wieder auf die ursprüngliche zurückgesetzt werden.
          2. Optimierungsmöglichkeiten gibt es auch einige. Wie von Spider erwähnt, ist die Multiplikation nicht optimal. Unter MUL & DIV (Ganzzahl) findest du weitere Infos.
            Statt die Zeichen pixelweise durch die Sprites zu shiften, könntest du die Sprites auch einfach wie beim Scrolling bewegen und dann immer nur ganze Zeichen umkopieren. Auch eine spezielle Organisation des Zeichensatzes bringt eine Beschleunigung. Statt immer alle 8 BYTEs eines Zeichens am Stück zu speichern, könnte man die Daten auch so organisieren, dass man zunächst nur das erste BYTE für alle Zeichen ablegt. Danach dann alle ‚zweiten‘ BYTEs, ‚dritten‘ BYTEs usw. Dann kann man sich die Positionsberechnung sogar ganz sparen. Zeitkritisch ist alles ab getChar bis zum rti.
  2. Nee, is auch okay so und alles schön didaktisch gut gemacht hier auf der Seite. Hab mich nur gewundert, weil Du ja im Multiplikation/Division Artikel bereits auf „Brute Force“ vs. asl eingehst.

    Auch wenn ich bislang für mich nichts Neues gefunden habe, macht es durchaus Spaß hier zu stöbern!

    1. Deine ‚Sicht‘ ist aber auch nicht verkehrt.
      Eventuell sollte ich bereits behandelte Themen noch mehr einfließen lassen und dann zusätzlich auf die Grundlagen verweisen. Der Einsteiger wird dann ggf. ‚genötigt‘ sich tiefer in die Materie einzuarbeiten und der Fortgeschrittene bekommt direkt ein Beispiel, das näher an der Praxis ist.

      Auch wenn für dich alles kalter Kaffee ist, freut es mich, dass du hier dennoch vorbeischaust.

  3. Der Loop bei getChar verbraucht unnötig viel Rasterzeit. Kann man auch so machen:
    asl ZP_HELPADR
    rol HIBYTE
    asl ZP_HELPADR
    rol HIBYTE
    asl ZP_HELPADR
    rol HIBYTE

    clc
    lda ZP_HELPADR+1
    adc HIBYTE
    sta ZP_HELPADR+1

    asl entspricht *2, also asl, asl, asl entspricht *2*2*2 = *8

    1. Deshalb steht ja auch mehrfach im Text, dass es ums Prinzip und nicht um die optimale Lösung geht.
      Es wird sogar extra darauf hingewiesen, dass noch Optimierungen möglich/nötig sind.

      Ich wähle für meine Erklärungen meistens den zwar langsameren, aber (zumindest in meinen Augen) leichter nachvollziehbaren Weg.

      Trotzdem vielen Dank für dein Feedback, davon kann ich durchaus mehr gebrauchen. Vielleicht bin ich ja auch auf dem Holzweg und es wäre besser die Beispiele so effektiv wie möglich zu gestalten. Allerdings befürchte ich dadurch Einsteiger (für die das meiste hier gedacht ist) abzuschrecken.

Schreibe einen Kommentar

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

Protected by WP Anti Spam