IRQ-KURS "Die Hardware ausgetrickst..." (Teil 9)
Herzlich Willkommen zum neunten Teil
unseres Raster-IRQ- Kurses. In dieser
Ausgabe soll es um die trickreiche Programmierung von Sprites gehen, die wir
bisher ja nur am Rande angeschnitten
hatten. Durch Raster-IRQs ist es nämlich
möglich, mehr als acht Sprites auf den
Bildschirm zu zaubern! Wir wollen hierzu
eine sogenannte Sprite-Multiplexer- Routine kennenlernen, mit der wir bis zu
16 Sprites ( fast) frei über den Bildschirm bewegen können!
1) DAS FUNKTIONSPRINZIP Zunächst wollen wir klären, wie man im
Allgemeinen die Anzahl der Sprites
erhöht. Das Prinzip ist höchst simpel:
dadurch nämlich, daß man einen Rasterinterrupt z. B. in der Mitte des sichtbaren Bildschirms auslöst, hat man die Möglichkeit die Sprite-Register des VIC
neu zu beschreiben, um z. B. neue Spritepositionen und Spritepointer zu setzen.
Bevor der Rasterstrahl nun wieder die
obere Bildschirmhälfte erreicht, können
dann wieder die Daten der ersten acht
Sprites in den VIC geschrieben werden.
Auf diese Weise kann man in der oberen, sowie in der unteren Bildschirmhälfte je
acht Sprites darstellen. Wie einfach
dieses Prinzip funktioniert soll folgendes Programmbeispiel verdeutlichen, das
Sie auf dieser MD auch unter dem Namen
"16 SPRITES" finden, und wie immer mit
",8,1" laden und durch ein " SYS4096" starten. Nach dem Start werden Sie 16( !) Sprites auf dem Bildschirm sehen, acht in der oberen und acht in der unteren Bildschirmhäfte, jeweils in einer
Reihe. Kommen wir zunächst zur Init-Routine unsres kleinen Beispiels, deren
Funktionsprinzip uns mittlerweile bekannt sein sollte:
Init:sei ;IRQs sperren lda #6 ;Farbe auf 'blau' sta $d020 sta $d021 lda #$7f ;Alle Interruptquellen sta $dc0d ; von IRQ- und NMI-CIA sta $dd0d ; sperren ggf. aufge- bit $dc0d ; tretene IRQs frei- bit $dd0d ; geben lda #$94 ;Rasterzeile $94 als sta $d012 ; IRQ-Auslöser lda $d011 ; festlegen and #$7f sta $d011 lda #$01 ;Rasterstrahl ist sta $d01a ; Interruptquelle lda #<irq1 ;Vektor auf erste IRQ- sta $fffe ; Routine verbiegen lda #>irq1 sta $ffff ldy #19 ;Pseudo-Sprite-Register lo1: lda vic1,y ; in Zeropage von sta $80,y ; von $80 bis $C0 lda vic2,y ; kopieren sta $a0,y dey bpl lo1 ldy #62 ;Spriteblock Nr. 13 lda #$ff ; mit $FF auffüllen lo2: sta 832,y dey bpl lo2 ldy #7 ;Spritepointer aller lo3: lda #13 ; Sprites auf Block 13, sta 2040,y ; sowie Farbe aller lda #1 ; Sprites auf 'weiß' sta $d027,y ; setzen dey bpl lo3 lda #$35 ;ROM abschalten sta $01 cli ;IRQs freigeben lo4: jsr $1200 ;Bewegunsroutine aufr. lda $dc01 ;SPACE-Taste abfragen cmp #$ef bne lo4 lda #$37 ;Wenn SPACE, dann ROM
sta $01 ; wieder einschalten jmp $ fce2 ; und RESET auslösen.
Hier schalten wir nun also wie gewohnt
alle Interruptquellen der CIA ab, und
aktivieren den Raster-IRQ, wobei dieser
das erste Mal in Rasterzeile $94 auftreten soll, was in etwa die Mitte des
sichtbaren Bildbereichs ist. Dort soll
dann die Routine " IRQ1" aufgerufen werden, deren Adresse in den Hard-IRQ- Vektor bei $ FFFE/$ FFFF geschrieben wird.
Damit der Prozessor beim Auftreten des
IRQs auch tatsächlich unsere Routine
anspringt, wird zuvor noch das ROM abgeschaltet, und anschließend in eine
Schleife gesprungen, die auf die SPACE-Taste wartet, und in dem Fall einen Reset auslöst. Wichtig sind nun noch die
drei Kopierschleifen innerhalb der Initialisierung. Die erste davon (" LO1") kopiert nun zunächst eine Tabelle mit
Sprite-Register- Werten, die am Ende des
Programms stehen, in die Zeropage ab Adresse $80 . Was es damit auf sich hat, sehen wir später. Die zweite und dritte
Schleife füllen dann noch den Spriteblock 13 mit gesetzten Pixeln, setzen
die Spritepointer aller Sprites auf diesen Block, sowie die Farbe Weiß als
Spritefarbe.
Sehen wir nun, was die Interruptroutine
" IRQ1" macht:
IRQ1:pha ;Prozessorregister txa ;retten pha tya pha lda #0 ;Farbe auf 'schwarz' sta $d020 sta $d021
lda #$ fc ; Rasterzeile $ FC soll sta $ d012 ; nächster IRQ-Auslöser lda #< irq2 ; sein, wobei Routine sta $ fffe ;" IRQ2" angesprungen
lda #>irq2 ; werden soll sta $ffff dec $d019 ;VIC-ICR freigeben lda $a0 ;X-Pos. Sprite 0 sta $d000 ; setzen lda $a1 ;Y-Pos. Sprite 0 sta $d001 ; setzen lda $a2 ;Ebenfalls für Sprite 1 sta $d002 lda $a3 sta $d003 lda $a4 ;Sprite 2 sta $d004 lda $a5 sta $d005 lda $a6 ;Sprite 3 sta $d006 lda $a7 sta $d007 lda $a8 ;Sprite 4 sta $d008 lda $a9 sta $d009 lda $aa ;Sprite 5 sta $d00a lda $ab sta $d00b lda $ac ;Sprite 6 sta $d00c lda $ad sta $d00d lda $ae ;Sprite 7 sta $d00e lda $af sta $d00f lda $b0 ;Hi-Bits der X-Pos. sta $d010 ; aller Sprites setzen lda $b1 ;Sprite-Enable setzen sta $d015 ; (welche sind an/aus) lda $b2 ;X-Expansion setzen sta $d017 lda $b3 ;Y-Expansion setzen sta $d01d
lda #6 ; Farbe wieder ' blau' sta $ d020 sta $ d021
pla ;Prozessor-Regs. tya ; zurückholen pla txa pla rti ;Und IRQ beenden...
Wie Sie sehen, tut die Routine eigentlich nichts anderes, als Werte aus den
Zeropageadressen von $ A0 bis $ B3 in einzelne VIC-Register zu kopieren. Damit
geben wir dem VIC wir ab der Rasterposition $94 also einfach neue Spritewerte.
Gleiches macht nun auch die Routine
" IRQ2", die an Rasterzeile $ FC ausgelöst
wird, nur daß sie die Werte aus den Zeropageadressen von $80 bis $93 in den
VIC-Kopiert. In den beiden genannten
Zeropage-Bereichen haben wir also quasi
eine Kopie der wichtigsten Sprite-Register für jeweils zwei Bildschirmbereiche untergebracht, deren Inhalte je- weils an Rasterzeile $94 und $ FC in den
VIC übertragen werden. Verändern wir
diese Werte nun innerhalb der Zeropage, so können wir jedes der 16 sichtbaren
Sprites einzeln bewegen, einoder auschalten, sowie X-, bzw. Y-Expandieren.
Wir haben also quasi zwei " virtuelle", oder " softwaremäßige" Sprite-VICs erschaffen, deren Register wie die des
normalen VICs beschrieben werden können.
Dies können Sie übrigens mit einer Routine ab Adresse $1200 machen. Sie wird
im Hauprogramm ( sh. INIT-Listing) ständig aufgerufen, womit ich Ihnen die Möglichkeit der Spritebewegung offenhalten
wollte. Im Beispiel steht an dieser
Adresse nur ein " RTS", das Sie jedoch
mit einer eigenen Routine ersetzen können.
Wir haben nun also 16 Sprites auf dem
Bildschirm, jedoch mit der Einschränkung, daß immer nur jeweils acht im oberen und unteren Bildschirmbereich er- scheinen dürfen. Setzen wir die Y-Position eines Sprites aus dem unteren
Bereich auf eine Zahl kleiner als $94, so wird es nicht mehr sichtbar sein, da
diese Position ja erst dann in den VIC
gelangt, wenn der Rasterstrahl schon an
ihr vorbei ist. Umgekehrt darf ein Sprite im oberen Bildbereich nie eine Position größer als $94 haben. Ausserdem ist
noch ein weiterer Nachteil in Kauf zu
nehmen: das Umkopieren der Register ist
zwar schnell, da wir absichtlich mit
Zeropageadressen arbeiten, auf die der
Zugriff schneller ist, als auf Low-/ High-Byte- Adressen (2 Taktzyklen, anstelle von 3), jedoch dauert es immer
noch knappe 4 Rasterzeilen, in denen gar
keine Sprites dargestellt werden können, da es dort zu Problemen kommen kann, wenn der VIC teilweise schon die Werte
der oberen und der unteren Sprites
enthält.
2) DIE OPTIMIEREUNG Wie Sie in obigem Beispiel sahen, ist
die Programmierung von mehr als 8 Sprites recht problemlos, solange alle weiteren Sprites in der Horizontalen voneinander getrennt dargestellt werden
können. Was nun aber, wenn Sie z. B. ein
Action-Spiel programmieren möchten, in
dem mehr als acht Sprites auf dem Bildschirm darstellbar sein sollen, und zudem auch noch möglichst kreuz und quer
beweglich sein müssen? Für diesen Fall
brauchen wir eine etwas intelligentere
Routine, die es uns ermöglicht, flexibler mit den horizontalen Sprite-Positionen umzugehen. Solch eine Routine
werden wir nun realisieren. Sie ist allgemeinhin unter dem Namen " Sprite-Multiplexer" bekannt. Wer sich nichts
darunter Vorstellen kann, der sollte
sich auf dieser MD einmal das Double-Density- Demo anschauen, in dem die Hintergrundsterne, sowie die Meteore, die
über den Bildschirm huschen auf diese Art und Weise dargestellt werden.
Kommen wir zunächst zum Grundprinzip der
Multiplex-Routine. Mit ihr soll es uns
möglich sein,16 Sprites auf dem Bildschirm darzustellen, wobei wir uns möglichst wenig Sorgen über die Darstellung machen wollen. Diese Arbeit soll
unsere Routine übernehmen, und automatisch die richtige Darstellung wählen.
Damit es keine Bereiche gibt, in denen
gar keine Sprites dargestellt werden
können, weil gerade irgendwelche Pseudo- VIC-Daten kopiert werden, sollte Sie
zusätzlich auch noch möglichst schnell
sein, bzw. den Zeitpunkt der anfallenden
Werteänderungen im VIC sorgfältig
auswählen können.
Um nun all diesen Anforderungen zu genügen, legen wir uns wieder einen " virtuellen" VIC an, den wir so behandeln, als könne er tatsächlich 16 Sprites darstellen. Seine Register sollen wieder in
der Zeropage zu finden sein, damit die Zugriffe darauf schneller ausgeführt
werden können. Hierzu belegen wir die
obere Hälfte der Zeropage mit den benötigten Registerfunktionen. Beachten Sie
bitte, daß in dem Fall keine Betriebssystemroutinen mehr verwendet werden können, da diese nämlich ihre Parameter in
der Zeropage zwischenspeichern und somit
unseren VIC verändern würden! Hier nun
zunächst eine Registerbelegung unseres
Pseudo-VICs:
$80-$8 F 16 Bytes, die bestimmen, welche Sprites
einoder ausgeschaltet sind. Eine 0 in
einem dieser Bytes schaltet das entsprechende Sprite aus. Der Wert 1 schaltet
es ein ($80 ist für Sprite0,$8 F für
Sprite15 zuständig) .
$90-$9 F Diese 16 Bytes halten das Low-Byte der
X-Koordinaten der 16 Sprites.
$ A0-$ AF In diesen 16 Bytes sind die High-Bytes
der X-Koordinaten der 16 Sprites untergebracht.
$B0-$BF Hier sind nacheinander alle Y- Koordinaten der 16 Sprites zu finden.
$ C0-$ CF Diese Register legen die Farben der 16 Sprites fest.
$ D0-$ DF Hier werden die Spritepointer untergebracht, die angeben, welcher Grafikblock
durch ein Sprite dargestellt wird.
$ E0-$ EF Dies ist ein Puffer für die Y-Positionen
der 16 Sprites. Er wird für die Darstellung später benötigt.
$ F0-$ FF
Innerhalb dieses Bereichs werden Zeiger
auf die Reihenfolge der Sprites angelegt. Mehr dazu später.
Die hier angegebenen Register können nun
von uns genauso verwendet werden, als
könne der VIC tatsächlich 16 Sprites
darstellen. Möchten wir also z. B. Sprite
Nr.9 benutzen, so müssen wir zunächst Xund Y-Position durch Beschreiben der
Register $99,$ A9, sowie $ B9 setzen, Farbe und Spritepointer in $ C9 und $ D9 unterbringen, und anschließend das Sprite durch Schreiben des Wertes 1 in Register $89 einschalten. Analog wird mit
allen anderen Sprites verfahren, wobei
die Sprite-Nummer immer der zweiten Ziffer des passenden Registers entspricht.
Wie muß unsere Multiplex-Routine nun
vorgehen, um dem VIC tatsächlich 16 unabhängige Sprites zu entlocken? Man
greift hier, wie bei allen Raster-IRQ- Programmen, zu einem Trick: Wie wir bis- her gesehen hatten, ist die einzige
hardwaremäßige Beschränkung, die uns
daran hindert, mehr als acht Sprites
darzustellen, die X-Koordinate. Es können also immer maximal acht Sprites mit
derselben Y-Koordinate nebeneinander
stehen. Daran können wir auch mit den
besten Rastertricks nichts ändern. In
der Horizontalen, können wir aber sehr
wohl mehr als acht Sprites darstellen, und das eigentlich beliebig oft ( das
erste Beispielprogramm hätte auch problemlos 24, oder gar 32 Sprites auf den
Bildschirm bringen können) . Rein theoretisch kann ja ein Sprite, das weiter
oben am Bildschirm schon benutzt wurde, weiter unten ein weiteres Mal verwendet
werden. Einzige Bedingung hierzu ist, daß das zweite, virtuelle, Sprite mindestens 22 Rasterzeilen ( die Höhe eines
ganzen Sprites plus eine Zeile " Sicherheitsabstand") unterhalb des ersten virtuellen Sprites liegt. Damit diese Bedingung so oft wie nur möglich erfüllt ist, verwendet man ein echtes VIC-Sprite
immer zur Darstellung des nten, sowie
des < n+8>- ten virtuellen Sprites. Das
echte Sprite0 stellt also das virtuelle
Sprite0, sowie das virtuelle Sprite8 dar. Da die Y-Koordinaten beider Sprites
jedoch beliebig sein können, müssen wir
zuvor eine interne Sortierung der Y-Koordinaten vornehmen, so daß der
größtmögliche Abstand zwischen den beiden Sprites erreicht wird. Dies sollte
unsere Routine in einem Bildschirmbereich tun, in dem keine Sprites angezeigt werden können, also dann, wenn der
Rasterstrahl gerade dabei ist, den oberen und unteren Bildschirmrand zu zeichnen ( selbst wenn man diesen auch abschalten kann) . Nach der Sortierung können nun die Werte der ersten acht virtuellen Sprites im VIC eingetragen werden, wobei gleichzeitig ermittelt wird, in welcher Rasterzeile Sprite0 zu Ende
gezeichnet ist. Für diese Rasterzeile
wird ein Raster-Interrupt festgelegt,
der die Werte für Sprite8 in den VIC
eintragen soll, und zwar in die Register
des " echten" Sprite0 . Ebenso wird mit
den Sprites von 9-15 verfahren. Jedesmal, wenn das korrespondierende Sprite
( n-8) zu Ende gezeichnet wurde, muß ein
Rasterinterrupt auftreten, der die Werte
des neuen Sprites schreibt.
Kommen wir nun jedoch zu der Routine
selbst. Sie finden Sie übrigens auch in
den beiden Programmbeispielen " MULTI-PLEX1" und " MULTIPLEX2" auf dieser MD.
Beide wierden wie üblich mit ",8,1" geladen um mittels " SYS4096" gestartet.
Die Initialierung möchte ich Ihnen diesmal ersparen, da Sie fast identisch mit
der obigen ist. Wichtig ist, daß durch
sie zunächst ein Rasterinterrupt an der
Rasterposition $ F8 ausgelöst wird, an
der unsere Multiplex-Routine ihre Organisationsarbeit durchführen soll. Hierbei wird in die folgende IRQ-Routine
verzweigt, die in den Programmbeispielen
an Adresse $1100 zu finden ist:
BORD:pha ;Prozessor-Regs. txa ; retten pha tya pha lda #$e0 ;Neuen Raster-IRQ sta $d012 ; für Zeile $E0 lda #$00 ; und Routine "BORD" sta $fffe ; festlegen lda #$11 sta $ffff dec $d019 ;VIC-IRQs freigeben jsr PLEX ;Multiplexen jsr SETSPR ;Sprites setzen pla ;Prozessorregs. wieder tay ; zurückholen und pla ; IRQ beenden tax pla rti
( Bitte wählen Sie nun den 2 . Teil des IRQ-Kurses aus dem Textmenu!)