Erstellt: 23. Februar 2014 (zuletzt geändert: 1. November 2021)

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.

;*** Startadresse 
*=$0801
;*** BASIC-Zeile: 2018 SYS 2624
 !word basicend-2, 2018 
 !byte $9e
 !text " 2624"
 !byte $00,$00,$00
basicend

;*** Platzhalter für 8 Sprites
!align 63,0                         ;für korrekten Start der Daten sorgen
sprite0 
sprite1 = sprite0+64
sprite2 = sprite1+64
sprite3 = sprite2+64
sprite4 = sprite3+64
sprite5 = sprite4+64
sprite6 = sprite5+64
sprite7 = sprite6+64

;*** Hier beginnt erst das Hauptprogramm! (Adresse $0a40 = 2624)
*=sprite7+64
main

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.

;*** Hier beginnt erst das Hauptprogramm! (Adresse $0a40 = 2624)
*=sprite7+64
main
 jsr clearSprites                   ;Spritedaten löschen
 sei                                ;IRQs sperren
 lda #<rasterIrq                    ;Vektor umbiegen
 sta $0314
 lda #>rasterIrq
 sta $0315
 lda #%00000001                     ;Raster-IRQs erlauben
 sta $d01a
 lda #$7f                           ;Timer-IRQ sperren
 sta $dc0d
 lda $d011                          ;höchstes BIT für die Rasterzeile löschen
 and #%01111111                 
 sta $d011
 lda #$ff                           ;gewünschte Zeile für den Raster-IRQ
 sta $d012
 cli                                ;IRQs wieder erlauben

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

;*** Sprites einrichten
;*** Startadresse berechnen
 ldx #sprite0/64
 stx $07f8
 inx
 stx $07f9
 inx
 stx $07fa
 inx
 stx $07fb
 inx
 stx $07fc
 inx
 stx $07fd
 inx
 stx $07fe
;*** Y-Position für alle Sprites setzen
 lda #$32
 sta $d001
 sta $d003
 sta $d005
 sta $d007
 sta $d009
 sta $d00b
 sta $d00d
;*** X-Position für alle Sprites setzen
 lda #$18
 sta $d000
 lda #$48
 sta $d002
 lda #$78
 sta $d004
 lda #$a8
 sta $d006
 lda #$d8
 sta $d008
 lda #$08
 sta $d00a
 lda #$38
 sta $d00c
;*** X-Pos für Sprite 5 & 6 > 255
 lda #%01100000
 sta $d010
;*** Farbe (gelb) für alle Sprites
 lda #$07
 sta $d027
 sta $d028
 sta $d029
 sta $d02a
 sta $d02b
 sta $d02c
 sta $d02d
;*** Die ersten sieben Sprites...
 lda #%01111111
 sta $d01d                          ;in der Breite
 sta $d017                          ;und Höhe verdoppeln
 sta $d015                          ;und zum Schluß sichtbar schalten

 jmp *                              ;Endlosschleife

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.

;*** Raster-IRQ
rasterIrq
 pla                            ;Y- und X-Register vom Stack holen
 tay
 pla
 tax

 lda $D019                      ;IRQ bestätigen
 sta $D019

 pla                            ;Akku vom Stack

 rti                            ;Interrupt 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.

;*** Alle Spritedaten auf 0 setzen.
clearSprites
 ldx #62
nextByte
 lda #$ff
 sta sprite0,x
 sta sprite2,x
 sta sprite4,x
 sta sprite6,x
 lda #$00
 sta sprite1,x
 sta sprite3,x
 sta sprite5,x
 sta sprite7,x
 dex
 bpl nextByte
 rts

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.

;*** Zeichen durch die Sprites schieben
shiftAll
 ldx #3*7                           ;eine Zeile hat 3 Bytes; ein Zeichen 8 Zeilen
shiftNext                          
 clc                                ;Carry löschen
 rol sprite7+2,X                    ;jetzt alle Spritedaten 'shiften'
 rol sprite7+1,X                    ;übers C-Flag wird das herausfallende 
 rol sprite7,X                      ;Bit zum nächsten Daten-Byte übernommen
 rol sprite6+2,X
 rol sprite6+1,X
 rol sprite6,X
 rol sprite5+2,X
 rol sprite5+1,X
 rol sprite5,X
 rol sprite4+2,X
 rol sprite4+1,X
 rol sprite4,X
 rol sprite3+2,X
 rol sprite3+1,X
 rol sprite3,X
 rol sprite2+2,X
 rol sprite2+1,X
 rol sprite2,X
 rol sprite1+2,X
 rol sprite1+1,X
 rol sprite1,X
 rol sprite0+2,X
 rol sprite0+1,X
 rol sprite0,X                  
 dex                                ;das X-Register dreimal verringer
 dex                                ;da wir oben immer drei BYTEs auf einmal 
 dex                                ;'shiften'
 bpl shiftNext                      ;solange positiv -> wiederholen

 rts                                ;zurück zum Aufrufer

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.

 lda infotextPos                    ;für den ersten Test nur das erste Zeichen
 bpl exit                           ;sonst gleich wieder raus...

 inc infotextPos                    ;Zeiger aufs nächste Zeichen erhöhen
 ldx infotextPos                    ;und ins X-Register holen
 lda infotext,X                     ;Zeichen in den Akku
 bne getChar                        ;falls kein Textende ($00) weiter bei getChar
 ldx #$00                           ;sonst Zeiger aufs erste Zeichen
 stx infotextPos                    ;zurückstellen
 lda infotext                       ;und das Zeichen in den Akku holen

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.

getChar                             ;ein Zeichen aus dem Char-ROM holen
 sta $0400                          ;nur zur Kontrolle ausgeben (kann später weg!)
 tax                                ;Zeichen ins X-Register
 lda #$00                           ;Startadresse des Char-ROMs auf die Zero-Page
 sta ZPHELPADR
 lda #$d0
 sta ZPHELPADR+1
nextChar                            ;Jetzt für jedes Zeichen, bis zum gesuchten,
 clc                                ;8-Bytes auf die Char-ROM-Adresse in der
 lda #$08                           ;Zero-Page addieren
 adc ZPHELPADR
 sta ZPHELPADR
 lda #$00
 adc ZPHELPADR+1
 sta ZPHELPADR+1
 dex
 bne nextChar

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.

 lda #%11111011                     ;E/A-Bereich abschalten, um aufs Char-ROM
 and $01                            ;zugreifen zu können
 sta $01

 ldy #$00                           ;Y-Reg. für die Y-nach-indizierte-Adressierung
 lda (ZPHELPADR),Y                  ;jeweils ein BYTE aus dem Char-ROM
 sta sprite7+2                      ;ganz nach rechts in Sprite-7 kopieren
 iny                                ;Y fürs nächste BYTE erhöhen
 lda (ZPHELPADR),Y
 sta sprite7+5
 iny
 lda (ZPHELPADR),Y
 sta sprite7+8
 iny
 lda (ZPHELPADR),Y
 sta sprite7+11
 iny
 lda (ZPHELPADR),Y
 sta sprite7+14
 iny
 lda (ZPHELPADR),Y
 sta sprite7+17
 iny
 lda (ZPHELPADR),Y
 sta sprite7+20
 iny
 lda (ZPHELPADR),Y
 sta sprite7+23

 lda #%00000100                     ;E/A-Bereich wieder aktivieren
 ora $01
 sta $01

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

ZPHELPADR      = $fb

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

infotext                            ;Text für die Laufschrift
 !scr "dies ist ein spritescroller! "
 !scr "damit kann man z. b. eine laufschrift im rahmen bauen. "
 !scr "er verwendet z. zt. den standard zeichensatz des c64 als muster.... "
 !byte $00                          ;Textende 

infotextPos                         ;Zeiger auf aktuelles Zeichen
 !byte $ff

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.

infotextBitPos
 !byte 0

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 0 initialisiert wird!

Löscht nun direkt hinter rasterIrq diese Zeilen

 jsr shiftAll                   ;alle Spritedaten 'shiften'

 lda infotextPos                ;für den ersten Test nur das erste Zeichen
 bpl exit                       ;sonst gleich wieder raus...

und ersetzt sie durch

 dec infotextBitPos                 ;wurden bereits 8-Bits 'verschoben'
 bpl shiftAll                       ;falls nein, alle Spritedaten 'shiften'
 lda #$07                           ;sonst Zähler zurücksetzen
 sta infotextBitPos                 ;und dann nächstes Zeichen holen

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.
ZPHELPADR           = $fb

;*** Startadresse 
*=$0801
;*** BASIC-Zeile: 2018 SYS 2624
 !word basicend-2, 2018 
 !byte $9e
 !text " 2624"
 !byte $00,$00,$00
basicend

;*** Platzhalter für 8 Sprites
!align 63,0                         ;für korrekten Start der Daten sorgen
sprite0 
sprite1 = sprite0+64
sprite2 = sprite1+64
sprite3 = sprite2+64
sprite4 = sprite3+64
sprite5 = sprite4+64
sprite6 = sprite5+64
sprite7 = sprite6+64

;*** Hier beginnt erst das Hauptprogramm! (Adresse $0a40 = 2624)
*=sprite7+64
main
 jsr clearSprites
 sei                                ;IRQs sperren
 lda #<rasterIrq                    ;Vektor umbiegen
 sta $0314
 lda #>rasterIrq
 sta $0315
 lda #%00000001                     ;Raster-IRQs erlauben
 sta $d01a
 lda #$7f                           ;Timer-IRQ sperren
 sta $dc0d
 lda $d011                          ;höchstes BIT für die Rasterzeile löschen
 and #%01111111                 
 sta $d011
 lda #$ff                           ;gewünschte Zeile für den Raster-IRQ
 sta $d012
 cli                                ;IRQs wieder erlauben
;*** Sprites einrichten
;*** Startadresse berechnen
 ldx #sprite0/64
 stx $07f8
 inx
 stx $07f9
 inx
 stx $07fa
 inx
 stx $07fb
 inx
 stx $07fc
 inx
 stx $07fd
 inx
 stx $07fe
;*** Y-Position für alle Sprites setzen
 lda #$32
 sta $d001
 sta $d003
 sta $d005
 sta $d007
 sta $d009
 sta $d00b
 sta $d00d
;*** X-Position für alle Sprites setzen
 lda #$18
 sta $d000
 lda #$48
 sta $d002
 lda #$78
 sta $d004
 lda #$a8
 sta $d006
 lda #$d8
 sta $d008
 lda #$08
 sta $d00a
 lda #$38
 sta $d00c
;*** X-Pos für Sprite 5 & 6 > 255
 lda #%01100000
 sta $d010
;*** Farbe (gelb) für alle Sprites
 lda #$07
 sta $d027
 sta $d028
 sta $d029
 sta $d02a
 sta $d02b
 sta $d02c
 sta $d02d
;*** Die ersten sieben Sprites...
 lda #%01111111
 sta $d01d                          ;in der Breite
 sta $d017                          ;und Höhe verdoppeln
 sta $d015                          ;und zum Schluß sichtbar schalten

 jmp *                              ;Endlosschleife



;*** Alle Spritedaten auf 0 setzen.
clearSprites
 ldx #62
 lda #$00
nextByte
 sta sprite0,x
 sta sprite1,x
 sta sprite2,x
 sta sprite3,x
 sta sprite4,x
 sta sprite5,x
 sta sprite6,x
 sta sprite7,x
 dex
 bpl nextByte
 rts



;*** Raster-IRQ
rasterIrq
 dec infotextBitPos                 ;wurden bereits 8-Bits 'verschoben'
 bpl shiftAll                       ;falls nein, alle Spritedaten 'shiften'
 lda #$07                           ;sonst Zähler zurücksetzen
 sta infotextBitPos                 ;und dann nächstes Zeichen holen
 
 inc infotextPos                    ;Zeiger aufs nächste Zeichen erhöhen
 ldx infotextPos                    ;und ins X-Register holen
 lda infotext,X                     ;Zeichen in den Akku
 bne getChar                        ;falls kein Textende ($00) weiter bei getChar
 ldx #$00                           ;sonst Zeiger aufs erste Zeichen
 stx infotextPos                    ;zurückstellen
 lda infotext                       ;und das Zeichen in den Akku holen
getChar                             ;ein Zeichen aus dem Char-ROM holen
 tax                                ;Zeichen ins X-Register
 lda #$00                           ;Startadresse des Char-ROMs auf die Zero-Page
 sta ZPHELPADR
 lda #$d0
 sta ZPHELPADR+1
nextChar                            ;Jetzt für jedes Zeichen, bis zum gesuchten,
 clc                                ;8-Bytes auf die Char-ROM-Adresse in der
 lda #$08                           ;Zero-Page addieren
 adc ZPHELPADR
 sta ZPHELPADR
 lda #$00
 adc ZPHELPADR+1
 sta ZPHELPADR+1
 dex
 bne nextChar

 lda #%11111011                     ;E/A-Bereich abschalten, um aufs Char-ROM
 and $01                            ;zugreifen zu können
 sta $01

 ldy #$00                           ;Y-Reg. für die Y-nach-indizierte-Adressierung
 lda (ZPHELPADR),Y                  ;jeweils ein BYTE aus dem Char-ROM
 sta sprite7+2                      ;ganz nach rechts in Sprite-7 kopieren
 iny                                ;Y fürs nächste BYTE erhöhen
 lda (ZPHELPADR),Y
 sta sprite7+5
 iny
 lda (ZPHELPADR),Y
 sta sprite7+8
 iny
 lda (ZPHELPADR),Y
 sta sprite7+11
 iny
 lda (ZPHELPADR),Y
 sta sprite7+14
 iny
 lda (ZPHELPADR),Y
 sta sprite7+17
 iny
 lda (ZPHELPADR),Y
 sta sprite7+20
 iny
 lda (ZPHELPADR),Y
 sta sprite7+23

 lda #%00000100                     ;E/A-Bereich wieder aktivieren
 ora $01
 sta $01
;*** Zeichen durch die Sprites schieben
shiftAll
 ldx #3*7                           ;eine Zeile hat 3 Bytes; ein Zeichen 8 Zeilen
shiftNext                          
 clc                                ;Carry löschen
 rol sprite7+2,X                    ;jetzt alle Spritedaten 'shiften'
 rol sprite7+1,X                    ;übers C-Flag wird das herausfallende 
 rol sprite7,X                      ;Bit zum nächsten Daten-Byte übernommen
 rol sprite6+2,X
 rol sprite6+1,X
 rol sprite6,X
 rol sprite5+2,X
 rol sprite5+1,X
 rol sprite5,X
 rol sprite4+2,X
 rol sprite4+1,X
 rol sprite4,X
 rol sprite3+2,X
 rol sprite3+1,X
 rol sprite3,X
 rol sprite2+2,X
 rol sprite2+1,X
 rol sprite2,X
 rol sprite1+2,X
 rol sprite1+1,X
 rol sprite1,X
 rol sprite0+2,X
 rol sprite0+1,X
 rol sprite0,X                  
 dex                                ;das X-Register dreimal verringer
 dex                                ;da wir oben immer drei BYTEs auf einmal 
 dex                                ;'shiften'
 bpl shiftNext                      ;solange positiv -> wiederholen
 pla                                ;Y- und X-Register vom Stack holen
 tay
 pla
 tax

 lda $d019                          ;IRQ bestätigen
 sta $d019

 pla                                ;Akku vom Stack

 rti                                ;Interrupt verlassen


 
infotext                            ;Text für die Laufschrift
 !scr "dies ist ein spritescroller! "
 !scr "damit kann man z. b. eine laufschrift im rahmen bauen. "
 !scr "er verwendet z. zt. den standard zeichensatz des c64 als muster.... "
 !byte $00                          ;Textende 

infotextPos                         ;Zeiger auf aktuelles Zeichen
 !byte $ff
 
infotextBitPos
 !byte 0

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! (5 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