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 schwe- re Aufgabe, da der VIC uns diesbezüglich ein wenig entgegenkommt. Ändern wir näm- lich die X-Position eines Sprites inner- halb einer Rasterzeile, so hat dies eine sofortige eine Wirkung auf die Spritepo- sition: 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 ge- zeichnet wird geändert, so hat das keine direkte Auswirkung auf die restlichen Spritezeilen. Der VIC zeichnet das Spri- te stur zu Ende, bevor er die Y-Position nocheinmal prüft. Das gibt uns die Mö- glichkeit, die Y-Position schon im Vor- raus zu ändern, nämlich irgendwann in- nerhalb der 21 Rasterzeilen, die das Sprite hoch ist. Setzen wir die neue Y-Position nun also genau auf die Ra- sterzeile nach der letzen Spritezeile, so kümmert sich der VIC darum erst ein- mal 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 wor- den, 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 Pro- grammbeispiel zuvor gesehen haben, ist das eigentlich eine recht einfache Auf- gabe. Da wir zum Abschalten der Ränder links und rechts jedoch ein hypergenaues Timing benötigen, wäre es recht aufwen- dig für die Rasterzeilen $FA, $28 und $32 eigene IRQ-Routinen zu schreiben, ohne dabei das Timing für die Sidebor- derabschaltung damit durcheinander zu bringen. Aus diesem Grund haben wir uns einen Trick ausgedacht, mit dem wir bei- de 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 Regi- sters $D011. Warum sollten wir also nicht aus zwei eins machen, und generell in jeder Rasterzeile einen Wert in die- ses Register schreiben? Das hilft uns zum Einen das Verzögern des Raster- strahls bis zum Ende der Rasterzeile, und hat zudem den angenehmen "Nebenef- fekt" 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 vo- renthalten. Die Init-Routine werden wir uns diesmal sparen, da sie nahezu ident- lisch mit der des letzten Programmbei- spiels 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 Rou- tine 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 unbe- kannte Routine. Der hier aufgezeigte Auszug dient lediglich dem Glätten des IRQs und sollte Ihnen, wenn auch leicht modifiziert, schon aus anderen Beispie- len 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 ei- nen entscheidenden Vorteil: durch einfa- ches 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, je- doch 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 Charakter- zeilen abzuschalten. Da im Prinzip nur drei verschiedene Werte in dieser Tabel- le 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 Ta- bellenwerte übersprungen und somit auf den "richtigen" ersten Tabelleneintrag zugegriffen wird. Wichtig ist nur noch, daß die Tabelle unbedingt innerhalb ei- nes 256-Byte-Blocks steht und an einer Adresse mit Low-Byte-Wert 0 beginnt. Durch die Y-indizierte 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 $11FF,Y" auf eine Speicherzelle zu, und enthält in diesem Fall das Y-Register einen Wert größer 1, so tritt ein Öber- lauf bei der Adressierung auf ($11FF+1=$1200 - High-Byte muß hoch- gezä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 durcheinan- derbringen, weswegen die Tabelle ab Adresse $1500 abgelegt wurde und mit Ihren 156 Einträgen keinen solchen Öber- lauf erzeugt. Die Tabelle selbst enthält nun, jeweils blockweise, die Werte $10, $00 und $18, die an den richtigen Ra- sterpositionen untergebracht wurden, so daß die Ränder und Charakterzeilen beim Schreiben des entsprechenden Wertes ab- geschaltet 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 Ra- sterzeilen die seitlichen Ränder ab- schalten 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 14x 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ück- sta $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 fol- genden 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 Spri- tes) 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 des- selben Registers wieder auf 40 Zeilen zurückgehen. Damit wäre der Rand geöff- net worden. Als nächstes wird der Wert für $D011 aus der besagten Tabelle aus- gelesen und in dieses Register eingetra- gen, 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 ei- gentlichen Adresse, sondern 5 Bytes wei- ter. 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 JSR- und RTS-Befehl draufgehen. Beide ver- brauchen 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 ein- fach 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 die- ser Zeile kein neuer Wert in $D011 ein- getragen 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 ge- raden Zeilennummern die alte Befehlsfol- ge 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 einge- tragen, 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än- der, 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 Bild- schirmrand wieder zum oberen Bildschirm- rand zu wandern hat, in der wir eben- falls 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 ken- nenlernen, wie man diese kleinen Grafik- winzlinge hardwaremässig dehnen und ver- biegen kann. Wie immer gibt es dabei einen Trick, den VIC zu überreden das Unmögliche möglich zu machen... (ih/ub)