Erstellt: 3. Juli 2013 (zuletzt geändert: 1. November 2021)

Color-Cycling

…und warum diese Bezeichnung beim C64 eigentlich falsch ist.

C64 Studio, AMCE & TASM

Color-Cycling wurde in der „guten alten Zeit“ verwendet, um durch einfaches Kopieren von Farbwerten den Eindruck von Bewegung zu erzeugen. Rechenzeit war knapp und die Grafikkarten weit weg von ihrer heutigen Leistung. Wen der geschichtliche Hintergrund nicht interressiert, der kann den gräulichen Text überspringen.

Wie funktioniert das klassische Color-Cycling?
Schauen wir dazu mal auf den VGA-Modus, wie er vor über 20 Jahren am PC zum Standard für Spiele wurde. Die standard VGA-Auflösung betrug, wie beim C64, 320*200 Bildschirmpunkte. Der PC kann aber 256 Farben gleichzeitig, aus einer gesamt Palette von 262144 Farben, darstellen. Dabei wurde für jeden Bildpunkt ein Byte verwendet. Dieses Byte zeigte dann wiederum auf eine Liste, in der für jeden Byte-Wert eine Farbe hinterlegt wurde.

Bildpunkt zeigt auf Palette, diese enthält max. 256 Farben aus 262144 möglichen. (VGA)
Bildpunkt zeigt auf Palette, diese enthält max. 256 Farben aus 262144 möglichen. (VGA)

Um nun den Eindruck einer Bewegung, z. B. fließendes Wasser, zu erzeugen, brauchte man nicht alle Bildpunkte zu ändern, was sehr viel Rechenzeit benötigt hätte. Wenn man die Liste mit den 256-Farben geschickt aufgebaut hat und die Grafik entsprechend entworfen wurde, dann konnte man die Bewegung durch einfaches Umkopieren der Farben in der Liste erreichen. Dabei brauchte man meistens nur wenige Kopiervorgänge.

Wenn ihr auf das folgende Bild klickt, könnt ihr euch dies einmal genauer anschauen. Ihr findet dort verschiedene Color-Cycling-Bilder unter HTML 5. Wenn ihr dort dann noch auf den Button Show Options rechts oben klickt, wird euch die Liste der Farben angezeigt und ihr könnt nachverfolgen, was kopiert wird.

ColorCycling_001
Color-Cycling unter HTML 5

(Art by Mark Ferrari)

 

Wer sich schon etwas mit der Grafikprogrammierung auf dem C64 beschäftigt hat, dem wird nun auffallen, dass es dort kaum solche Farblisten gibt, die umkopiert werden können. Mit viel gutem Willen, kann man die MultiColor-Werte (z. B. vom Sprite) als micro Farbpalette ansehen, aber das sind ja auch nur zwei Farben. Wir verwenden das Color-Cycling gleich im Textmodus. Da werden wir aber die Farben im Farb-RAM ändern müssen, um einen solchen Effekt zu erzielen und somit (nach meinem Verständnis) kein klassisches Color-Cycling verwenden. Dies ist der Grund, weshalb ich zu Beginn behauptet habe, dass die Bezeichnung eigentlich falsch ist.

Wie soll der Effekt auf dem C64 funktionieren?

Um im Textmodus des C64 einen solchen Effekt zu erzielen, gehen wir nun zweistufig vor. Im Speicher legen wir eine Tabelle mit 40 Bytes an. Dort stehen die von uns sorgsam ausgewählten Farbwerte drin. Diese Tabelle wird zyklisch umkopiert und dann muss noch die Farbe im Farb-RAM aktualisiert werden.

Text ausgeben

Als erstes wird nur dafür gesorgt, dass die Rahmen- und Hintergrundfarbe schwarz ist und der Text auf dem Bildschirm erscheint.

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

main
 lda #$00                           ;schwarz
 sta $d020                          ;für Rahmen
 sta $d021                          ;und Hintergrund

 jsr clrScreen                      ;BS löschen
 jsr showText                       ;Text ausgeben

 jmp *                              ;Endlosschleife

;*** Bildschirm löschen
clrScreen                         
 ldx #$00                           ;Schleifenzähler (256 Zeichen je Page)
 lda #" "                           ;alles mit Leerzeichen füllen
clrLoop
 sta $0400,X                        ;1. Page des BS-Speichers
 sta $0500,X                        ;2. Page
 sta $0600,X                        ;3. Page
 sta $0700-24,X                     ;4. Page (-24 wegen der Sprite-Register)
 dex                                ;Schleifenzähler verringern
 bne clrLoop                        ;Solange größer 0 -> clrLoop
 rts                                ;sonst, zurück



;*** Text ausgeben
showText
 ldx #$27                           ;Schleife für 40 Zeichen
nextChar
 lda infotext,X                     ;Zeichen holen
 sta $0400,X                        ;und ausgeben
 dex                                ;Schleifenzähler verringern
 bpl nextChar                       ;solange positiv -> nextChar
 rts                                ;sonst, zurück



infotext
 !scr "* (c) 2018 by www.retro-programming.de *"
Zunächst nur eine einfache Textausgabe.
Zunächst nur eine einfache Textausgabe.

Na, das ist doch mal unspektakulär, oder? Lasst uns diesen Text jetzt einfärben, aber nicht einfach in einer Farbe, sondern laut einer Tabelle, in der für jedes Zeichen der Zeile eine Farbe steht.

Text einfärben

Dazu fügen wir zunächst den Aufruf der neuen Funktion jsr setColor hinter die Textausgabe jsr showText ein.

Dann wird natürlich die Funktion setColor benötigt, also diese am Besten hinter die vorhandenen Funktion showText einfügen.

;*** Farb-RAM laut Tabelle setzen
setColor
 ldx #$27                           ;Schleifenzähler für 40 Zeichen
nextColor
 lda colors,X                       ;Farbe holen
 sta $d800,X                        ;und ins Farb-RAM
 dex                                ;Schleifenzähler verringern
 bpl nextColor                      ;solange positiv -> nextColor
 rts                                ;sonst, zurück

Die Funktion macht nichts Anderes, als einfach die Farbwerte aus der Tabelle colors ins Farb-RAM für die erste Bildschirmzeile zu kopieren.

Ganz am Ende des Programms legen wir jetzt noch unsere Farbtabelle ab:

colors
 !byte $01,$01,$03,$03,$0E,$0E,$06,$06
 !byte $06,$06,$0E,$0E,$03,$03,$01,$01
 !byte $03,$03,$0E,$06,$06,$0E,$03,$03
 !byte $01,$01,$03,$03,$0E,$0E,$06,$06
 !byte $06,$06,$0E,$0E,$03,$03,$01,$01

Hier können nun die gewünschten Farben abgelegt werden. Man sollte darauf achten, dass diese gut harmonieren, sonst wirkt der Effekt nachher zu fahrig. Ich habe mich hier für blau Töne und weiß entschieden.

Ein neuer Start sieht zwar schon bunter, aber immer noch uninteressant aus.

Die Farben wurden laut Tabelle gesetzt.
Die Farben wurden laut Tabelle gesetzt.

Hier kann man jetzt schön sehen, dass ich die Farben so gewählt habe, dass diese erst von hell (weiß) immer dunkler werden und dann wieder aufsteigend heller. Das sorgt gleich für einen relativ ansehnlichen Effekt.

Farben rotieren lassen

Jetzt wird etwas benötigt, um den Effekt zu timen. Ich begnüge mich hier mit dem bekannten Interrupt, der bei $ea31 verarbeitet wird. Also direkt vor dem jmp * den Interrupt umbiegen.

;*** IRQ einrichten 
 sei                                ;IRQs sperren
 lda #<irq                          ;LSB
 sta $0314          
 lda #>irq                          ;MSB
 sta $0315                          ;in den RAM-Vektor
 cli                                ;Interrupts wieder erlauben

Bei der Gelegenheit, können wir auch gleich wieder die Zeile jsr setColor löschen, die Funktion wird nun im Interrupt aufgerufen.

Fügt anschließend die Interrupt-Routine (wie wäre es direkt hinter dem jmp *?) ein:

;*** Eigene Interrupt-Routine
irq                               
 dec cycleCnt                       ;Zähler verringern
 bpl irqExit                        ;solange positiv -> EXIT
 lda #$02                           ;sonst Zähler 
 sta cycleCnt                       ;wieder auf Anfang setzen
 jsr doColorCycle                   ;Farben umkopieren
 jsr setColor                       ;und ins Farb-RAM kopieren
irqExit
 jmp $ea31                          ;weiter zur System-Routine

Da 60 Veränderungen pro Sekunde evtl. etwas viel sind, benutzen wir einen kleinen Zähler cycleCnt. Bei jedem Interrupt wird der um eins verringert. Sobald er negativ wird, setzen wir ihn auf den Startwert zurück, kopieren die Farben in doColorCycle um, setzen dann wieder die Farben im Farb-RAM (hier ist nun das jsr setColor von eben gelandet) und springen dann (wie beim positiven Zähler) zur System-Routine nach $ea31.

Die Variable cycleCnt kann einfach am Programmende eingefügt werden.

cycleCnt
 !byte 2

Kommen wir zur Kernfunktion, dem Umkopieren der Farben. Fügt diese doch einfach hinter setColors ein.

;*** Farben umkopieren von links nach rechts
doColorCycle                      
 ldy colors+$27                     ;letzte Farbe 'retten'
 ldx #$27                           ;Schleifenzähler (40 Farbwerte)
loopColor
 lda colors-1,X                     ;Farbe 'links' von X holen
 sta colors,X                       ;und nach X kopieren
 dex                                ;Schleife verringern
 bne loopColor                      ;solange größer null -> loopColor
 sty colors                         ;sonst, gerettete Farbe an die erste Position
 rts                                ;zurück

Die Funktion ist auch ganz simpel. Es werden die Farben von links nach rechts kopiert. Das X-Register zeigt dabei auf das aktuelle Zielfeld, hier hin wird also die links stehende Farbe kopiert. Da wir die Farben wieder rückwärts abarbeiten, retten wir zu Beginn die letzte Farbe erstmal im Y-Register. Dann wird der Schleifenzähler (X-Reg.) wieder mit #$27 (dez. 39) initialisiert. Die Schleife holt sich erstmal die Farbe links vom X-Register in den Akku und kopiert diese an die Position, auf die X zeigt. Dann wird X verringert und das ganze wiederholt sich, solange X größer 0 ist. Somit wurden alle Farben kopiert, nur die Farbe für die erste Stelle fehlt noch. Den benötigten Wert haben wir uns zum Glück ganz zu Anfang im Y-Reg. gemerkt. Der kann nun direkt an die erste Position der Farbtabelle geschrieben werden. Schon sind wir fertig und können zurück zum Aufrufer.

Das Programm ist nun fertig und bereit von euch assembliert und gestartet zu werden.

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

main
 lda #$00                           ;schwarz
 sta $d020                          ;für Rahmen
 sta $d021                          ;und Hintergrund

 jsr clrScreen                      ;BS löschen
 jsr showText                       ;Text ausgeben

;*** IRQ einrichten 
 sei                                ;IRQs sperren
 lda #<irq                          ;LSB
 sta $0314          
 lda #>irq                          ;MSB
 sta $0315                          ;in den RAM-Vektor
 cli                                ;Interrupts wieder erlauben
 
 jmp *                              ;Endlosschleife



;*** Eigene Interrupt-Routine
irq                               
 dec cycleCnt                       ;Zähler verringern
 bpl irqExit                        ;solange positiv -> EXIT
 lda #$02                           ;sonst Zähler 
 sta cycleCnt                       ;wieder auf Anfang setzen
 jsr doColorCycle                   ;Farben umkopieren
 jsr setColor                       ;und ins Farb-RAM kopieren
irqExit
 jmp $ea31                          ;weiter zur System-Routine



;*** Bildschirm löschen
clrScreen                         
 ldx #$00                           ;Schleifenzähler (256 Zeichen je Page)
 lda #" "                           ;alles mit Leerzeichen füllen
clrLoop
 sta $0400,X                        ;1. Page des BS-Speichers
 sta $0500,X                        ;2. Page
 sta $0600,X                        ;3. Page
 sta $0700-24,X                     ;4. Page (-24 wegen der Sprite-Register)
 dex                                ;Schleifenzähler verringern
 bne clrLoop                        ;Solange größer 0 -> clrLoop
 rts                                ;sonst, zurück



;*** Text ausgeben
showText
 ldx #$27                           ;Schleife für 40 Zeichen
nextChar
 lda infotext,X                     ;Zeichen holen
 sta $0400,X                        ;und ausgeben
 dex                                ;Schleifenzähler verringern
 bpl nextChar                       ;solange positiv -> nextChar
 rts                                ;sonst, zurück



;*** Farb-RAM laut Tabelle setzen
setColor
 ldx #$27                           ;Schleifenzähler für 40 Zeichen
nextColor
 lda colors,X                       ;Farbe holen
 sta $d800,X                        ;und ins Farb-RAM
 dex                                ;Schleifenzähler verringern
 bpl nextColor                      ;solange positiv -> nextColor
 rts                                ;sonst, zurück



;*** Farben umkopieren von links nach rechts
doColorCycle                      
 ldy colors+$27                     ;letzte Farbe 'retten'
 ldx #$27                           ;Schleifenzähler (40 Farbwerte)
loopColor
 lda colors-1,X                     ;Farbe 'links' von X holen
 sta colors,X                       ;und nach X kopieren
 dex                                ;Schleife verringern
 bne loopColor                      ;solange größer null -> loopColor
 sty colors                         ;sonst, gerettete Farbe an die erste Position
 rts                                ;zurück



infotext
 !scr "* (c) 2018 by www.retro-programming.de *"


colors
 !byte $01,$01,$03,$03,$0e,$0e,$06,$06
 !byte $06,$06,$0e,$0e,$03,$03,$01,$01
 !byte $03,$03,$0e,$06,$06,$0e,$03,$03
 !byte $01,$01,$03,$03,$0e,$0e,$06,$06
 !byte $06,$06,$0e,$0e,$03,$03,$01,$01
 
cycleCnt
 !byte 2

Wie ihr seht, könnt ihr auch ganz ohne Rastertricks und VIC-Manipulationen schon einen netten kleinen Effekt erzielen. OK, einen Preis wird man damit nicht gewinnen, aber ein Anfang ist gemacht.

Nun ist es an euch, weiter zu experimentieren.

Sollen sich die Farben z. B. in die andere Richtung bewegen, dann müsst ihr diese nur von rechts nach links kopieren. Testet auch mal andere Farben in der Farbtabelle, gebt mehrere Texte aus, bei denen die Farben in unterschiedliche Richtungen laufen oder ändert CycleCnt.
Auch sind natürlich noch Optimierungen möglich, warum das Umkopieren und Setzen des Farb-RAMs trennen? Dies könnte in einem Rutsch geschehen bzw. warum nicht gleich nur das Farb-RAM umkopieren?

Man kann den Effekt zum Beispiel noch verstärken, wenn man mehrere Zeilen direkt untereinander ausgibt und die Farben in jeder Zeile um eine Position verschiebt. Ihr könnt das Color-Cycling auch mit einem Textscroller verbinden.

Im folgenden Image findet ihr eine etwas aufgebohrte Version, bei der auch schon eine Laufschrift eingesetzt wird (es ist auch noch ein Rasterzeileninterrupt im Spiel).


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

Loading...


Zurück

3 Gedanken zu „Color-Cycling“

  1. Hi Jörn,

    auch dieses Tutorial konnte ich wunderbar nachvollziehen, habe es auch geschafft mehrere Textzeilen ‘sinnvoll’ einzufärben 🙂

    Bei weiterer Recherche im Web habe ich gesehen, dass man auch auf die Clear Screen Routine Kernel Routine bei $E544 zugreifen kann um den Bildschirm zu löschen. Also einfach -> JSR $E544

    Das macht den Code dann nochmal schlanker 🙂

    1. Ja, es gibt durchaus mehrere Möglichkeiten, den Bildschirm zu löschen.

      Du kannst z. B. auch das bereits an anderer Stelle erwähnte:
      lda #147
      jsr $FFD2

      verwenden.

      Denk aber immer daran, dass die Kernalfunktionen nicht sonderlich schnell sind! Hier spielt es zwar keine Rolle, aber bei anderen Projekten, kann es notwendig sein, den BS schnell zu löschen. Außerdem muss hier nur auf den Sprung nach $EA31 verzichtet werden, dann kann der Code auch ohne Kernal zum Laufen gebracht werden. (Achtung: Dabei an die IRQ-Vektoren denken.)

Schreibe einen Kommentar

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

Protected by WP Anti Spam