Gleichzeitig müssen wir berücksichtigen, daß alle 21 Rasterzeilen die Y-Position
der Sprites um diesen Betrag hochgezählt
werden muß, damit sie auch untereinander
auf dem Bildschirm erscheinen. Dies ist
glücklicherweise eine nicht allzu schwere Aufgabe, da der VIC uns diesbezüglich
ein wenig entgegenkommt. Ändern wir nämlich die X-Position eines Sprites innerhalb einer Rasterzeile, so hat dies eine
sofortige eine Wirkung auf die Spriteposition: das Sprite erscheint ab dieser
Rasterzeile an der neuen X-Position. Mit
der Y-Position verhält es sich anders:
wird sie noch während das Sprite gezeichnet wird geändert, so hat das keine
direkte Auswirkung auf die restlichen
Spritezeilen. Der VIC zeichnet das Sprite stur zu Ende, bevor er die Y-Position
nocheinmal prüft. Das gibt uns die Möglichkeit, die Y-Position schon im Vorraus zu ändern, nämlich irgendwann innerhalb der 21 Rasterzeilen, die das
Sprite hoch ist. Setzen wir die neue Y-Position nun also genau auf die Rasterzeile nach der letzen Spritezeile, so kümmert sich der VIC darum erst einmal gar nicht. Er zeichnet zunächst sein
aktuelles Sprite zu Ende, und wirft dann
erst einen Blick in das Y-Positions- Register des Sprites. Da er dort dann
eine Position vorfindet, die er noch
nicht überlaufen hat, glaubt er, das
Sprite wäre noch nicht gezeichnet worden, woraufhin er unverzüglich mit dem
Zeichnen wiederanfängt - und schon wäre
dasselbe Sprite zweimal untereinander
auf dem Bildschirm zu sehen! Dadurch
können wir uns in der Rasterroutine mit
der Neupositionierung Zeit lassen, und
selbige über zwei Rasterzeilen verteilt
durchführen ( innerhalb einer Zeile wäre
auch gar nicht genug Zeit dafür) .
Um die Bildschirmränder unsichtbar zu
machen muß sich ein Teil unserer IRQ-Routine um das Abschalten des oberen und
unteren Bildschirmrandes, sowie der Cha- rakterzeilen kümmern. Wie Sie im Programmbeispiel zuvor gesehen haben, ist
das eigentlich eine recht einfache Aufgabe. Da wir zum Abschalten der Ränder
links und rechts jedoch ein hypergenaues
Timing benötigen, wäre es recht aufwendig für die Rasterzeilen $ FA,$28 und
$32 eigene IRQ-Routinen zu schreiben, ohne dabei das Timing für die Sideborderabschaltung damit durcheinander zu
bringen. Aus diesem Grund haben wir uns
einen Trick ausgedacht, mit dem wir beide Aufgaben quasi " in einem Aufwasch" bewältigen: Wie Sie zuvor vielleicht
schon bemerkt haben, hatten alle IRQs
unserer Beispielroutine eins gemeinsam:
Sie erzielten den gewünschten Effekt
durch irgendeine Manipulation des Registers $ D011 . Warum sollten wir also
nicht aus zwei eins machen, und generell
in jeder Rasterzeile einen Wert in dieses Register schreiben? Das hilft uns
zum Einen das Verzögern des Rasterstrahls bis zum Ende der Rasterzeile,
und hat zudem den angenehmen " Nebeneffekt" die horizontalen Bildschirmränder, sowie die Charakterzeilendarstellung
quasi " automatisch" abzuschalten.
Nach all dieser trockenen Theorie möchte
ich Ihnen den Source-Code zu unserer
ESCOS-Routine nun nicht mehr länger vorenthalten. Die Init-Routine werden wir
uns diesmal sparen, da sie nahezu identlisch mit der des letzten Programmbeispiels ist. Wichtig für uns ist, daß Sie
den VIC darauf vorbereitet, einen IRQ in
Rasterzeile 0 auszulösen, bei dessen
Auftreten der Prozessor in die Routine
" SIDEBORD" zu springen hat. Selbige Routine befindet sich an Adresse $1200 und
sieht folgendermaßen aus:
SIDEBORD:
pha ;Prozessorregs. retten txa pha tya pha dec $d019 ;VIC-ICR löschen inc $d012 ;nächste Rasterz. neuer lda #<irq2 ; IRQ-Auslöser, mit Sprung sta $fffe ; auf "IRQ2" cli ;IRQs erlauben ch:nop ;13 NOPs während der der ... ;IRQ irgendwann auftritt nop jmp ch IRQ2: lda #$ff ;Alle Sprites, sowie sta $d015 ; X-Expansion sta $d01d ; einschalten clc lda $00FB ;"SOFTROLL" lesen adc #$02 ;Die Y-Position sta $d001 ; der Sprites sta $d003 ; befindet sich sta $d005 ; 2 Rasterzeilen sta $d007 ; nach RasterIRQ sta $d009 ; und muß demnach sta $d00b ; gesetzt werden. sta $d00d sta $d00f lda $d012 ;den letzen Zyklus cmp $d012 ;korrigieren bne onecycle
ONECYCLE:
pla ;Vom 2. IRQ erzeugte pla ;Rückspr.adr, u. CPU- pla ;Status v.Stapel entf. lda #<sidebord;IRQ-Ptr. wieder auf sta $fffe ; "SIDEBORD" zurück dec $d019 ;VIC-ICR löschen lda $FB ;Index für $D011- lsr ; Tabelle vorbereiten tay
Soweit also keine uns besonders unbekannte Routine. Der hier aufgezeigte
Auszug dient lediglich dem Glätten des
IRQs und sollte Ihnen, wenn auch leicht modifiziert, schon aus anderen Beispielen bekannt sein. Einzig zu erwähnender
Punkt wäre die Adresse $ FB. In dieser
Zeropageadresse steht die Nummer der
Rasterzeile, in der der IRQ aufgerufen
werden soll. Obwohl das bei uns zwar
immer der Wert 0 ist ( womit also immer 0 in diesem Register steht), hat dies einen entscheidenden Vorteil: durch einfaches Hochzählen dieses Registers um 1, nach jedem Rasterdurchlauf, wird der
Raster-IRQ jeweils eine Zeile später
erst auftreten, womit wir einen ganz
simplen Scrolleffekt erzielen. Zu sehen
ist dies auch im Beispiel " ESCOS2", das
absolut identisch zu " ESCOS1" ist, jedoch mit der oben genannten Änderung.
Gleichzeitig hat die Adresse $ FB noch
eine zweite Aufgabe: sie wird zusätzlich
als Index auf eine Liste " mißbraucht", in der die Werte stehen, die in $ D011 eingetragen werden müssen, um oberen und
unteren Bildschirmrand, sowie Charakterzeilen abzuschalten. Da im Prinzip nur drei verschiedene Werte in dieser Tabelle stehen, und zudem die Änderungen der
Werte nicht hundertprozentig genau in
einer speziellen Rasterzeile auftreten
müssen, sondern ruhig auch einmal eine
Rasterzeile früher oder später, haben
wir die Tabelle nur halb solang gemacht
und tragen nur jede zweite Rasterzeile
einen Wert von ihr in $ D011 ein. Dies
hat den zusätzlichen Vorteil, daß sie
nicht länger als 256 Byte wird und somit
keine High-Byte- Adressen berücksichtigt
werden müssen. Aus diesem Grund wird
also auch der Index-Wert, der ins Y-Register geladen wird, durch einen
" LSR"- Befehl halbiert, damit bei einer
Verschiebung automatsich die ersten Tabellenwerte übersprungen und somit auf
den " richtigen" ersten Tabelleneintrag
zugegriffen wird. Wichtig ist nur noch, daß die Tabelle unbedingt innerhalb eines 256- Byte-Blocks steht und an einer
Adresse mit Low-Byte- Wert 0 beginnt.
Durch die Yindizierte Adressierung, mit der wir auf sie zugreifen, könnte es im
anderen Fall zu Timing-Problemen kommen.
Greifen wir nämlich mit dem Befehl " LDA
$11 FF, Y" auf eine Speicherzelle zu, und
enthält in diesem Fall das Y-Register
einen Wert größer 1, so tritt ein Öberlauf bei der Adressierung auf
($11 FF+1=$1200- High-Byte muß hochgezählt werden!) . Ein solcher Befehl
benötigt dann nicht mehr 4 Taktzyklen, sondern einen Taktzyklus mehr, in dem
das High-Byte korrigiert wird! Dadurch
würden wir also unser Timing durcheinanderbringen, weswegen die Tabelle ab
Adresse $1500 abgelegt wurde und mit
Ihren 156 Einträgen keinen solchen Öberlauf erzeugt. Die Tabelle selbst enthält
nun, jeweils blockweise, die Werte $10,$00 und $18, die an den richtigen Rasterpositionen untergebracht wurden, so
daß die Ränder und Charakterzeilen beim
Schreiben des entsprechenden Wertes abgeschaltet werden. Sie können ja einmal
mit Hilfe eines Speichermonitors einen Blick hineinwerfen.
Wir werden uns nun um den Rest unserer
Routine kümmern, in der wir in 294 Rasterzeilen die seitlichen Ränder abschalten und zudem jede 21 . Rasterzeile
die Sprites neu positionieren, so daß
insgesamt 14 Zeilen zu je 8 Sprites auf
dem Bildschirm erscheinen. Dies ist die
Fortsetzung der SIDEBORD-IRQ- Routine:
nop ; Diese Befehlsfolge wird jsr open21 ; insgesamt 14 x aufgerufen
... ; um je 21 Zeilen zu öffnen ... nop ;Sprite Line 14 jsr open21 lda #$00 ;Sprites aus sta $d015
lda #$ c8 ;40- Spalten-Modus zurücksta $ d016 ; setzen lda $ FB ; Zeile aus $ FB für nächsten sta $ d012 ; Raster-IRQ setzen
pla ;Prozessorregs. zurückholen tay ; und IRQ beenden0 pla tax pla rti
Wie Sie sehen besteht der Kern unserer
Routine aus den 14 Mal aufeinander folgenden Befehlen:" NOP" und " JSR OPEN21" .
Der NOP-Befehl dient dabei lediglich dem
Verzögern. Die Unterroutine " OPEN21" ist
nun dafür zuständig, in den folgenden 21 Rasterzeilen ( genau die Höhe eines Sprites) den Rand zu öffnen, sowie die neuen
Y-Spritepositionen zu setzen. Sie sieht
folgendermaßen aus:
;line 00 OPEN21:
nop ; Verzögern bis rechter
nop ; Rand erreicht dec $d016 ;38 Spalten inc $d016 ;40 Spalten lda $1500,y ;$D011-Wert aus Tabelle sta $d011 ; lesen und eintragen iny ;Tabellen-Index+1 jsr cycles+5;24 Zyklen verzögern ;line 01 dec $d016 ;38 Spalten inc $d016 ;40 Spalten jsr cycles ;34 Zyklen verzögern
. . . ; dito für 2-15
Wie Sie sehen können, verzögern wir
zunächst mit zwei NOPs bis zum Ende des
sichtbaren Bildschirmfensters, wo wir
durch Herunterzählen von Register $ D016 die 38- Spalten-Darstellung einschalten, und gleich darauf durch Hochzählen desselben Registers wieder auf 40 Zeilen
zurückgehen. Damit wäre der Rand geöffnet worden. Als nächstes wird der Wert
für $ D011 aus der besagten Tabelle aus- gelesen und in dieses Register eingetragen, sowie der Index-Zähler im Y-Register für die Öbernächste Zeile um 1 erhöht. Danach wird die Routine " CYCLES" aufgerufen, jedoch nicht an ihrer eigentlichen Adresse, sondern 5 Bytes weiter. Diese Routine besteht aus insgesamt
11 NOPs, die lediglich die Zeit verzögern sollen, bis die nächste Rand-Abschaltung fällig wird. Da ein NOP 2 Taktzyklen verbraucht, verzögert sie 22 Taktyklen. Hier muß man zusätzlich noch
die Zeit hinzurechnen, die für den JSRund RTS-Befehl draufgehen. Beide verbrauchen je 6 Taktzyklen, womit die
" CYCLES"- Routine insgesamt 34 Zyklen in
Anspruch nimmt. Durch den Offset von 5 Bytes, den wir beim ersten Einsprung in
die Routine machen," übergehen" wir einfach die ersten 5 NOPs, womit nur 24 Zyklen verbraucht werden. Nach Rückkehr
aus dieser Routine befindet sich der
Rasterstrahl nun genau an der Position, an der wir den Rand für die neue Raster- zeile öffnen müssen, was sogleich auch
durch die INC-DEC- Befehlsfolge getan
wird. Anschließend wird wieder verzögert, wobei wir diesmal die " CYCLES"- Routine komplett durchlaufen, da in dieser Zeile kein neuer Wert in $ D011 eingetragen wird, und wir somit 10 Zyklen
mehr zu verzögern haben!
Dieser Programm-Auszug wiederholt sich
nun insgesamt noch 7 Mal, bis wir in die
16 . Spritezeile gelangt sind. Hier nun
beginnen wir mit dem Neupositionieren
der Sprites:
;line 16 dec $d016 ;Altbekannte Methode inc $d016 ; um Zeile 16 zu öffnen lda $1500,y ; u. $D011 neu zu setzen sta $d011 iny jsr cycles+5 ;line 17 dec $d016 ;38 Spalten inc $d016 ;40 Spalten clc ;Neue Y-Pos. für nächste lda $d001 ; Spr.-Reihe= Alte Y-Pos. adc #$15 ; plus 21 sta $d001 ;Und für Sprites 0-3 sta $d003 ; eintragen sta $d005 sta $d007 nop ;Nur 10 Zyklen verzögern nop nop nop nop ;line 18 dec $d016 ;Altbekannte Methode zum inc $d016 ; öffnen der 17. Zeile... lda $1500,y sta $d011 iny jsr cycles+5 ;line 19 dec $d016 ;38 Spalten inc $d016 ;40 Spalten clc ;Neue Y-Pos. für nächste lda $d009 ; Sprite-Reihe berechnen adc #$15 sta $d009 ;und für Sprites 4-7 sta $d00b ; setzen sta $d00d sta $d00f nop ;Nur 10 Zyklen verzögern nop nop nop nop ;line 20 dec $d016 ;38 Spalten inc $d016 ;40 Spalten lda $1500,y;Neuen Wert für $D011 aus sta $d011 ; Tab. holen u. eintragen iny ;Tab-Index+1 nop ;6 Zyklen verzögern nop nop rts ;Und fertig!
Wie Sie sehen, benutzen wir für die geraden Zeilennummern die alte Befehlsfolge zum Üffnen des Randes und Setzen des
neuen $ D011- Wertes. In den Spritezeilen
17 und 19 jedoch wird nach dem Üffnen
des Randes die Y-Position um 21 erhöht
und in je vier Y-Sprite- Register eingetragen, womit also die Y-Positionen der
nächsten Spritereihe festgelegt wären.
In Spritezeile 20 wird nun ein letztes
Mal der $ D011- Wert neu gesetzt und nach
einer kurzen Verzögerung wieder zum
Hauptprogramm zurückgekehrt, von wo die
Routine für alle 14 Spritereihen nochmal
aufgerufen wird, und womit unser ESCOS-Effekt fertig programmiert ist!
4) ABSCHLIESSENDE HINWEISE Wie schon erwähnt, ist der Prozessor zur
Anzeige dieser insgesamt 114 Sprites, sowie dem Abschalten der Bildschirmränder, fast den gesamten Rasterdurchlauf lang voll beschäftigt. Viel Rechenzeit
bleibt uns nicht mehr übrig, um weitere
Effekte zu programmieren. Dennoch sind
am Ende des Rasterdurchlaufs noch 16 Rasterzeilen zur freien Verfügung, und
man muß noch die Zeit hinzurechnen, in
der der Rasterstrahl vom unteren Bildschirmrand wieder zum oberen Bildschirmrand zu wandern hat, in der wir ebenfalls noch mit dem Prozessor arbeiten
können. Diese Zeit wurde im Beispiel
" ESCOS2" benutzt, um die Spritereihen
zusätzlich noch zu scrollen. Jedoch ist
auch noch ein Moviescroller problemlos
machbar. Dieses Thema werden wir jedoch
erst nächsten Monat ansprechen, und uns
auch weiterhin mit den Sprites befassen.
Sie werden dabei eine Möglichkeit kennenlernen, wie man diese kleinen Grafikwinzlinge hardwaremässig dehnen und verbiegen kann. Wie immer gibt es dabei
einen Trick, den VIC zu überreden das
Unmögliche möglich zu machen. . .
( ih/ ub)