Erstellt: 1. Mai 2013 (zuletzt geändert: 20. November 2023)

Sprites (ASM)

Die wichtigsten Grundlagen zu den Sprites

C64 Studio

Unter BASIC habt ihr evtl. schon mal die Sprites benutzt. Hier wird nochmal alles zusammengetragen und von der Assemblerseite betrachtet.

Das Sprites freibewegliche und vom aktuellen Grafikmodus unabhängige Objekte sind, wisst ihr bestimmt schon. Der C64 kann davon 8 Stück gleichzeitig darstellen. Durch entsprechende Techniken (Rasterzeileninterrupt), lassen sich auch mehr anzeigen. Sprites können einfarbig und hochauflösend (Hi-Res) oder mehrfarbig (Multicolor) und gröber sein. Ihre Größe lässt sich in der Breite und/oder Höhe einfach verdoppeln. Außerdem könnt ihr festlegen, ob die Sprites vor oder hinter der Hintergrundgrafik erscheinen sollen. Darüberhinaus gibt es eine Kollisionserkennung, man kann also feststellen, ob sich zwei Sprites berühren oder ob ein Sprite mit dem Hintergrund Kontakt hat.

Für die Sprites ist natürlich wieder der Grafikchip (VIC-II) des C64 zuständig. Wie wichtig die Sprites sind, kann man evtl. daran erkennen, dass von den 47 VIC-Registern (beim C128 gibt es noch zwei weitere) alleine 34 für die Sprites zusändig sind. Wie ihr in Kleine Hardwarkunde gesehen habt, beginnen die Register des VIC-II ab $d000, schauen wir uns also die Register für die Sprites einmal an:

Adresse
Nr.
Beschreibung
$D000
00
Sprite 0: X-Position (s. auch $D010)
$D001
01
Sprite 0: Y-Position
$D002
02
Sprite 1: X-Position (s. auch $D010)
$D003
03
Sprite 1: Y-Position
$D004
04
Sprite 2: X-Position (s. auch $D010)
$D005
05
Sprite 2: Y-Position
$D006
06
Sprite 3: X-Position (s. auch $D010)
$D007
07
Sprite 3: Y-Position
$D008
08
Sprite 4: X-Position (s. auch $D010)
$D009
09
Sprite 4: Y-Position
$D00A
10
Sprite 5: X-Position (s. auch $D010)
$D00B
11
Sprite 5: Y-Position
$D00C
12
Sprite 6: X-Position (s. auch $D010)
$D00D
13
Sprite 6: Y-Position
$D00E
14
Sprite 7: X-Position (s. auch $D010)
$D00F
15
Sprite 7: Y-Position
$D010
16
Für ALLE Sprites: Das höchste Bit der X-Position
Da der Bildschirm breiter als 256 (mehr passt ja nicht in ein Byte) Pixel ist benötigt man noch ein weiteres Bit um ein Sprite z. B. nach X = 300 zu bringen. Die C64-Entwickler haben sich entschieden diese Info in diesem einzelnen Byte für alle Sprites zu speichern.

Sprite-Nr. = Bit-Nr. (denkt dran: Bit-7 ist ganz links, Bit-0 ist ganz rechts)
$D015
21
Legt fest, ob das jeweilige Sprite aktiv (sichtbar) ist (=1)
Sprite-Nr. = Bit-Nr.
$D017
23
Bestimmt, ob das jeweilige Sprite in seiner Höhe verdoppelt (=1) werden soll oder nicht (=0)
Sprite-Nr. = Bit-Nr.
$D01B
27
Soll das jeweilige Sprite vor (=0) oder hinter der Hintergrundgrafik (=1) erscheinen.
Sprite-Nr. = Bit-Nr.
$D01C
28
Legt fest, ob das jeweilige Sprite Hi-Res (=0) oder Multicolor (=1) ist.
Sprite-Nr. = Bit-Nr.
$D01D
29
Legt fest, ob das jeweilige Sprite normal (=0) oder in seiner Breite verdoppelt (=1) dargestellt werden soll.
Sprite-Nr. = Bit-Nr.
$D01E
30
Zeigt eine Kollision (=1) zwischen Sprites an.
Sprite-Nr. = Bit-Nr.

Der Registerinhalt wird beim lesen gelöscht!
$D01F
31
Zeigt eine Kollision (=1) zwischen einem Sprite und dem Hintergrund an.
Sprite-Nr. = Bit-Nr.

Der Registerinhalt wird beim lesen gelöscht!
$D025
37
Erste Spritefarbe (Multicolor_0) für den Sprite-Multicolormodus (s. $D01C)
Achtung: Diese ist für alle Sprites gleich!
$D026
38
Zweite Spritefarbe (Multicolor_1) für den Sprite-Multicolormodus (s. $D01C)
Achtung: Diese ist für alle Sprites gleich!
$D027
39
Sprite-0: Farbe - Hauptfarbe des Sprites
$D028
40
Sprite-1: Farbe - Hauptfarbe des Sprites
$D029
41
Sprite-2: Farbe - Hauptfarbe des Sprites
$D02A
42
Sprite-3: Farbe - Hauptfarbe des Sprites
$D02B
43
Sprite-4: Farbe - Hauptfarbe des Sprites
$D02C
44
Sprite-5: Farbe - Hauptfarbe des Sprites
$D02D
45
Sprite-6: Farbe - Hauptfarbe des Sprites
$D02E
46
Sprite-7: Farbe - Hauptfarbe des Sprites

Wie ihr seht, werden in $d000$d00f die jeweiligen X- und Y-Postionen der acht Sprites gespeichert. Die X-Position könnte damit max. 255 betragen, da ja nur ein Byte vorgesehen ist. Deshalb gibt es noch das Register $d010! Dort steht für jedes Sprite ein weiteres Bit zur Verfügung. Somit haben wir neun Bits und können Zahlen von 0 bis 511 darstellen.

Wichtig: Die Bit-Nr. entspricht dabei der Sprite-Nr. (das kommt hier häufiger vor!)

Beispiel für $d010

Inhalt: %00100011
         ||||||||
Bit-Nr.: 76543210 = Sprite-Nr.

Im obigen Beispiel ist die X-Position von Sprite 0, 1 und 5 also größer als 255, da im Register $d010 das jeweilige Bit gesetzt ist.

Was sind nun Hi-Res- und Multicolor-Sprites (siehe Register $d01c, $d025 und $d026)?
Die Hi-Res-Sprites sind einfarbig und haben eine Größe von 24 Pixeln (Punkten) mal 21 Zeilen. Ein einzelnes Bit bestimmt jeweils, ob der Pixel sichtbar oder durchsichtig ist. Die Farbe der sichtbaren Pixel kann für jedes einzelne Sprite frei gewählt werden, dies geschieht über die Register von $d027$d02e.

Bei Multicolor-Sprites reduziert sich die Auflösung auf 12 * 21 Pixel, da nun immer zwei Bits benötigt werden, um festzulegen ob ein Pixel sichtbar ist. Auf dem Bildschirm werden aber für einen Punkt zwei Pixel benötigt, das Sprite wirkt dadurch um einiges gröber.

%00 = Pixel nicht gesetzt (durchsichtig)
%01 = Multicolor_0  ($d025)
%10 = Spritefarbe   ($d027 - $d02e, wie bei Hi-Res)
%11 = Multicolor_1  ($d026)

Das Besondere ist nun, dass alle Sprites die selben beiden Multicolor-Werte benutzen müssen, Multicolor_0 $d025 und Multicolor_1 $d026 kann man also nur einmal festlegen. Wie beim Hi-Res-Sprite besitzt aber jedes Sprite weiterhin seine individuelle Farbe in $d027$d02e.

Die Erklärungen in der Tabelle zu den restlichen Register sollen erstmal reichen, wir werden aber gleich im Beispiel alle einmal verwenden, damit ihre Funktion klar wird.

Sprites anzeigen

Was müssen wir nun unternehmen, um Sprites anzuzeigen?

  • die Sprites enwerfen und im Speicher ablegen
  • dem VIC-II mitteilen, wo die Spritedaten liegen
  • Einstellungen wie doppelte Breite / Höhe; Hi-Res oder Multicolor vornehmen
  • die Farben festlegen
  • die aktuellen X- und Y-Positionen eintragen
  • die Sprites sichtbar schalten

Die Sprites entwerfen

Im BASIC-Teil habe ich noch einen Excel-Sprite-Editor vorgestellt, aber dieses Mal habe ich den Sprite-Editor vom C64 Studio verwendet und die Daten als !byte-Anweisungen in den Source aufgenommen. Ihr könnt also auch mit ACME fortfahren.

Sprite als !byte-Zeilen exportieren.

Am besten benutzt ihr erstmal die von mir vorgegebenen Sprites, damit das Ergebnis vergleichbar bleibt. Aber sobald ihr den Umgang mit den Sprites verinnerlicht habt, solltet ihr eure eigenen Versuche durchführen.

Dem VIC-II mitteilen, wo die Spritedaten liegen

Zunächst müssen wir klären, wo die Speicherstellen überhaupt liegen, an denen der VIC II die Adressen der Spritedaten erwartet. Erwähnt habe ich das in anderen Beiträgen schon häufiger: Die Adressen befinden sich hinter dem Bildschirmspeicher, um genau zu sein, in den letzten 8 Bytes des Kilobytes, das ab der Startadresse des Bildschirmspeichers zufinden ist. Diese Adressen werden Sprite-Pointer genannt. Werfen wir mal einen Blick auf den Bildschirmspeicher, wie er nach dem Einschalten vorliegt:

Screen-RAM nach dem Einschalten
Das Screen-RAM nach dem Einschalten

Direkt nach dem Einschalten findet ihr die Sprite-Pointer also an den Stellen $07f8 bis $07ff.

Mini Exkurs: VIC II
Ganz so eindeutig ist das nun aber doch wieder nicht. Die genaue Position ist noch von weiteren Faktoren abhängig.
Der VIC II kann immer nur 16KB „sehen“. Ihr habt daher die Möglichkeit dem VIC mitzuteilen, auf welche 16KB (Bank-0 bis 3) er zugreifen darf. Innerhalb der jeweiligen Bank könnt ihr dann noch den Beginn des BS-Speichers in 1KB Schritten auf die von euch gewünschte Position verschieben.
Genaueres dazu gibt es im Beitrag VIC-II: Speicherbereiche festlegen, den ihr aktuell aber nicht unbedingt braucht.

Wie sollen wir unsere Adressen für die Spritedaten nun in nur einem Byte ablegen? Hier kommt eine ähnliche Logik wie eben beim Exkurs zum Tragen. Da ein Sprite max. 24 Pixel * 21 Zeilen oder auch 3 Bytes * 21 Zeilen, also 63 Byte groß sein kann, werden die Daten auf 64 Byte aufgefüllt und an einer durch 64 teilbaren Adresse erwartet. Teilt man die 16KB, die der VIC „sieht“, durch die 256 möglichen Plätze (wir haben ja nur ein BYTE für die Adresse der aktuellen Spritedaten) dann kommen wir wieder auf 64 Bytes. Wir müssen also die Nr. des 64-Byte-Blocks, innerhalb der für den VIC-II sichtbaren 16KB-Bank angeben.
Normalerweise würdet ihr euch direkt eine passende Adresse aussuchen und diese dann verwenden. Wir halten das aber in unserem Beispiel zu Übungszwecken einfach mal dynamisch und berechnen uns die entsprechende Position.

Beginnen wir mit dem Programm. Nicht erschrecken, dass es so lang ist, ich starte erstmal mit einer Reihe von Variablen/Konstanten, die ich zukünftig ausbaue und häufiger verwende.

;*******************************************************************************
;*** Farben                                                                  ***
;*******************************************************************************
COLOR_BLACK         = $00           ;schwarz
COLOR_WHITE         = $01           ;weiß
COLOR_RED           = $02           ;rot
COLOR_CYAN          = $03           ;türkis
COLOR_PURPLE        = $04           ;lila
COLOR_GREEN         = $05           ;grün
COLOR_BLUE          = $06           ;blau
COLOR_YELLOW        = $07           ;gelb
COLOR_ORANGE        = $08           ;orange
COLOR_BROWN         = $09           ;braun
COLOR_PINK          = $0a           ;rosa
COLOR_DARKGREY      = $0b           ;dunkelgrau
COLOR_GREY          = $0c           ;grau
COLOR_LIGHTGREEN    = $0d           ;hellgrün
COLOR_LIGHTBLUE     = $0e           ;hellblau
COLOR_LIGHTGREY     = $0f           ;hellgrau

;*******************************************************************************
;*** Die VIC II Register  -  ANFANG                                          ***
;*******************************************************************************
VICBASE             = $d000         ;(RG) = Register-Nr.
SPRITE0X            = $d000         ;(00) X-Position von Sprite 0
SPRITE0Y            = $d001         ;(01) Y-Position von Sprite 0
SPRITE1X            = $d002         ;(02) X-Position von Sprite 1
SPRITE1Y            = $d003         ;(03) Y-Position von Sprite 1
SPRITE2X            = $d004         ;(04) X-Position von Sprite 2
SPRITE2Y            = $d005         ;(05) Y-Position von Sprite 2
SPRITE3X            = $d006         ;(06) X-Position von Sprite 3
SPRITE3Y            = $d007         ;(07) Y-Position von Sprite 3
SPRITE4X            = $d008         ;(08) X-Position von Sprite 4
SPRITE4Y            = $d009         ;(09) Y-Position von Sprite 4
SPRITE5X            = $d00a         ;(10) X-Position von Sprite 5
SPRITE5Y            = $d00b         ;(11) Y-Position von Sprite 5
SPRITE6X            = $d00c         ;(12) X-Position von Sprite 6
SPRITE6Y            = $d00d         ;(13) Y-Position von Sprite 6
SPRITE7X            = $d00e         ;(14) X-Position von Sprite 7
SPRITE7Y            = $d00f         ;(15) Y-Position von Sprite 7
SPRITESMAXX         = $d010         ;(16) Höhstes BIT der jeweiligen X-Position
                                    ;        da der BS 320 Punkte breit ist reicht
                                    ;        ein BYTE für die X-Position nicht aus!
                                    ;        Daher wird hier das 9. Bit der X-Pos
                                    ;        gespeichert. BIT-Nr. (0-7) = Sprite-Nr.
SPRITEACTIV         = $d015         ;(21) Bestimmt welche Sprites sichtbar sind
                                    ;        Bit-Nr. = Sprite-Nr.
SPRITEDOUBLEHEIGHT  = $d017         ;(23) Doppelte Höhe der Sprites
                                    ;        Bit-Nr. = Sprite-Nr.
SPRITEDEEP          = $d01b         ;(27) Legt fest ob ein Sprite vor oder hinter
                                    ;        dem Hintergrund erscheinen soll.
                                    ;        Bit = 1: Hintergrund vor dem Sprite
                                    ;        Bit-Nr. = Sprite-Nr.
SPRITEMULTICOLOR    = $d01c         ;(28) Bit = 1: Multicolor Sprite 
                                    ;        Bit-Nr. = Sprite-Nr.
SPRITEDOUBLEWIDTH   = $d01d         ;(29) Bit = 1: Doppelte Breite des Sprites
                                    ;        Bit-Nr. = Sprite-Nr.
SPRITESPRITECOLL    = $d01e         ;(30) Bit = 1: Kollision zweier Sprites
                                    ;        Bit-Nr. = Sprite-Nr.
                                    ;        Der Inhalt wird beim Lesen gelöscht!!
SPRITEBACKGROUNDCOLL= $d01f         ;(31) Bit = 1: Sprite / Hintergrund Kollision
                                    ;        Bit-Nr. = Sprite-Nr.
                                    ;        Der Inhalt wird beim Lesen gelöscht!
SPRITEMULTICOLOR0   = $d025         ;(37) Spritefarbe 0 im Multicolormodus
SPRITEMULTICOLOR1   = $d026         ;(38) Spritefarbe 1 im Multicolormodus
SPRITE0COLOR        = $d027         ;(39) Farbe von Sprite 0
SPRITE1COLOR        = $d028         ;(40) Farbe von Sprite 1
SPRITE2COLOR        = $d029         ;(41) Farbe von Sprite 2
SPRITE3COLOR        = $d02a         ;(42) Farbe von Sprite 3
SPRITE4COLOR        = $d02b         ;(43) Farbe von Sprite 4
SPRITE5COLOR        = $d02c         ;(44) Farbe von Sprite 5
SPRITE6COLOR        = $d02d         ;(45) Farbe von Sprite 6
SPRITE7COLOR        = $d02e         ;(46) Farbe von Sprite 7

;*******************************************************************************
;*** Die VIC II Register  -  ENDE                                            ***
;*******************************************************************************

SCREENRAM           = $0400             ;Beginn des Bildschirmspeichers
SPRITE0DATA         = SCREENRAM+$03f8   ;Sprite-Pointer für die
                                        ;Adresse der Sprite-0-Daten
SPRITE1DATA         = SCREENRAM+$03f9   ;wie eben, nur für Sprite-1

;*** Startadresse
*=$0801
 
;*** BASIC-Zeile
 !word main-2
 !word 2018
 !text $9E," 2062",$00,$00,$00
 
!zone main
main
 lda #<spritedata                   ;LSB der Spritedaten holen
 sta calc16Bit                      ;im 'Hilfsregister' speichern
 lda #>spritedata                   ;MSB auch
 sta calc16Bit+1                    ;ins 'Hilfregister'
 ldx #$06                           ;Schleifenzähler fürs Teilen durch 64
.loop
 lsr calc16Bit+1                    ;MSB nach rechts 'shiften'
 ror calc16Bit                      ;LSB nach rechts 'rotieren', wg. Carry-Flag!
 dex                                ;Schleifenzähler verringern
 bne .loop                          ;wenn nicht 0, nochmal

 ldx calc16Bit                      ;Im LSB des 'Hilfsregisters' steht der 64-Byte-Block
 stx SPRITE0DATA                    ;In der zuständigen Speicherstelle ablegen
 inx                                ;Das 2. Sprite folgt direkt dahinter, also +1
 stx SPRITE1DATA                    ;und dem VIC II mitteilen

;*** Weitere Sourcezeilen ab hier einfügen...

 rts
 
 

;*** Hilfsregister für 16-Operartionen
calc16Bit
 !byte $00, $00

;*** Mittels !align 63,0', den Assembler anweisen, den Beginn 
;*** der Daten an eine durch 64 teilbare Adresse zulegen.
!align 63,0

spritedata
; Hi-Res-Sprite
!byte  $ff,$ff,$ff
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $ff,$08,$ff
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $ff,$08,$ff
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $ff,$ff,$ff
!byte  $00                          ;auf 64 Bytes auffüllen

; Multicolor-Sprite
!byte  $aa,$55,$ff
!byte  $aa,$55,$ff
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $aa,$55,$ff
!byte  $aa,$55,$ff
!byte  $00                          ;auf 64 Bytes auffüllen

 

Das Programm macht noch nicht wirklich viel. Nach main wird zunächst nur der erste 64-Byte-Block der Spritedaten berechnet. Dazu kopieren wir das LSB & MSB unserer Startadresse der Spritedaten von spritedata in ein Hilfsregister calc16Bit. Beachtet bitte, dass wir den Assembler mittels !align 63,0 dazu gebracht haben, den Beginn von spritedata an eine durch 64 teilbare Adresse zu verschieben.
Um die Nr. des 64-Byte-Blocks zu erhalten, müssen wir nun (das klappt so nur, weil wir alles auf Standard gelassen haben!) unsere Adresse durch 64 teilen. Dazu verschieben wir diese einfach 6 mal bitweise nach rechts (2*2*2*2*2*2 = 64). Also in einer kleinen Schleife mit lsr das MSB nach rechts verschieben und danach mit ror, damit das Carry-Flag beachtet wird, das LSB nach rechts verschieben. Zum Schluß haben wir im LSB des Hilfsregisters unsere gesuchte Nr. des 64-Byte-Blocks. Diesen Wert holen wir ins X-Register und speichern ihn unter SPRITE0DATA. Dann erhöhen wir X um 1 (das zweite Sprite folgt ja direkt hinter dem ersten) und speichern X an SPRITE1DATA. Somit weiß der VIC II jetzt, wo er die Daten für Sprite-0 und 1 finden kann. Hier endet das Programm erstmal.
Wer kommt über einen Richtungswechsel für diesen Sonderfall auf eine schnellere Lösung für die Berechnung des 64-BYTE-Blocks?

Gebt die weiteren Sourcezeilen bitte einfach vor dem rts ein.

Einstellungen wie doppelte Breite / Höhe, Hi-Res oder Multicolor vornehmen

Unsere beiden Sprites sollen zunächst ohne jegliche Vergrößerung angezeigt werden. Daher löschen wir die Register $d017 und $d01d

 lda #%00000000               ;Keine Vergrößerung gewünscht
 sta SPRITEDOUBLEHEIGHT       ;doppelte Höhe zurücksetzen
 sta SPRITEDOUBLEWIDTH        ;doppelte Breite zurücksetzen

Unser Sprite-0 ist Hi-Res, das Sprite-1 aber Multicolor. Daher müssen wir Bit-1 in $d01c setzen. Wir wollen zur Sicherheit, alle anderen Sprites auf Hi-Res setzen, daher schreiben wir einfach %00000010 ins Register.

 lda #%00000010               ;nur Sprite-1 ist Mulicolor, der Rest Hi-Res
 sta SPRITEMULTICOLOR         ;Multicolor für Sprite-1 aktivieren

Außerdem sorgen wir dafür, dass unsere Sprites erstmal vor dem Hintergrund zu sehen sind, indem wir $d01b auf null setzten.

 lda #%00000000               ;Alle Sprites vor dem Hintergrund anzeigen
 sta SPRITEDEEP
Die Farben festlegen

Die Hauptfarben für Sprite-0 und 1 schreiben wir in die Register $d027 und $d028.

 lda #COLOR_PURPLE            ;Farbe für
 sta SPRITE0COLOR             ;Sprite-0 (hat nur eine, da Hi-Res!)

 lda #COLOR_BLACK             ;Hauptfarbe für
 sta SPRITE1COLOR             ;Sprite-1 (ist Multicolor, daher Hauptfarbe!)

Da das zweite ein Multicolor-Sprite ist, sollten wir auch noch die dazugehörigen Farben setzen.

 lda #COLOR_RED               ;Farbe für
 sta SPRITEMULTICOLOR0        ;Multicolor-0
 lda #COLOR_YELLOW            ;Farbe für
 sta SPRITEMULTICOLOR1        ;Mulitcolor-1

Denkt daran, dass diese beiden Farben für alle Multicolor-Sprites gleich sind!

Die aktuellen X- und Y-Positionen eintragen

Bei der Positionierung der Sprites müssen wir einiges beachten. Zunächst mal findet die Positionierung in Pixeln und nicht in Zeichen und Reihen, wie bei unseren Textausgaben, statt.
Der C64 hat zwar eine Auflösung von 320 * 200 Pixel, dies gilt aber nur für den sichtbaren Bereich. Bei der Positionierung unserer Sprites müssen wir beachten, dass der Ursprung die Koordinaten X=0 und Y=0 hat und sich links oben in der Ecke befindet. Die X-Werte nehmen nach rechts und die Y-Werte nach unten zu. Da wir noch den Rahmen beachten müssen, wird unser Sprite erst ab X=24 und Y=50 komplett sichtbar. Werft einen Blick auf die nachfolgende Grafik, um euch ein besseres Bild davon zu machen. Damit es nicht zu einfach wird, verschieben sich die im Bild angegebenen Werte, wenn ihr den 38 * 24 Zeichenmodus aktiviert.

ASM_ScreenCoords

Da der Rahmen eine höhere Priorität, als die Sprites hat, verschwinden diese standardmäßig dahinter! Auch hier gibt es wieder Tricks, um Sprites im Rahmen anzuzeigen. Wo wir gerade bei der Priorität sind, untereinander hat Sprite-0 die höchste und Sprite-7 die niedrigste Priorität, d. h. Sprite-0 wird bei Überschneidungen über alle anderen Sprites gezeichnet. Diese Sprite/Sprite-Priorität läßt sich, im Gegensatz zu der mit dem Hintergrund, nicht ändern!

Die Priorität der Sprites: 0 überdeckt alle anderen, 1 über deckt Sprite 2-7, usw.
Die Priorität der Sprites: 0 überdeckt alle anderen, 1 überdeckt Sprite 2-7, 2 überdeckt 3-7 usw. Die Sprites stammen aus ‚The Way of the Exploding Fist‘

Außerdem haben wir weiter oben in der Tabelle ja schon erfahren, dass die X-Position über zwei Register gesteuert wird. Dies ist notwendig, da man sonst mit einem Byte ja nur die Positionen 0 – 255 erreichen könnte.

Setzen wir unsere Sprites einfach erstmal so, dass wir sie sehen. Sprite-0 soll an X=$20 und Y=$80 erscheinen, das zweite rechts daneben an ($40, $80). Die Register für Sprite-0 sind $d000 & $d001, die für Sprite-1 $d002 und $d003. Auch hier löschen wir zur Sicherheit wieder ein Register, diesmal $d010 in dem ja das höchste Bit für die X-Position gespeichert wird.

 ldx #$00                     ;Zur Sicherheit das höchste
 stx SPRITESMAXX              ;Bit für die X-Position löschen
 ldx #$20                     ;X-Pos für
 stx SPRITE0X                 ;Sprite-0
 ldx #$40                     ;X-Pos für
 stx SPRITE1X                 ;Sprite-1

 ldy #$80                     ;Y-Position
 sty SPRITE0Y                 ;für Sprite-0
 sty SPRITE1Y                 ;und Sprite-1
Die Sprites sichtbar schalten

Ein letzter, kleiner Schritt noch, dann sehen wir unsere Sprites endlich. Wir müssen nur noch in $d015 die sichtbaren Sprites eintragen. Um Grafikmüll zu vermeiden, aktivieren wir hier nur unsere beiden Sprites, alle anderen schalten wir ab.

Fügt also abschließend noch diese beiden Zeilen hinzu:

 lda #%00000011               ;nur Sprite-0 und 1
 sta SPRITEACTIV              ;aktivieren, alle anderen ausblenden
;*******************************************************************************
;*** Farben                                                                  ***
;*******************************************************************************
COLOR_BLACK         = $00           ;schwarz
COLOR_WHITE         = $01           ;weiß
COLOR_RED           = $02           ;rot
COLOR_CYAN          = $03           ;türkis
COLOR_PURPLE        = $04           ;lila
COLOR_GREEN         = $05           ;grün
COLOR_BLUE          = $06           ;blau
COLOR_YELLOW        = $07           ;gelb
COLOR_ORANGE        = $08           ;orange
COLOR_BROWN         = $09           ;braun
COLOR_PINK          = $0a           ;rosa
COLOR_DARKGREY      = $0b           ;dunkelgrau
COLOR_GREY          = $0c           ;grau
COLOR_LIGHTGREEN    = $0d           ;hellgrün
COLOR_LIGHTBLUE     = $0e           ;hellblau
COLOR_LIGHTGREY     = $0f           ;hellgrau

;*******************************************************************************
;*** Die VIC II Register  -  ANFANG                                          ***
;*******************************************************************************
VICBASE             = $d000         ;(RG) = Register-Nr.
SPRITE0X            = $d000         ;(00) X-Position von Sprite 0
SPRITE0Y            = $d001         ;(01) Y-Position von Sprite 0
SPRITE1X            = $d002         ;(02) X-Position von Sprite 1
SPRITE1Y            = $d003         ;(03) Y-Position von Sprite 1
SPRITE2X            = $d004         ;(04) X-Position von Sprite 2
SPRITE2Y            = $d005         ;(05) Y-Position von Sprite 2
SPRITE3X            = $d006         ;(06) X-Position von Sprite 3
SPRITE3Y            = $d007         ;(07) Y-Position von Sprite 3
SPRITE4X            = $d008         ;(08) X-Position von Sprite 4
SPRITE4Y            = $d009         ;(09) Y-Position von Sprite 4
SPRITE5X            = $d00a         ;(10) X-Position von Sprite 5
SPRITE5Y            = $d00b         ;(11) Y-Position von Sprite 5
SPRITE6X            = $d00c         ;(12) X-Position von Sprite 6
SPRITE6Y            = $d00d         ;(13) Y-Position von Sprite 6
SPRITE7X            = $d00e         ;(14) X-Position von Sprite 7
SPRITE7Y            = $d00f         ;(15) Y-Position von Sprite 7
SPRITESMAXX         = $d010         ;(16) Höhstes BIT der jeweiligen X-Position
                                    ;        da der BS 320 Punkte breit ist reicht
                                    ;        ein BYTE für die X-Position nicht aus!
                                    ;        Daher wird hier das 9. Bit der X-Pos
                                    ;        gespeichert. BIT-Nr. (0-7) = Sprite-Nr.
SPRITEACTIV         = $d015         ;(21) Bestimmt welche Sprites sichtbar sind
                                    ;        Bit-Nr. = Sprite-Nr.
SPRITEDOUBLEHEIGHT  = $d017         ;(23) Doppelte Höhe der Sprites
                                    ;        Bit-Nr. = Sprite-Nr.
SPRITEDEEP          = $d01b         ;(27) Legt fest ob ein Sprite vor oder hinter
                                    ;        dem Hintergrund erscheinen soll.
                                    ;        Bit = 1: Hintergrund vor dem Sprite
                                    ;        Bit-Nr. = Sprite-Nr.
SPRITEMULTICOLOR    = $d01c         ;(28) Bit = 1: Multicolor Sprite 
                                    ;        Bit-Nr. = Sprite-Nr.
SPRITEDOUBLEWIDTH   = $d01d         ;(29) Bit = 1: Doppelte Breite des Sprites
                                    ;        Bit-Nr. = Sprite-Nr.
SPRITESPRITECOLL    = $d01e         ;(30) Bit = 1: Kollision zweier Sprites
                                    ;        Bit-Nr. = Sprite-Nr.
                                    ;        Der Inhalt wird beim Lesen gelöscht!!
SPRITEBACKGROUNDCOLL= $d01f         ;(31) Bit = 1: Sprite / Hintergrund Kollision
                                    ;        Bit-Nr. = Sprite-Nr.
                                    ;        Der Inhalt wird beim Lesen gelöscht!
SPRITEMULTICOLOR0   = $d025         ;(37) Spritefarbe 0 im Multicolormodus
SPRITEMULTICOLOR1   = $d026         ;(38) Spritefarbe 1 im Multicolormodus
SPRITE0COLOR        = $d027         ;(39) Farbe von Sprite 0
SPRITE1COLOR        = $d028         ;(40) Farbe von Sprite 1
SPRITE2COLOR        = $d029         ;(41) Farbe von Sprite 2
SPRITE3COLOR        = $d02a         ;(42) Farbe von Sprite 3
SPRITE4COLOR        = $d02b         ;(43) Farbe von Sprite 4
SPRITE5COLOR        = $d02c         ;(44) Farbe von Sprite 5
SPRITE6COLOR        = $d02d         ;(45) Farbe von Sprite 6
SPRITE7COLOR        = $d02e         ;(46) Farbe von Sprite 7

;*******************************************************************************
;*** Die VIC II Register  -  ENDE                                            ***
;*******************************************************************************

SCREENRAM           = $0400             ;Beginn des Bildschirmspeichers
SPRITE0DATA         = SCREENRAM+$03f8   ;Sprite-Pointer für die
                                        ;Adresse der Sprite-0-Daten
SPRITE1DATA         = SCREENRAM+$03f9   ;wie eben, nur für Sprite-1

;*** Startadresse
*=$0801
 
;*** BASIC-Zeile
 !word main-2
 !word 2018
 !text $9E," 2062",$00,$00,$00
 
!zone main
main
 lda #<spritedata                   ;LSB der Spritedaten holen
 sta calc16Bit                      ;im 'Hilfsregister' speichern
 lda #>spritedata                   ;MSB auch
 sta calc16Bit+1                    ;ins 'Hilfregister'
 ldx #$06                           ;Schleifenzähler fürs Teilen durch 64
.loop
 lsr calc16Bit+1                    ;MSB nach rechts 'shiften'
 ror calc16Bit                      ;LSB nach rechts 'rotieren', wg. Carry-Flag!
 dex                                ;Schleifenzähler verringern
 bne .loop                          ;wenn nicht 0, nochmal

 ldx calc16Bit                      ;Im LSB des 'Hilfsregisters' steht der 64-Byte-Block
 stx SPRITE0DATA                    ;In der zuständigen Speicherstelle ablegen
 inx                                ;Das 2. Sprite folgt direkt dahinter, also +1
 stx SPRITE1DATA                    ;und dem VIC II mitteilen

;*** Weitere Sourcezeilen ab hier einfügen...
 lda #%00000000                     ;Keine Vergrößerung gewünscht
 sta SPRITEDOUBLEHEIGHT             ;doppelte Höhe zurücksetzen
 sta SPRITEDOUBLEWIDTH              ;doppelte Breite zurücksetzen
 lda #%00000010                     ;nur Sprite-1 ist Mulicolor, der Rest Hi-Res
 sta SPRITEMULTICOLOR               ;Multicolor für Sprite-1 aktivieren
 lda #%00000000                     ;Alle Sprites vor dem Hintergrund anzeigen
 sta SPRITEDEEP
 lda #COLOR_PURPLE                  ;Farbe für
 sta SPRITE0COLOR                   ;Sprite-0 (hat nur eine, da Hi-Res!)

 lda #COLOR_BLACK                   ;Hauptfarbe für
 sta SPRITE1COLOR                   ;Sprite-1 (ist Multicolor, daher Hauptfarbe!)
 lda #COLOR_RED                     ;Farbe für
 sta SPRITEMULTICOLOR0              ;Multicolor-0
 lda #COLOR_YELLOW                  ;Farbe für
 sta SPRITEMULTICOLOR1              ;Mulitcolor-1

 ldx #$00                           ;Zur Sicherheit das höchste
 stx SPRITESMAXX                    ;Bit für die X-Position löschen
 ldx #$20                           ;X-Pos für
 stx SPRITE0X                       ;Sprite-0
 ldx #$40                           ;X-Pos für
 stx SPRITE1X                       ;Sprite-1

 ldy #$80                           ;Y-Position
 sty SPRITE0Y                       ;für Sprite-0
 sty SPRITE1Y                       ;und Sprite-1

 lda #%00000011                     ;nur Sprite-0 und 1
 sta SPRITEACTIV                    ;aktivieren, alle anderen ausblenden
 
 rts
 
 

;*** Hilfsregister für 16-Operartionen
calc16Bit
 !byte $00, $00

;*** Mittels !align 63,0', den Assembler anweisen, den Beginn 
;*** der Daten an eine durch 64 teilbare Adresse zulegen.
!align 63,0

spritedata
; Hi-Res-Sprite
!byte  $ff,$ff,$ff
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $ff,$08,$ff
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $ff,$08,$ff
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $ff,$ff,$ff
!byte  $00                          ;auf 64 Bytes auffüllen

; Multicolor-Sprite
!byte  $aa,$55,$ff
!byte  $aa,$55,$ff
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $aa,$55,$ff
!byte  $aa,$55,$ff
!byte  $00                          ;auf 64 Bytes auffüllen
Unsere bedeiden Sprites
Unsere beiden Sprites

 

Aktiviert jetzt mal für Sprite-0 die Verdopplung.
Ändert dazu die Zeile:

 lda #%00000000               ;Keine Vergrößerung gewünscht

in

 lda #%00000001               ;Vergrößerung für Sprite-0

Da wir den Akku danach in die Register $d017 und $d01d schreiben, wird unser Sprite-0 sofort in Breite und Höhe verdoppelt. Selbstverständlich könnt ihr das Sprite auch nur horizontal oder vertikal vergrößern.

ASM-SpriteTest002

Bei dieser Vergrößerung könnt ihr gleich zwei Sachen genauer erkennen:

  1. Unser Sprite-0 wird wie gewünscht vor dem Hintergrund dargestellt
  2. Sprite-0 überlagert Sprite-1, da die Priorität von Sprite-0 höher ist.

Jetzt soll Sprite-0 hinter dem Hintergrund erscheinen, ändert dazu

 lda #%00000000               ;Alle Sprites vor dem Hintergrund anzeigen

in

 lda #%00000001               ;Sprite-0 hinter, den Rest vor dem Hintergrund

ASM-SpriteTest003

Damit es etwas deutlicher wird, habe ich den Cursor um eine Stelle nach rechts bewegt. Man kann aber durchaus erkennen, dass der Text jetzt über Sprite-0 liegt.

Verschiebt ihr jetzt den Cursor auf einen Bereich, an dem sich Sprite-0 und 1 überschneiden, dann seht ihr, dass, obwohl Sprite-1 vor dem Hintergrund erscheinen soll, der Hintergrund an den Stellen sichtbar wird, an denen Sprite-0 das nächste Sprite überdeckt.

ASM-SpriteTest004

Das liegt daran, dass zuerst die Farbe von Sprite-0 bestimmt wird (Sprite- oder Hintergrundfarbe) dann wird Sprite-0 auf Grund der höheren Priorität über Sprite-1 gezeichnet und somit bleibt die Hintergrundfarbe an den Schnittpunkten erhalten.

I like to move it, move it

Bringen wir mal etwas Bewegung in die ganze Sache. Sprite-1 (Mulitcolor) soll sich einfach mal auf und ab bewegen. Dabei soll es erst umkehren, wenn es jeweils komplett unter dem oberen bzw. unterem Rand verschwunden ist. Fügt dazu einfach hinter unsere Aktivierung sta SPRITEACTIV und vor dem rts die folgenden Zeilen ein.

move
 inc SPRITE1Y                       ;Y-Pos von Sprite-1 erhöhen
 lda SPRITE1Y                       ;für den Vergleich in den Akku holen
 cmp #$FA                           ;Sprite-1 hinter unterem Rand?
 bne move_0                         ;wenn nein, dann weiter bei move_0
 lda #$CE                           ;sonst, OpCode für DEC in den Akku
 sta move                           ;und bei move INC wieder in DEC ändern
move_0
 cmp #$1D                           ;Sprite-1 unter oberem Rand verschwunden?
 bne move_1                         ;wenn nein, weiter bei move_1
 lda #$EE                           ;sonst, OpCode für INC in den Akku
 sta move                           ;und bei move DEC in INC ändern
move_1
 jmp move                           ;Endlosschleife!!!

Wie ihr seht, erhöhen wir bei move zunächst direkt das Register SPRITE1Y. Damit bewegt sich das Sprite-1 nach unten. Dann holen wir das Register SPRITE1Y in den Akku, um zu prüfen, ob es vollständig unter dem unteren Rand verschwunden ist. Dies ist der Fall, sobald das Sprite-1 die Position 250 $fa erreicht hat. Ist die Postion noch nicht erreicht, dann springen wir zu move_0, um den oberen Rand zu testen. Ist das Sprite aber in der 250. Zeile angekommen, dann laden wir den OpCode für den absolut adressierten DEC-Befehl in den Akku und überschreiben den bisherigen INC-Befehl bei move. Durch die Veränderung unseres Programmes zur Laufzeit, haben wir hier also eine Umkehr der Bewegungsrichtung erzielt. Das Programm läuft anschließend bei move_0 weiter. Hier wird nun geprüft, ob das Sprite-1 unter dem oberen Rand verschunden ist, falls ja, tauschen wir bei move den DEC-Befehl wieder gegen INC aus, anderenfalls geht es bei move_1 weiter. Das abschließende move_1 benötigen wir erst gleich, für die Bewegung von Sprite-0.

Wenn ihr das Programm nun startet, seht ihr das Sprite-1 wahrscheinlich mehrfach. Das liegt daran, dass die Bewegung so schnell vonstatten geht, dass unser Auge nicht mehr mitkommt. Normalerweise würde man das Programm über einen Interrupt im Takt halten, aber wir benutzen für unseren Test hier einfach eine Warteschleife.

Fügt zwischen move_1 und dem jmp move die folgenden Zeilen ein:

sleep                               ;kleine Warteschleife
 ldx #$05                           ;Startwert für äußere Schleife
sleepx
 ldy #$00                           ;Startwert für innere Schleife
sleepy
 dey                                ;inneren Schleifenzähler verringern
 bne sleepy                         ;falls nicht 0, weiter bei sleepy
 dex                                ;äußeren Schleifenzähler verringern
 bne sleepx                         ;falls nicht 0, weiter bei sleepx

Die verschaltelte Schleife sollte für euch kein Problem sein, durch anpassen von X und Y könnt ihr die Wartezeit beeinflussen.

Jetzt solltet ihr die Bewegung des Sprites, bei einem erneuten Start, in Ruhe verfolgen können.

Soweit, so gut, nun wollen wir Sprite-0 von links nach rechts und umgekehrt bewegen. Dabei soll jetzt aber das Sprite-0 vom Rahmen abprallen, also nicht dahinter verschwinden. Gebt die folgenden Zeilen ab dem bereits vorhandenen Label move_1 ein.

 inc SPRITE0X                       ;X-Pos von Sprite-0 erhöhen
 bne move_2                         ;Sprite-0 erreicht nie 0, daher erkennen wir
                                    ;hier das Überschreiten der 255. X-Position
 lda SPRITESMAXX                    ;die höchsten Bits in den Akku
 eor #%00000001                     ;und für Sprite-0 setzen/löschen
 sta SPRITESMAXX                    ;wieder zurückschreiben
 and #%00000001                     ;prüfen, ob das höchste Bit gesetzt ist
 bne move_2                         ;wenn ja, weiter bei move_2
 dec SPRITE0X                       ;sonst X-Pos korrigieren
move_2
 lda SPRITESMAXX                    ;die höchsten Bits in den Akku
 and #%00000001                     ;prüfen, ob das höchste Bit gesetzt ist
 beq move_3                         ;falls nicht, weiter bei move_3
 lda SPRITE0X                       ;sonst X-Pos in den Akku
 cmp #$40                           ;rechter Rand erreicht?
 bne coll_1                         ;falls nicht, weiter bei coll_1
 lda #$ce                           ;sonst, OpCode für DEC in den Akku
 sta move_1                         ;und bei move_1 INC in DEC ändern
 jmp coll_1                         ;weiter zur Kollisionsprüfung
move_3
 lda SPRITE0X                       ;X-Pos in den Akku
 cmp #$20-8                         ;linker Rand erreicht?
 bne coll_1                         ;falls nicht, weiter bei coll_1
 lda #$ee                           ;sonst OpCode für INC in den Akku
 sta move_1                         ;und bei move_1 DEC in INC ändern
coll_1

Prinzipiell machen wir das Gleiche wie eben. Hinter move_1 werden INC und DEC wieder gegeneinander ausgetauscht, um die Bewegungsrichtung zu ändern. Aber die Bewegung in X-Richtung erfordert etwas mehr Aufwand, wir müssen ja auch das höchste Bit im Register $d010 setzen, sobald die X-Position größer als 255 ist. Wir werden mit einer kleinen Unzulänglichlkeit dieses Problem lösen. Da wir unser Sprite-0 immer um einen Pixel bewegen und der linke Rand 24 Pixel breit ist, können wir nach INC bzw. DEC an einer 0 erkennen, ob die 255. X-Position betroffen ist. Das funktioniert beim Überlauf von $ff => $100 auch wie geplant, wir landen wieder bei 0 und können das höchste Bit setzen. Genaugenommen schalten wir es mit dem EOR #%00000001 einfach um. Wir prüfen dann mit dem AND #%00000001, ob das höchste Bit für Sprite-0 gesetzt ist, wenn ja geht es bei move_2 weiter. Anderenfalls verringern wir die X-Position aber nochmal. Daher bewegt sich unser Sprite-0, wenn es von rechts nach links läuft von der Position 257 auf 255. Wer den Grund für diese unschöne Lösung (OK ich war nur zu faul es korrekt zu machen 😉 ) sehen möchte, der kann die Zeile ja mal auskommentieren und genau darauf achten, was dann passiert. Da es auf unser Beispiel keine weiteren Auswirkungen hat, belasse ich es aber dabei. Ihr könnt das ja mal korrigieren. Bei move_2 schauen wir dann, ob das höchste Bit gesetzt ist, um zu entscheiden, ob wir den linken oder rechten Rand prüfen müssen. Hier geschieht wieder das Gleiche, wie beim Sprite-1. Wird der Rand erreicht, dann schalten wir den Befehl hinter move_1 um, damit das Sprite-0 in die andere Richtung läuft. Diese Art der Bewegung taugt wirklich nur für unser Beispiel. Für eure Programme solltet ihr die anders realisieren.
Zum Schluß gibt es wieder ein Label coll_1, dass uns erst gleich beim letzten Abschnitt interessiert.

Das Programm läßt sich nun starten und die beiden Sprites bewegen sich über den Bildschirm. Beim Sprite-0 (links/rechts) könnt ihr evtl. das Tearing erkennen, fällt bei Vollbild bzw. auf einem großen Bildschirm besonders auf.

Sprites: Tearing
Tearing

Tearing tritt auf, wenn man mit dem Zeichnen des nächsten Bildes bzw. Änderungen daran, nicht wartet, bis der Rasterstrahl eine definierte Position (am besten außerhalb des sichtbaren Bereichs) erreicht hat. Oben im Bild wurde das Sprite von rechts nach links bewegt und während der Rasterstrahl das aktuelle Bild gezeichnet hat, haben wir das Sprite um einen Pixel nach links verschoben, so das der Rest nun versetzt angezeigt wird.

Es sollte klar sein, dass durch gleichzeitige Veränderung der X- und Y-Position eines Sprites, sich dieses in jedem erdenklichen Bewegungsmuster (z. B. linear oder sinusförmig) an jede gewünschte Bildschirmposition bringen lässt.

Achtung: Kollision!

Abschließend wollen wir noch einen Blick auf die Kollisionserkennung werfen. Diese kann man auch über einen Interrupt durchführen, aber wir beschränken uns hier aufs manuelle Prüfen.
Wenn die beiden Sprites (0 & 1) sich berühren, dann wollen wir die Rahmenfarbe ändern. Trifft Sprite-1 auf den Hintergrund, dann soll die Hintergrundfarbe geändert werden.

Fügt also, hinter dem vorhandenen coll_1, diese Zeilen ein:

coll_1                              ;Kollisionsprüfungen
 lda SPRITESPRITECOLL               ;Register für Sprite/Sprite-Kollisionen holen
 beq coll_2                         ;falls keine Kollision, weiter bei coll_2
 inc $d020                          ;sonst, Rahmenfarbe erhöhen
coll_2
 lda SPRITEBACKGROUNDCOLL           ;Register für Sprite/Hintergrund-Kollision holen
 and #%00000010                     ;prüfen, ob Sprite-0 den Hintergrund 'berührt'
 beq sleep                          ;falls nicht, weiter zu sleep
 inc $d021                          ;sonst, Hintergrundfarbe erhöhen

Da wir nur zwei Sprites haben, machen wir es uns für die Sprite/Sprite-Kollision sehr einfach und kontrollieren nur, ob das Register $d01e SPRITESPRITECOLL noch null ist. Ist dies der Fall, dann haben wir keine Kollision und springen zu coll_2, sobald es ungleich null ist, berühren sich zwei Sprites und wir ändern die Rahmenfarbe.

Bei coll_2 holen wir zunächst das Register $d01f für die Sprite/Hintergrund-Kollision SPRITEBACKGROUNDCOLL in den Akku. Dann prüfen wir ob Bit-1 (fürs 1. Sprite) gesetzt ist, falls nicht gehts zur Warteschleife bei sleep, wenn ja ändern wir die Hintergrundfarbe.

Beachtet, dass die Register $d01e und $d01f durchs Lesen gelöscht werden. Solltet ihr die Werte später nochmal benötigen, dann müsst ihr diese irgendwo speichern. Überprüfen könnt ihr dies, indem ihr einfach mal die entsprechenden lda-Befehle verdoppelt. Dann sollten Änderung an der Rahmen- bzw. Hintergrundfarbe kaum noch auftreten. Es kann immer noch passieren, dass beim zweiten lda wieder eine neue Kollision erkannt wurde und das Register wieder gefüllt ist, aber das passiert nur noch sehr selten.

;*******************************************************************************
;*** Farben                                                                  ***
;*******************************************************************************
COLOR_BLACK         = $00           ;schwarz
COLOR_WHITE         = $01           ;weiß
COLOR_RED           = $02           ;rot
COLOR_CYAN          = $03           ;türkis
COLOR_PURPLE        = $04           ;lila
COLOR_GREEN         = $05           ;grün
COLOR_BLUE          = $06           ;blau
COLOR_YELLOW        = $07           ;gelb
COLOR_ORANGE        = $08           ;orange
COLOR_BROWN         = $09           ;braun
COLOR_PINK          = $0a           ;rosa
COLOR_DARKGREY      = $0b           ;dunkelgrau
COLOR_GREY          = $0c           ;grau
COLOR_LIGHTGREEN    = $0d           ;hellgrün
COLOR_LIGHTBLUE     = $0e           ;hellblau
COLOR_LIGHTGREY     = $0f           ;hellgrau

;*******************************************************************************
;*** Die VIC II Register  -  ANFANG                                          ***
;*******************************************************************************
VICBASE             = $d000         ;(RG) = Register-Nr.
SPRITE0X            = $d000         ;(00) X-Position von Sprite 0
SPRITE0Y            = $d001         ;(01) Y-Position von Sprite 0
SPRITE1X            = $d002         ;(02) X-Position von Sprite 1
SPRITE1Y            = $d003         ;(03) Y-Position von Sprite 1
SPRITE2X            = $d004         ;(04) X-Position von Sprite 2
SPRITE2Y            = $d005         ;(05) Y-Position von Sprite 2
SPRITE3X            = $d006         ;(06) X-Position von Sprite 3
SPRITE3Y            = $d007         ;(07) Y-Position von Sprite 3
SPRITE4X            = $d008         ;(08) X-Position von Sprite 4
SPRITE4Y            = $d009         ;(09) Y-Position von Sprite 4
SPRITE5X            = $d00a         ;(10) X-Position von Sprite 5
SPRITE5Y            = $d00b         ;(11) Y-Position von Sprite 5
SPRITE6X            = $d00c         ;(12) X-Position von Sprite 6
SPRITE6Y            = $d00d         ;(13) Y-Position von Sprite 6
SPRITE7X            = $d00e         ;(14) X-Position von Sprite 7
SPRITE7Y            = $d00f         ;(15) Y-Position von Sprite 7
SPRITESMAXX         = $d010         ;(16) Höhstes BIT der jeweiligen X-Position
                                    ;        da der BS 320 Punkte breit ist reicht
                                    ;        ein BYTE für die X-Position nicht aus!
                                    ;        Daher wird hier das 9. Bit der X-Pos
                                    ;        gespeichert. BIT-Nr. (0-7) = Sprite-Nr.
SPRITEACTIV         = $d015         ;(21) Bestimmt welche Sprites sichtbar sind
                                    ;        Bit-Nr. = Sprite-Nr.
SPRITEDOUBLEHEIGHT  = $d017         ;(23) Doppelte Höhe der Sprites
                                    ;        Bit-Nr. = Sprite-Nr.
SPRITEDEEP          = $d01b         ;(27) Legt fest ob ein Sprite vor oder hinter
                                    ;        dem Hintergrund erscheinen soll.
                                    ;        Bit = 1: Hintergrund vor dem Sprite
                                    ;        Bit-Nr. = Sprite-Nr.
SPRITEMULTICOLOR    = $d01c         ;(28) Bit = 1: Multicolor Sprite 
                                    ;        Bit-Nr. = Sprite-Nr.
SPRITEDOUBLEWIDTH   = $d01d         ;(29) Bit = 1: Doppelte Breite des Sprites
                                    ;        Bit-Nr. = Sprite-Nr.
SPRITESPRITECOLL    = $d01e         ;(30) Bit = 1: Kollision zweier Sprites
                                    ;        Bit-Nr. = Sprite-Nr.
                                    ;        Der Inhalt wird beim Lesen gelöscht!!
SPRITEBACKGROUNDCOLL= $d01f         ;(31) Bit = 1: Sprite / Hintergrund Kollision
                                    ;        Bit-Nr. = Sprite-Nr.
                                    ;        Der Inhalt wird beim Lesen gelöscht!
SPRITEMULTICOLOR0   = $d025         ;(37) Spritefarbe 0 im Multicolormodus
SPRITEMULTICOLOR1   = $d026         ;(38) Spritefarbe 1 im Multicolormodus
SPRITE0COLOR        = $d027         ;(39) Farbe von Sprite 0
SPRITE1COLOR        = $d028         ;(40) Farbe von Sprite 1
SPRITE2COLOR        = $d029         ;(41) Farbe von Sprite 2
SPRITE3COLOR        = $d02a         ;(42) Farbe von Sprite 3
SPRITE4COLOR        = $d02b         ;(43) Farbe von Sprite 4
SPRITE5COLOR        = $d02c         ;(44) Farbe von Sprite 5
SPRITE6COLOR        = $d02d         ;(45) Farbe von Sprite 6
SPRITE7COLOR        = $d02e         ;(46) Farbe von Sprite 7

;*******************************************************************************
;*** Die VIC II Register  -  ENDE                                            ***
;*******************************************************************************

SCREENRAM           = $0400             ;Beginn des Bildschirmspeichers
SPRITE0DATA         = SCREENRAM+$03f8   ;Sprite-Pointer für die
                                        ;Adresse der Sprite-0-Daten
SPRITE1DATA         = SCREENRAM+$03f9   ;wie eben, nur für Sprite-1

;*** Startadresse
*=$0801
 
;*** BASIC-Zeile
 !word main-2
 !word 2018
 !text $9E," 2062",$00,$00,$00
 
!zone main
main
 lda #<spritedata                   ;LSB der Spritedaten holen
 sta calc16Bit                      ;im 'Hilfsregister' speichern
 lda #>spritedata                   ;MSB auch
 sta calc16Bit+1                    ;ins 'Hilfregister'
 ldx #$06                           ;Schleifenzähler fürs Teilen durch 64
.loop
 lsr calc16Bit+1                    ;MSB nach rechts 'shiften'
 ror calc16Bit                      ;LSB nach rechts 'rotieren', wg. Carry-Flag!
 dex                                ;Schleifenzähler verringern
 bne .loop                          ;wenn nicht 0, nochmal

 ldx calc16Bit                      ;Im LSB des 'Hilfsregisters' steht der 64-Byte-Block
 stx SPRITE0DATA                    ;In der zuständigen Speicherstelle ablegen
 inx                                ;Das 2. Sprite folgt direkt dahinter, also +1
 stx SPRITE1DATA                    ;und dem VIC II mitteilen

;*** Weitere Sourcezeilen ab hier einfügen...
 lda #%00000000                     ;Keine Vergrößerung gewünscht
 sta SPRITEDOUBLEHEIGHT             ;doppelte Höhe zurücksetzen
 sta SPRITEDOUBLEWIDTH              ;doppelte Breite zurücksetzen
 lda #%00000010                     ;nur Sprite-1 ist Mulicolor, der Rest Hi-Res
 sta SPRITEMULTICOLOR               ;Multicolor für Sprite-1 aktivieren
 lda #%00000000                     ;Alle Sprites vor dem Hintergrund anzeigen
 sta SPRITEDEEP
 lda #COLOR_PURPLE                  ;Farbe für
 sta SPRITE0COLOR                   ;Sprite-0 (hat nur eine, da Hi-Res!)

 lda #COLOR_BLACK                   ;Hauptfarbe für
 sta SPRITE1COLOR                   ;Sprite-1 (ist Multicolor, daher Hauptfarbe!)
 lda #COLOR_RED                     ;Farbe für
 sta SPRITEMULTICOLOR0              ;Multicolor-0
 lda #COLOR_YELLOW                  ;Farbe für
 sta SPRITEMULTICOLOR1              ;Mulitcolor-1

 ldx #$00                           ;Zur Sicherheit das höchste
 stx SPRITESMAXX                    ;Bit für die X-Position löschen
 ldx #$20                           ;X-Pos für
 stx SPRITE0X                       ;Sprite-0
 ldx #$40                           ;X-Pos für
 stx SPRITE1X                       ;Sprite-1

 ldy #$80                           ;Y-Position
 sty SPRITE0Y                       ;für Sprite-0
 sty SPRITE1Y                       ;und Sprite-1

 lda #%00000011                     ;nur Sprite-0 und 1
 sta SPRITEACTIV                    ;aktivieren, alle anderen ausblenden
 
move
 inc SPRITE1Y                       ;Y-Pos von Sprite-1 erhöhen
 lda SPRITE1Y                       ;für den Vergleich in den Akku holen
 cmp #$fa                           ;Sprite-1 hinter unterem Rand?
 bne move_0                         ;wenn nein, dann weiter bei move_0
 lda #$ce                           ;sonst, OpCode für DEC in den Akku
 sta move                           ;und bei move INC wieder in DEC ändern
move_0
 cmp #$1d                           ;Sprite-1 unter oberem Rand verschwunden?
 bne move_1                         ;wenn nein, weiter bei move_1
 lda #$ee                           ;sonst, OpCode für INC in den Akku
 sta move                           ;und bei move DEC in INC ändern
move_1
 inc SPRITE0X                       ;X-Pos von Sprite-0 erhöhen
 bne move_2                         ;Sprite-0 erreicht nie 0, daher erkennen wir
                                    ;hier das Überschreiten der 255. X-Position
 lda SPRITESMAXX                    ;die höchsten Bits in den Akku
 eor #%00000001                     ;und für Sprite-0 setzen/löschen
 sta SPRITESMAXX                    ;wieder zurückschreiben
 and #%00000001                     ;prüfen, ob das höchste Bit gesetzt ist
 bne move_2                         ;wenn ja, weiter bei move_2
 dec SPRITE0X                       ;sonst X-Pos korrigieren
move_2
 lda SPRITESMAXX                    ;die höchsten Bits in den Akku
 and #%00000001                     ;prüfen, ob das höchste Bit gesetzt ist
 beq move_3                         ;falls nicht, weiter bei move_3
 lda SPRITE0X                       ;sonst X-Pos in den Akku
 cmp #$40                           ;rechter Rand erreicht?
 bne coll_1                         ;falls nicht, weiter bei coll_1
 lda #$ce                           ;sonst, OpCode für DEC in den Akku
 sta move_1                         ;und bei move_1 INC in DEC ändern
 jmp coll_1                         ;weiter zur Kollisionsprüfung
move_3
 lda SPRITE0X                       ;X-Pos in den Akku
 cmp #$20-8                         ;linker Rand erreicht?
 bne coll_1                         ;falls nicht, weiter bei coll_1
 lda #$ee                           ;sonst OpCode für INC in den Akku
 sta move_1                         ;und bei move_1 DEC in INC ändern
coll_1                              ;Kollisionsprüfungen
 lda SPRITESPRITECOLL               ;Register für Sprite/Sprite-Kollisionen holen
 beq coll_2                         ;falls keine Kollision, weiter bei coll_2
 inc $d020                          ;sonst, Rahmenfarbe erhöhen
coll_2
 lda SPRITEBACKGROUNDCOLL           ;Register für Sprite/Hintergrund-Kollision holen
 and #%00000010                     ;prüfen, ob Sprite-0 den Hintergrund 'berührt'
 beq sleep                          ;falls nicht, weiter zu sleep
 inc $d021                          ;sonst, Hintergrundfarbe erhöhen
sleep                               ;kleine Warteschleife
 ldx #$05                           ;Startwert für äußere Schleife
sleepx
 ldy #$00                           ;Startwert für innere Schleife
sleepy
 dey                                ;inneren Schleifenzähler verringern
 bne sleepy                         ;falls nicht 0, weiter bei sleepy
 dex                                ;äußeren Schleifenzähler verringern
 bne sleepx                         ;falls nicht 0, weiter bei sleepx
 jmp move                           ;Endlosschleife!!! 
 
 rts
 
 

;*** Hilfsregister für 16-Operartionen
calc16Bit
 !byte $00, $00

;*** Mittels !align 63,0', den Assembler anweisen, den Beginn 
;*** der Daten an eine durch 64 teilbare Adresse zulegen.
!align 63,0

spritedata
; Hi-Res-Sprite
!byte  $ff,$ff,$ff
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $ff,$08,$ff
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $ff,$08,$ff
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $81,$08,$81
!byte  $81,$10,$81
!byte  $ff,$ff,$ff
!byte  $00                          ;auf 64 Bytes auffüllen

; Multicolor-Sprite
!byte  $aa,$55,$ff
!byte  $aa,$55,$ff
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $82,$41,$c3
!byte  $aa,$55,$ff
!byte  $aa,$55,$ff
!byte  $00                          ;auf 64 Bytes auffüllen

Das Programm ist nun fertig und ihr könnt das Ergebnis nach einem Start bewundern.

Ihr solltet nun viel damit experimentieren und euren Umgang mit Sprites vertiefen. Im Rahmen des L.O.V.E.-Projektes werden später natürlich weiterführende Themen behandelt (z. B. Animation, mehr als 8 Sprites usw.).

Für unsere Berechnung, der Spriteadressen haben wir die 16-Bit 
Adresse 6x nach rechts 'geshiftet'.

Beispiel: Unser Sprite liegt an der Adresse $0980 oder binär
%0000100110000000
um sechs Stellen nach rechts 'geshiftet'
%0000000000100110

Im LSB steht nun unser 64-Byte-Block (%00100110 bzw. $26 bzw. 38)

Wir können die 16-Bit Adresse aber auch einfach 2x nach links
'shiften' und finden das Ergebnis dann im MSB.

%0000100110000000
um zwei Stellen nach links 'shiften'
%0010011000000000

Probiert es ruhig mal mit anderen Adressen aus, achtet nur darauf,
dass die Ausgangsadresse immer durch 64 teilbar ist und innerhalb
der ersten 16KB liegt.
Wie oben erwähnt, klappt die Erkennung ob die 256. Position in
SPRITE0X erreicht ist problemlos, wenn das Sprite sich von
links nach rechts bewegt.

SPRITE0X   höchstes Bit   BS_Pos
  $fe          %0          254

+1
  $ff          %0          255

+1, nun kippt $ff nach $00 ($100 passt ja nicht in ein Byte!)
    und wir setzten das höchste Bit, da wir auf $00 in 
    SPRITE0X reagieren
  $00    ->    %1          256

+1
  $01          %1          257

Jetzt bewegen wir das Sprite wieder zurück von rechts nach links

SPRITE0X   höchstes Bit   BS_Pos
  $01          %1          257

-1, da wir auf $00 in SPRITE0X reagieren, löschen wir das
    höchste Bit wieder!
  $00   ->     %0           0 !!! 
Das Sprite würde jetzt sehr kurz ganz links erscheinen!!!
Deshalb korrgieren wir das und ziehen direkt noch mal
1 ab, damit das Sprite so schnell wie möglich an der
Position 255 $ff gezeichnet wird.

Wie erwähnt, ist das ganz großer Mist, einfach eine Position zu
überspringen und zu hoffen, dass die Korrektur schnell genug
geschieht!

Schrott!!Naja...Geht so...Ganz gut...SUPER! (15 Bewertungen | Ø 4,93 von 5 | 98,67%)

Loading...


Zurück

12 Gedanken zu „Sprites (ASM)“

  1. Hallo Jörn. Super Tutorial.

    Ich habe einen kleinen “Fehler” im Text gefunden. Du schreibst: “Unsere beiden Sprites sollen zunächst ohne jegliche Vergrößerung angezeigt werden. Daher löschen wir die Register $d017 und $d018”.

    Statt der $d018 sollte da $d01d stehen. Ist wohl ein Tippfehler.

    Ich nutze dieses Tutorial immer wieder zur Auffrischung meiner Kenntnisse.

    Vielen Dank für diese Site.

  2. Hallo Jörn,
    schöner Kurs, bin fleißig am Durcharbeiten .. mit dem Turbo-Assembler 7.4 auf einem “echten” C64.

    Stoße aber gerade auf ein Problem, und zwar die Direktive

    .align

    will der TASM 7.4 nicht fressen, habe probiert:

    .align 63,0
    .align 64
    etc.

    Bleibt leider rot 🙁

    Hast Du noch eine Idee?

    Grüße,
    thomas

    1. Moin Thomas,

      das Align, das einige Assembler bieten, ist nur eine Komfortfunktion.
      Man kann die gewünschte Adresse auch mit dem Sternchen [*] setzen, dort lassen sich Berechnungen z. B. *=*+50/10 (5 zur aktuellen Adresse hinzuaddieren) vornehmen.

      Gruß,
      Jörn

  3. Hallo Jörn,

    mit der Backgroud-Kollision tue ich mich noch sehr schwer. Ich habe mal in der Routine “coll_2” das “inc $d021” durch ein “inc SPRITE1Y ” ersetzt (s.u.).
    Da Sprite 1 als erstes von unten mit dem Punkt von “Ready.” kollidiert, müsste Sprite 1 stehen bleiben, da die letzte Bewegung wieder rückgängig gemacht wird. Stattdessen wird Sprite 1 nur etwas langsamer.

    Erst bei:

    coll_2
    lda SPRITEBACKGROUNDCOLL ;Register für Sprite/Hintergrund-Kollision holen
    and #%00000010 ;prüfen, ob Sprite-0 den Hintergrund ‘berührt’
    beq sleep ;falls nicht, weiter zu sleep
    inc SPRITE1Y ;Y-Pos von Sprite-1 erhöhen
    inc SPRITE1Y ;Y-Pos von Sprite-1 erhöhen
    inc SPRITE1Y ;Y-Pos von Sprite-1 erhöhen
    ;inc $d021 ;sonst, Hintergrundfarbe erhöhen

    bleibt Sprite1 mehr oder weniger auf der gleichen Position. Es zittert eine Weile auf einer Position, macht nochmal einen kleinen Vorwärtssprung und zittert dann weiter auf der neuen Position. Scheinbar werden nicht alle Einzelbewegungen in dem Register berücksichtigt?

    Wie kann man das $d01f-Register so einsetzen, dass es pixelgenau und bei jedem Schritt zuverlässig abgefragt werden kann?

    Viele Grüße

    Henner

    1. Moin Henner,

      das Hauptproblem ist, dass $D01F nicht „live“ aktualisiert wird. Das entsprechende BIT wird erst beim Zeichnen des Sprites gesetzt, falls eine Kollision vorliegt.
      Das Beispielprogramm läuft einfach zu schnell. Die Sprites werden häufiger bewegt, als sie vom VIC gezeichnet werden, daher gehen Kollisionen verloren und dein Sprite bewegt sich nur etwas langsamer, anstatt stehen zu bleiben.
      Sobald du die Warteschleife am Ende bei sleep gegen eine Routine tauschst, die darauf wartet, dass ein neuer Bildschirmaufbau beginnt, bleibt das Sprite auch wie von dir erwartet stehen.
      Die beiden Sprites bewegen sich dann allerdings auch sehr viel langsamer!

      Wie du siehst, ist das Register $D01F für eine pixelgenaue Kollisionsabfrage relativ ungeeignet.

      • Wie eben erwähnt, wird eine Kollision erst beim Zeichnen des Sprites erkannt.
      • Falls das Register nicht gelesen wird (nur dies löscht eine erkannte Kollision), zeigt es weiterhin die letzte Kollision an, auch wenn aktuell evtl. gar keine mehr vorliegt.
      • Bewegen sich Sprites schneller, als mit einem Pixel je Bildschirmaufbau (s. o.), ist die Gefahr, dass eine Kollision unerkannt bleibt, relativ hoch.
      • Auch Sprites mit „Hohlräumen“ können bei schnellen Bewegungen Probleme machen. Ein transparentes Fenster würde dann evtl. keine Kollision auslösen.

      Es läuft also darauf hinaus, dass man sich seine pixelgenaue Kollisionsabfrage selbst bauen muss.

      Gruß,
      Jörn

  4. Hallo Jörn,
    es hat sich leider ein kleiner Fehler in der Beschreibung der Farbzuweisungen für die MC-Sprites eingeschlichen:
    %00 = Pixel nicht gesetzt (durchsichtig)
    %01 = Multicolor_0 ($D025)
    %10 = Multicolor_1 ($D026)
    %11 = Spritefarbe ($D027 – $D02E, wie bei Hi-Res)

    Korrekt wäre:
    %00 = Pixel nicht gesetzt (durchsichtig)
    %01 = Multicolor_0 ($D025)
    %10 = Spritefarbe ($D027 – $D02E, wie bei Hi-Res)
    %11 = Multicolor_1 ($D026)

    Gruß
    Frank


    09.05.2017 – Edit: Kommentar nach „Sprites (ASM)“ vorschoben, ich denke da passt er besser hin.

  5. Hallo Jörn,

    kleine Korrektur im Kommentar der Zeile
    “lda #$CE ;sonst, OpCode für INC in den Akku”

    Hier lädst du den OpCode für DEC in den Akku, nicht für INC.

    Ist ein wirklich ein super Tutorial, großes Lob!!!

    Schöne Grüße
    Alex

  6. Hallo Jörn,

    das hier ist das beste, WIRKLICH DAS BESTE, was ich jemals an deutschsprachigen Tutorials über den C64 gesehen habe. Ich wünschte, ich hätte Deine umfängliche Wissensbasis schon 1986 gehabt. Leider bin ich nie weit über den “Lothar Englisch”-Level hinausgekommen.

    Danke für die Mühe.
    Das hier ist Internet-Gold!!!

    Gruß,

    Tina

    1. Hallo Tina,
      danke fürs Lob.
      Es macht mir einfach Spaß, mich in die Themen einzuarbeiten und für andere Interessierte niederzuschreiben. Außerdem zwingt es mich dazu, mich sehr intensiv mit dem jeweiligen Themenblock zu beschäftigen.

      Gruß,
      Jörn

Schreibe einen Kommentar

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

Protected by WP Anti Spam