IRQ-KURS "Die Hardware ausgetrickst..." (Teil 9) ----------------------------------------
Herzlich Willkommen zum neunten Teil unseres Raster-IRQ-Kurses. In dieser Ausgabe soll es um die trickreiche Pro- grammierung 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 Bild- schirm 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 Rasterin- terrupt 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 Sprite- positionen 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 folgen- des Programmbeispiel verdeutlichen, das Sie auf dieser MD auch unter dem Namen "16SPRITES" 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 unte- ren Bildschirmhäfte, jeweils in einer Reihe. Kommen wir zunächst zur Init- Routine unsres kleinen Beispiels, deren Funktionsprinzip uns mittlerweile be- kannt 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 auftre- ten soll, was in etwa die Mitte des sichtbaren Bildbereichs ist. Dort soll dann die Routine "IRQ1" aufgerufen wer- den, 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 abge- schaltet, und anschließend in eine Schleife gesprungen, die auf die SPACE- Taste wartet, und in dem Fall einen Re- set auslöst. Wichtig sind nun noch die drei Kopierschleifen innerhalb der Ini- tialisierung. 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 Sprite- block 13 mit gesetzten Pixeln, setzen die Spritepointer aller Sprites auf die- sen 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 eigent- lich nichts anderes, als Werte aus den Zeropageadressen von $A0 bis $B3 in ein- zelne VIC-Register zu kopieren. Damit geben wir dem VIC wir ab der Rasterposi- tion $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 Ze- ropageadressen 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 Bildschirmbe- reiche 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, ein- oder au- schalten, sowie X-, bzw. Y-Expandieren. Wir haben also quasi zwei "virtuelle", oder "softwaremäßige" Sprite-VICs er- schaffen, deren Register wie die des normalen VICs beschrieben werden können. Dies können Sie übrigens mit einer Rou- tine ab Adresse $1200 machen. Sie wird im Hauprogramm (sh. INIT-Listing) stän- dig 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ön- nen. Wir haben nun also 16 Sprites auf dem Bildschirm, jedoch mit der Einschrän- kung, daß immer nur jeweils acht im obe- ren 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 Spri- te im oberen Bildbereich nie eine Posi- tion 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, ans- telle 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 Spri- tes recht problemlos, solange alle wei- teren Sprites in der Horizontalen von- einander 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 Bild- schirm darstellbar sein sollen, und zu- dem 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, flexi- bler mit den horizontalen Sprite- Positionen umzugehen. Solch eine Routine werden wir nun realisieren. Sie ist all- gemeinhin 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 Hin- tergrundsterne, 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 Bild- schirm darzustellen, wobei wir uns mö- glichst wenig Sorgen über die Darstel- lung machen wollen. Diese Arbeit soll unsere Routine übernehmen, und automa- tisch die richtige Darstellung wählen. Damit es keine Bereiche gibt, in denen gar keine Sprites dargestellt werden können, weil gerade irgendwelche Pseu- do-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 "vir- tuellen" VIC an, den wir so behandeln, als könne er tatsächlich 16 Sprites dar- stellen. 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 Betriebssy- stemroutinen mehr verwendet werden kön- nen, 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-$8F 16 Bytes, die bestimmen, welche Sprites ein- oder ausgeschaltet sind. Eine 0 in einem dieser Bytes schaltet das entspre- chende Sprite aus. Der Wert 1 schaltet es ein ($80 ist für Sprite0, $8F für Sprite15 zuständig). $90-$9F 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 unter- gebracht.
$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 unterge- bracht, 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 Darstel- lung später benötigt. $F0-$FF Innerhalb dieses Bereichs werden Zeiger auf die Reihenfolge der Sprites ange- legt. 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 X- und Y-Position durch Beschreiben der Register $99, $A9, sowie $B9 setzen, Farbe und Spritepointer in $C9 und $D9 unterbringen, und anschließend das Spri- te durch Schreiben des Wertes 1 in Regi- ster $89 einschalten. Analog wird mit allen anderen Sprites verfahren, wobei die Sprite-Nummer immer der zweiten Zif- fer 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ön- nen 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 pro- blemlos 24, oder gar 32 Sprites auf den Bildschirm bringen können). Rein theore- tisch 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 min- destens 22 Rasterzeilen (die Höhe eines ganzen Sprites plus eine Zeile "Sicher- heitsabstand") unterhalb des ersten vir- tuellen Sprites liegt. Damit diese Be- dingung so oft wie nur möglich erfüllt ist, verwendet man ein echtes VIC-Sprite immer zur Darstellung des n-ten, 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 bei- den Sprites erreicht wird. Dies sollte unsere Routine in einem Bildschirmbe- reich tun, in dem keine Sprites ange- zeigt werden können, also dann, wenn der Rasterstrahl gerade dabei ist, den obe- ren und unteren Bildschirmrand zu zeich- nen (selbst wenn man diesen auch ab- schalten kann). Nach der Sortierung kön- nen nun die Werte der ersten acht vir- tuellen Sprites im VIC eingetragen wer- den, 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. Jedes- mal, 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" ge- laden um mittels "SYS4096" gestartet. Die Initialierung möchte ich Ihnen dies- mal 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 Orga- nisationsarbeit durchführen soll. Hier- bei 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!)