5. Grafik

 

Bis jetzt können Sie nur Texte mit PRINT auf dem Bildschirm ausgeben, oder aber auch Zeichen mit POKE in den Bildschirmspeicher schreiben. Klar: PRINT ist sehr flexibel, aber was es eben von Haus aus nicht kann, das ist, eine andere Zeichentabelle zu benutzen, als die, die im Character-ROM steht. Das Character-ROM beinhaltet die Muster der einzelnen Zeichen als Bits, und diese Bits werden natürlich auch im Speicher abgelegt, damit der Grafikprozessor (oft einfach als VIC abgekürzt) diese Bits benutzen kann, um die Zeichen anzuzeigen. Ein Bit ist erst einmal nichts Wildes, es ist im Endeffekt nur eine Speichereinheit, die eine einzige Ziffer aufnehmen kann. Allerdings darf diese Ziffer nur 0 oder 1 sein, schon für die Zahl 2 benötigt man eine neue Stelle. Man nennt diese Darstellung binäre Darstellung, weil es eben nur die Ziffern 0 und 1 gibt.

 

5.1 selbst definierte Zeichen

 

Für die Darstellung der Zeichen werden immer 8 Bits zu einer Zeile zusammengefasst, 8 Bits nennt man ein Byte. In diesem Byte entspricht im Standardmodus ein 0-Bit einem Pixel mit der Hintergrundfarbe, und ein gesetztes Bit (1-Bit) einem Pixel mit der gerade aktiven Zeichenfarbe (wo sich die Farben befinden wissen Sie bereits: In den Adressen 55296-56295). Da ein Zeichen 8 Bildschirmzeilen hoch ist, belegt dieses 8 Bytes Speicher. Für 256 Zeichen (PET-Code 0-255) und die 2 Zeichentabellen, die es nun mal gibt, werden vom VIC 4096 Bytes in den Adressen 53248-57343 benutzt. Das Zeichen für den Cursor (Quadrat) hat hier den Zeichenwert 160, beginnt also an der Adresse 53248+(160*8)=54528 und geht bis zur Adresse 54535. Für das Cursor-Quadrat wird 8-mal der Wert 255 in den Adressen 54528-54535 gespeichert. Die Zahl 255 kommt hier wie folgt zustande:

 

Bit 7

Bit 6

Bit 5

Bit 4

Bit 3

Bit 2

Bit 1

Bit 0

gesetzt

gesetzt

gesetzt

gesetzt

gesetzt

gesetzt

gesetzt

gesetzt

Summe=128+64+32+16+8+4+2+1=255 (8 Zeilen mit gesetzten Bits=8*Wert 255)

 

Natürlich können auch andere Bitmuster auftreten, z.B. für das große A. Wenn Sie nun eine Zeit lang programmieren, dann werden Ihnen die Standardzeichen irgendwann nicht mehr ausreichen. Vielleicht wollen Sie nun eigene Zeichen entwerfen. An dieser Stelle stehen Sie aber vor einem Problem, denn die Zeichen stehen im ROM, dazu noch an Adressen, die sich z.B. mit dem Joystickport, aber auch mit den VIC-Registern überschneiden, und die deswegen auch ausgeblendet sind. Was Sie nun tun müssen, ist folgendes:

 

·       Deaktivieren der Geräte-Interrupts (die Adressen der Geräte-Register z.B. des VIC oder der Tastatur überschneiden sich teilweise mit den Adressen des Character-ROMs)

·       Einblenden des Character-ROMs in das RAM, damit Sie darauf zugreifen können

·       Kopieren des Character-ROMs in einen RAM-Bereich, den Sie bearbeiten können

·       Ausblenden des Character-ROMs (damit Ihre Geräte wieder korrekt funktionieren)

·       Reaktivieren der Geräte-Interrupts (sonst können Sie BASIC nicht mehr verwenden)

·       Umbiegen des Zeigers für die Zeichentabelle in den RAM-Bereich, den Sie nun umschreiben wollen

 

All dies ist nicht trivial und erfordert einiges an Spezialwissen. Das Erste, was Sie an dieser Stelle erfahren sollten, ist, dass auch der VIC seine Register in den Speicher einblendet, nämlich an die Adresse 53248. Diese Konfigurationsregister enthalten Steuerinformationen in Form von Bits und Bytes, die der VIC dazu benutzt, den Inhalt des Bildschirms darzustellen. Was Sie nun davon haben, ist Folgendes: Sie können mit PEEK und POKE das Verhalten des Grafikchips steuern, und eine andere Startdresse für die Zeichentabelle auswählen. Natürlich müssen Sie zuvor das Character-ROM an die entsprechende Stelle kopieren. Das folgende BASIC-Programm tut genau dies:

 

09-CHARCOPY 1

10 POKE 56334,0

20 POKE 1,51

30 FOR I=0 TO 2048: POKE 10240+I,PEEK(53248+I): NEXT I

40 POKE 1,55

50 POKE 56334,1

60 POKE 53272,26

 

In Zeile 10 wird auf den Tastaturcontroller über den Baustein CIA1 zugegriffen (was eine Abkürzung für „Complex Interface Adapter“ ist und mit dem amerikanischen Geheimdienst nichts zu tun hat). Auf diese Weise wird die Tastatur ausgeschaltet. In Zeile 20 muss danach auch die Interrupt-Bedienung des 6510-Prozessors abgeschaltet werden, denn auch hier überschneiden sich die Geräteadressen mit denen des Character-ROMs. Ferner wird in Zeile 20 auch das Character-ROM eingeblendet, denn in Adresse 1 stehen auch einige Bits zur Speichersteuerung des C64 (hier werden Sie besonders im Assembler-Kurs noch viel mehr zu erfahren). In Zeile 30 wird dann das Character-ROM an die Adressen 10240-12287 kopiert (also nur die Tabelle mit den Grafikzeichen), und in den Zeilen 40-50 werden die Geräte wieder in den normalen Zustand versetzt, und die normalen VIC-Register wieder eingeblendet. In Zeile 60 wird die Kopie der Zeichentabelle aus dem ROM verwendet, die nun im RAM steht.

Wenn Sie nun RUN eingeben, dann geschieht erst einmal eine Zeit lang nichts. Auch bei Erscheinen von READY sehen Sie noch nichts. Dies liegt einfach daran, dass Sie noch nichts verändert haben - Sie benutzen ja eine exakte Kopie des ROM-Zeichensatzes! Geben Sie nun die folgende Zeile ein:

 

POKE 10240,255

 

Nun hat sich doch etwas verändert, nämlich der Klammeraffe: Dieser wird nun mit einem Strich überschrieben. Noch nicht sehr begeistert? Ist ja auch noch nicht so viel, was hier passiert. Erstens ist Ihr Programm sehr langsam (Sie müssen lange warten, bis das Character-ROM kopiert worden ist) und zweitens haben Sie, wenn Sie Ihre Zeichentabelle an die Adresse 10240 legen, nur noch 8192 Bytes für Ihr BASIC-Programm zur Verfügung (das sind maximal 32 Blocks auf der Diskette). Die Lösung ist hier das nächste Listing, das die RAM-Zeichentabelle an die Adresse 14336 legt, und außerdem die langsame BASIC-Kopierschleife als Maschinenprogramm realisiert:

 

10-CHARCOPY 2

10 FOR I=49152 TO 49234: READ A:POKE I,A:NEXT I

20 SYS 49152:REM ZEICHEN NACH ADRESSE 14336 KOPIEREN

30 FOR I=0 TO 7:READ A:POKE 14336+I,A:NEXT I: REM SMILY

40 POKE 53272,30:REM ZEICHEN AB 14336

50 PRINT"[SHIFT+CLR/HOME]@ @ @ ALWAYS KEEP SMILING @ @ @"

60 END

10000 REM *** ROM-KOPIERROUTINE ***

10010 DATA 173,14,220,41,254,141,14,220,165,1,41,251,133,1,169,0,133,248

10020 DATA 169,208,133,249,169,0,133,250,169,56,133,251,160,0,177,248,145

10030 DATA 250,24,165,248,105,1,133,248,165,249,105,0,133,249,24,165,250

10040 DATA 105,1,133,250,165,251,105,0,133

10050 DATA 251,165,249,201,216,208,220,165

10060 DATA 1,9,4,133,1,173,14,220,9,1,141,14,220,96

10070 REM *** SMILY ***

10080 DATA 60,66,165,129,165,153,66,60

 

5.1.2 Hilfsmittel für die Zeichenerstellung

 

Sie können nun Zeichen, die Sie z.B. für Ihre Spiele brauchen, per Hand erstellen, und die folgende Tabelle hierfür benutzen:

 

128

64

32

16

8

4

2

1

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

Setzen Sie nun in jedes Kästchen, in dem ein Pixel erscheinen soll, ein Kreuz (oder was immer Sie wollen) und addieren zeilenweise jeweils die Zahlen für die entsprechenden Bit-Werte, in dem ein Kreuz steht. Die auf diese Weise berechneten Werte können Sie später in DATA-Zeilen übernehmen. Sie müssen also nur die Werte für die einzelnen Bits spaltenweise addieren, und erhalten auf diese Weise 8 Zeilen mit 8 Zahlen, die die Werte darstellen, die Sie später in den Speicher schreiben müssen. Wenn Sie mehrere Zeichen umdefinierten wollen, dann müssen Sie mehrere Tabellen erstellen - für jedes Zeichen eine. Die Daten tragen Sie anschließend in DATA-Zeilen ein, und lesen diese mit der folgenden BASIC-Zeilen aus:

 

10 FOR I=14336+(8*S) TO 14336+(8*E): REM S=STARTZEICHENCODE, E=ENDZEICHENCODE

20 READ A: POKE I,A

30 NEXT I

 

Vielleicht verwenden Sie für die Zeichentabellen auch lieber Excel, dort können Sie die Summe von Werten in Zeilen oder Spalten in einfacher Weise berechnen, wenn Sie statt des Kreuzes eine 1 in die Tabelle eintragen, und für nicht gesetzte Pixel eine 0. Der Königsweg ist sicherlich die Verwendung eines Zeicheneditors, der sich zum Beispiel auch auf dem Disk-Image BASIC-Kurs.d64 befindet. Ich bin zugegebenermaßen sehr stolz auf diesen Editor (den ich übrigens selbst programmiert habe), denn Sie können hier sogar (falls Ihr C64 diese korrekt erkennt) mit der 1351-Maus zeichnen (wenn Sie einen Emulator wie VICE benutzen, dann müssen Sie die Maus vorher über das Einstellungen-Menü aktivieren). Aber auch mit den Cursortasten können Sie arbeiten, falls dann keine Maus vorhanden ist, und Sie können mit dem Editor auch mehrfarbige Zeichen erstellen. Durch Verwendung von Maschinensprache-Routinen ist der Zeicheneditor auch sehr schnell und sehr flüssig zu bedienen.

Auf welche Weise Sie die einzelnen Zeichen jedoch erstellen: Inzwischen sind Sie sicherlich an die Grenzen Ihres Wissens gestoßen. Wie komme ich z.B. darauf, dass mit POKE 53272,30 die RAM-Zeichentabelle an der Adresse 14336 ausgewählt wird?

 

5.1.2 Die Lage der Zeichentabelle im Speicher festlegen

 

Die Antwort liegt hier in der Adresse 53272, was dem VIC-Register Nr. 24 entspricht. Dieses Steuerregister steuert neben anderen Dingen wie dem aktuellen Grafikmodus auch die Lage der Zeichentabelle durch Bit 4-1. Normalerweise bilden diese Bits die Zahl 2 ab (in Adresse 53272 steht normalerweise der Wert 21), was bedeutet, dass sich die Zeichentabelle an der Adresse 2*2048 befindet. Im Endeffekt müsste man sagen „befinden müsste“, denn alle Adressen zwischen 4096 und 8191 werden hier auf das Character-ROM abgebildet - man spricht hier auch von „mapping“. Deshalb wählt der „normale“ Wert von 2 in Bit 4-1 die erste Bank des Character-ROMs aus, und dies sind die Großbuchstaben und die Grafikzeichen. Deshalb wählt dann auch POKE 53272,22 die Zeichentabelle in der zweiten Bank des Character-ROMs aus, und dies ist die Groß/Kleinschrift (beachten Sie bitte, dass Bit 0 in der Adresse 53272 stets auf 1 gesetzt bleibt). POKE 53272,16 wählt also welche Zeichentabelle an welcher Adresse aus? Richtig: Die Adressen 0-2048 im RAM! Probieren Sie es ruhig aus und geben POKE 53272,16 ein. Wenn Sie alles richtig gemacht haben, dann erscheinen sogar einige Zeichen, die sich dauernd ändern. Das ist aber kein Wunder, denn einige Adressen im Bereich 0-255 werden von Kernal benutzt, und ändern sich eben dauernd.

Nun haben Sie alle Informationen zusammen, um die Adresse der Zeichentabelle wie folgt einzustellen (und auch zu verstehen, warum dies so ist):

 

·       POKE 53272,16 (identisch mit POKE 53272,17) wählt als Quelle Adresse 0-2047 aus

·       POKE 53272,18 (identisch mit POKE 53272,19) wählt als Quelle Adresse 2048-4095 aus

·       POKE 53272,20 (identisch mit POKE 53272,21) wählt als Quelle die ROM-Bank Nr. 0 aus

·       POKE 53272,22 (identisch mit POKE 53272,23) wählt als Quelle die ROM-Bank Nr. 1 aus

·       POKE 53272,24 (identisch mit POKE 53272,25) wählt als Quelle Adresse 8192-10239 aus

·       POKE 53272,26 (identisch mit POKE 53272,27) wählt als Quelle Adresse 10240-12287 aus

·       POKE 53272,28 (identisch mit POKE 53272,29) wählt als Quelle Adresse 12288-14335 aus

·       POKE 53272,30 (identisch mit POKE 53272,31) wählt als Quelle Adresse 14336-16383 aus

·       POKE 53272,21 stellt die Standard-Verhältnisse wieder her

 

5.1.3 mehrfarbige Zeichen

 

Im Endeffekt sehen Ihre selbst definierten Zeichen noch sehr blass aus. Sie haben ja nur eine einzige Farbe benutzt! Wenn Sie aber die schon sehr bunten Spiele von früher kennen, dann werden Sie nach kurzer Zeit keinen Spaß mehr an solchen einfachen Zeichen haben. Zum Glück gibt es aber die Möglichkeit, bei der Definition Ihrer Zeichen immer zwei Pixel zu einem einzigen (leider doppelt so großen) Pixel zusammenzufassen, und mit dieser Zahl (im Bereich 0-3) Zeichen mit vier Farben zu benutzen. Allerdings wird die Zahl 0 für die Hintergrundfarbe reserviert. Bleiben noch drei Farben übrig, aber das ist ja schon mal was! In der Tat können Farbe 1 und 2 beliebig mit POKE-Befehlen geändert werden:

 

POKE 53282,[Farbwert] setzt Farbe 1

POKE 53283,[Farbwert] setzt Farbe 2

 

Das heißt, dass mit den VIC-Registern Nr. 34 und 35 der Zeichenfarbe 1 und 2 ein neuer Wert zugewiesen werden kann, der dann für sämtliche Zeichen gültig ist. Allerdings kann Farbe 3 nicht mit

 

POKE 53284,[Farbwert]

 

geändert werden, denn Farbe 3 entspricht der Farbe, die Sie mit dem PRINT-Befehl durch die entsprechenden Farb-Steuerzeichen einstellen müssen. Diese Einschränkung ist nicht so schlimm, wie sie aussieht, denn mit PRINT (oder auch einem POKE in den Farbspeicher) können Sie für jedes Zeichen einen separaten Farbwert für Farbe Nr. 3 festlegen. Allerdings können Sie das Multicolor-Flag nicht mit der Speicheradresse 53272 festlegen, sondern müssen hierzu Bit Nr. 3 in Adresse 53270 setzen:

 

POKE 53270,PEEK(53270) OR 16

 

OR bewirkt hier (weil es als Bitmaske benutzt wird), dass der ursprüngliche Inhalt der Adresse 53270 (VIC-Register Nr. 22) kopiert wird, außer dass bei der Kopie zusätzlich Bit 3 gesetzt ist. Erschrecken Sie nicht, wenn Sie in den Multicolor-Modus wechseln, und nun sämtliche Zeichen leicht verändert dargestellt werden. Das ist ja auch klar: Die Bits in den einzelnen Spalten der 8 Zeilen werden nun anders interpretiert. Wenn Sie den Multicolor-Modus wieder „loswerden“ möchten, dann können Sie die folgende Zeile benutzen:

 

POKE 53270,PEEK(53270) AND (NOT 16)

 

Bei den Bitmasken gilt übrigens folgende Regel:

Bei Verknüpfung einer Zahl mit einer OR-Maske werden bei dieser Zahl sämtliche Bits gesetzt, die auch in der Maske gesetzt (=1) sind, bei Verknüpfung einer Zahl mit einer AND-Maske werden bei dieser Zahl sämtliche Bits gelöscht, die auch in der Maske gelöscht (=0) sind.

 

Bitmasken werden ab nun noch häufiger benutzt, vor Allem bei der Steuerung des Verhaltens der Grafikausgabe. Auch bei der Tonerzeugung mit dem SID werden Ihnen später noch zahlreiche Bitmasken begegnen.

 

5.2 Sprites

 

Der VIC kann über das eigentliche Bild sogenannte MIBs (Moveable Image Blocks) drüberlegen. MIBs sind 24*21 Pixel große Bildchen, die sogar transparente Farbinformationen enthalten können (das heißt, dass Farbe 0 transparent ist). Und nicht nur das: Von wo im Speicher diese kleinen Bildchen geholt werden, kann hier durch spezielle Zeiger in den Adressen 2040-2047 festgelegt werden. Da die MIBs nicht direkt im Bildschirmspeicher stehen, sondern irgendwo wie Kobolde im RAM „herumspuken“, hat sich für den Begriff MIB später das Wort Sprite eingebürgert.

 

5.2.1 Erstellen von Sprites

 

Ein Sprite wird fast wie ein selbst definiertes Zeichen erstellt, das heißt mittels der Definition von Bytes, die die einzelnen Pixel-Bits enthalten. Allerdings müssen für Sprites 3 Bytes für eine Zeile und 21 Zeilen (das sind 63 Bytes pro Sprite) benutzt werden. Allerdings werden die Farben anders definiert, und Farbe 2 ist die Hauptfarbe, die auch beliebig geändert werden kann. Farbe 1 und 3 sind die globalen Farben, die in den VIC-Registern Nr. 37 und 38 stehen. Für Sprites empfehle ich, einen guten Sprite-Editor zu verwenden, mit dem Sie auch mehrere Animations-Frames in einfacher Weise erstellen können. Ein sehr guter Sprite-Editor war damals in den 80-ern Super Spriter- ich weiß aber nicht, ob Sie diesen noch im Internet als D64-Datei finden können. Sie können natürlich Sprites auch von Hand oder mit Excel erstellen, aber dies ist sehr viel Arbeit. Einen eigenen Sprite-Editor habe ich natürlich auch in das Image BASIC-Kurs.d64 eingebunden, und auf diesen Editor bin ich mal wieder sehr stolz, weil man auch hier mit der Maus zeichnen kann. Aber nicht nur das. Sie können die einzelnen Frames horizontal und vertikal drehen, sowie auch kopieren und löschen. Ferner können Sie die Sprite-Frames als DATA-Zeilen in eine BASIC-Datei schreiben lassen.

Nachdem Sie einen Spriteblock definiert und mittels READ und DATA in den Speicher geschrieben haben (z.B. an die Adressen 12288-13248), sehen Sie leider noch nichts, denn Sie müssen ein Sprite einschalten und die Koordinaten festlegen. Ferner muss natürlich bekannt sein, von welcher Adresse der Sprinte-Block geholt werden soll. Und schon wieder müssen Sie sich Speicheradressen merken! Um die Sache etwas zu vereinfachen, kann man sich aber einfach nur die Registernummern des VIC merken, die die Sprites steuern, und die Basisadresse einfach mit

 

V=53248

 

festlegen. So ist es viel einfacher, ein bestimmtes Sprite einzuschalten. Hierzu muss nur das entsprechende Bit im VIC-Register Nr. 21 auf 1 gesetzt werden. Bit 0 bestimmt hier die Sichtbarkeit von Sprite Nr. 0 (gesetzt=sichtbar), und Bit Nr. 7 die Sichtbarkeit von Sprite Nr. 7. Das heißt, dass z.B.

 

POKE V+21,1

 

Sprite Nr. 0 einschaltet. Die Hauptfarbe wird nun mit

 

POKE V+39,[Farbe] festgelegt,

 

also z.B.

 

mit POKE V+39,1

 

auf Weiß gesetzt. Die Adresse, von der der Bildblock für ein Sprite geholt wird, wird in den Adressen 2040 für Sprite Nr. 0 bis 2047 für Sprite Nr. 7 festgelegt. Allerdings haben die Zeiger für die Bildblöcke nur 8 Bits zur Verfügung, deshalb wird der Wert, der in der Adresse 2040 steht (für Sprite Nr. 0) noch mal mit 64 multipliziert, um die tatsächliche Adresse für den Zeiger zu erhalten. Wenn Sie also Ihre Sprite-Daten an Adresse 12288-12348 ablegen, dann adressieren Sie diese mit

 

POKE 2040,192 (192*64=12288)

 

Die Koordinaten für ein Sprite legen Sie mit den VIC-Registern Nr. 0-15 fest, und zwar in Paaren zu je zwei Bytes. Das heißt, die X-Koordinate für Sprite Nr. 0 steht in VIC-Register 0 und die Y-Koordinate in VIC-Register 1. Sichtbar wird also z.B. Sprite Nr. 0 erst durch

 

POKE V,100: POKE V+1,100

 

Nun sehen Sie endlich ihr Sprite, aber so dolle ist die Sache dann auch wieder nicht, denn Sie bekommen Ihr Sprite ums Verrecken nicht an den rechten Bildschirmrand, egal welche Werte Sie in die Adresse 53248 schreiben. Dies ist kein Wunder, denn Ihr Bildschirm fasst 368 Pixel, von denen einige durch den Bildschirmrahmen verdeckt werden (nämlich links und rechts 24 und oben und unten jeweils 50). Bleiben immer noch 320 Pixel für den sichtbaren Teil in X-Richtung, und auch diese Zahl kann natürlich nicht durch ein einzelnes Byte dargestellt werden. Es hilft alles nichts, Sie benötigen für jede X-Koordinate jedes Sprites ein 9. Bit, und diese Bits stehen in VIC-Register Nr. 16. Und ja: Für jedes Sprite gibt es hier wieder (wie auch schon in Register Nr. 21) ein 9. Bit. Aber wie setzen Sie nun Ihr Sprite Nr. 0 z.B. an die Position 280? Auf folgende Weise:

 

X=280: POKE V,X AND 255: POKE V+16,INT(X/256)

 

Der Trick hier: Durch die AND-Maske werden sämtliche Bits von X ab Bit Nr. 9 zu 0 gesetzt.

 

5.2.2 Multicolor-Sprites

 

Multicolor-Sprites funktionieren genauso, wie Multicolor-Zeichen: Es werden in einer Spalte stets 2 Pixel zu einem (leider auch hier doppelt so großen) Pixel zusammengefasst. Jeder gute Sprite-Editor (auch mein eigener) lässt sich zum Glück immer auf Multicolor-Sprites umschalten. Wenn Sie dies per Hand machen wollen, so müssen Sie in VIC-Register 28 das entsprechende Bit auf 1 setzen. Beachten Sie, dass bei Sprites Farbe Nr. 2 die Hauptfarbe ist (nicht Farbe 3!) und 0 die transparente Hintergrundfarbe darstellt. Im Gegensatz zu Zeichen kann bei Sprites auch festgestellt werden, ob ein bestimmtes Sprite mit den Hintergrundzeichen oder einem anderen Sprite kollidiert ist (zu der Kollision gibt es am Ende von Kapitel 5 noch ein Beispiel in Form eines kleinen Autorennens). Ferner kann festgelegt werden, ob ein Sprite im Hintergrund erscheinen soll (in VIC-Register Nr. 27 durch ein gesetztes Bit für jedes Sprite). Dies können Sie mit Zeichen nicht machen. Allerdings können Sie eine Sache nicht ändern, nämlich die Anzeigepriorität: Sprite 7 wird stets zuerst gezeichnet, und Sprite 7 zuletzt, sodass Sprite 0 stets vor allen anderen Sprites und Sprite 7 hinter allen anderen Sprites platziert wird. Wenn Sie es nun besonders „dolle treiben“ wollen, dann können Sie Ihre Sprites noch in X-Richtung (Bits in VIC-Register Nr. 29) und Y-Richtung (VIC-Register Nr. 23) vergrößern, z.B. für wirklich riesige, feindliche Raumschiffe. Ihre Sprites erscheinen dann doppelt so groß. Allerdings wirken sogenannte expandierte Sprites recht pixelig.

 

5.3 Bitmap-Grafiken

 

Bitmaps werden nicht oft benutzt, außer in Zeichen- oder Mathematikprogrammen oder für Vorspänne bei Spielen. Der Grund: Die Verwaltung ist aufwendig, überfordert beim Scrolling den Prozessor, und die Grafiken benötigen zumindest unkomprimiert sehr viel Speicher (ein Bitmap benötigt auf der Diskette ganze 33 Blocks). Trotzdem können Bitmaps eine Option sein, z.B. beim Zeichnen von mathematischen Funktionen.

Im Bitmap-Modus des VIC werden standardmäßig sämtliche Bytes in den Adressen 8192-16383 als Grafik dargestellt, das heißt als Pixel. Es gibt also hier keine Zeichentabelle, sondern die Bytes werden einzeln ausgewertet. Leider geschieht dies nicht Zeile für Zeile (z.B. indem einfach 40 Bytes mit 320 Pixeln in der ersten Reihe, und weitere 40 Bytes in der nächsten Reihe angezeigt werden, usw.). Die Pixel befinden sich vielmehr in zeichenähnlichen, 8 Byte großen Blöcken. Das bedeutet nun folgendes: Die ersten 320 Bytes beschreiben die Pixel in der Bildschirmzeile 0-7, die nächsten 320 Bytes die Pixel in der Bildschirmzeile 8-15, und die einzelnen Pixel entsprechen bestimmten gesetzten Bits in bestimmten Adressen in 8 Zeilen hohen Blöcken (das bedeutet im Endeffekt, dass hier einfach die Character-Engine für den Bitmap-Modus „missbraucht“ wird). Erschwerend kommt noch hinzu, dass es bei Bitmaps einen Multicolor-Modus gibt, was die Verwaltung noch weiter erschwert. Der C64 besitzt also keinen linearen Framebuffer, also einen Speicherbereich, in den Sie die Pixel Byte für Byte hintereinander ablegen können. Wenn Sie z.B. die Zeichendaten für den Klammeraffen und das A in die Adressen 8192-8207 schreiben, so stehen die Bitmap-Blöcke auch direkt nebeneinander, so, als hätten Sie POKE 1024,0: POKE 1025,1 eingegeben. Sie können also einzelne Pixel nur durch einen Algorithmus setzen. Dieser Algorithmus funktioniert wie folgt (einfarbige Bitmaps):

 

·       Festlegen der Koordinaten (X,Y) des nächsten Pixels

·       Die Adresse AD der Bildschirmzeile, in der das Pixel angezeigt wird, ist (Y/8) ohne Komma-Anteil, multipliziert mit 320. Zu diesem Wert wird dann noch der Rest addiert (Y-Offset), der sich ergibt, wenn man von Y (Y/8) ohne Komma-Anteil subtrahiert.

·       Der Wert, mit dem man den Inhalt von AD durch OR verknüpfen muss, muss identisch sein mit der Bitnummer, die man erhält, wenn man von X (X/8) ohne Komma-Anteil subtrahiert (X-Offset).

 

Als BASIC-Listing sieht das Setzen eines Pixels nun so aus:

 

11-SETPIXEL

10 X=100:Y=100:BA=8192

20 GOSUB 10000

30 END

10000 REM *** SETPIXEL ***

10010 Z=INT(Y/8):REM ZUM ZEICHENBILDSCHIRM KOMPATIBLE ZEILENNUMMER

10020 C=INT(X/8):REM ZUM ZEICHENBLIDSCHIRM KOMPATIBLE ZEICHENPOSITION

10030 YO=Y AND 7:REM Y-OFFSET

10040 XO=7-(X AND 7):REM X-OFFSET

10050 AD=BA+Z*320+C*8+YO: REM SPEICHERADRESSE FUER PIXEL-BYTE

10060 POKE AD,PEEK(AD) OR (2^XO): REM PIXEL-BIT SETZEN (MIT OR)

10070 RETURN

 

Nun haben Sie noch ein Problem: Beim Einschalten des C64 wird der Bildschirmspeicher für Bitmap-Bilder nicht initialisiert. Deshalb befinden sich zufällig aussehende Muster in den Adressen 8192-16383, in denen Bitmaps normalerweise abgelegt werden. Deswegen muss, falls Sie vorhaben, Bitmaps zu erstellen, am Anfang Ihres Programmes immer folgende Schleife ausgeführt werden:

 

FOR I=8192 TO 16383:POKE I,0:NEXT I

 

5.3.1 Eine Sinuskurve zeichnen

 

Das Löschen des Bitmap-Speichers benötigt in etwa eine Minute, und das Setzen eines Pixels durch ein Unterprogramm benötigt in BASIC etwa 0,05 Sekunden. Das heißt, dass Sie mit BASIC nur 20 Pixel in der Sekunde setzen können. Obwohl also die ganze Sache ziemlich langsam ist, möchte ich dennoch einige Listings anführen, die ein paar mathematische Funktionen und geometrische Körper auf den Bildschirm zeichnen. Das nächste Listing zeichnet eine Sinuskurve.

 

12-SINUS

10 PRINT"[SHIFT+CLR/HOME]BITTE WARTEN..."

20 FOR I=8192 TO 16383:POKE I,0:NEXT I

30 PRINT"[SHIFT+CLR/HOME]":REM BILDSCHIRM WIEDER LOESCHEN

40 POKE 53265,PEEK(53265) OR 32: REM BITMAP-FLAG AUF 1 SETZEN

50 POKE 53272,PEEK(53272) OR 8: REM BIPMAP-BANK WIRD BEI 8192 EINGEBLENDET

60 FOR I=1024 TO 2023:POKE I,1:NEXT I

70 BA=8192

80 FOR X=0 TO 319 STEP 0.5

90 Y=INT(90+80*SIN(X/10))

100 GOSUB 10000

110 NEXT X

120 END

10000 REM *** SETPIXEL ***

10010 Z=INT(Y/8):REM ZUM ZEICHENBILDSCHIRM KOMPATIBLE ZEILENNUMMER

10020 C=INT(X/8):REM ZUM ZEICHENBLIDSCHIRM KOMPATIBLE ZEICHENPOSITION

10030 YO=Y AND 7:REM Y-OFFSET

10040 XO=7-(X AND 7):REM X-OFFSET

10050 AD=BA+Z*320+C*8+YO: REM SPEICHERADRESSE FUER PIXEL-BYTE

10060 POKE AD,PEEK(AD) OR (2^XO): REM PIXEL-BIT SETZEN (MIT OR)

10070 RETURN

 

Am Anfang muss zunächst der Bildschirmspeicher gelöscht werden, und dies dauert etwas - deshalb die Meldung „BITTE WARTEN“. Um den C64 anschließend in den Bitmap-Modus zu schalten, muss in Zeile 40 das Bitmap-Flag durch Setzen von Bit 5 in Adresse 53265 aktiviert werden. Allerdings verbleibt der Bildschirmspeicher durch diese Maßnahme an der Adresse 0 (dies ist die Standardeinstellung im Bitmap-Modus), deshalb muss anschließend in Zeile 50 noch die Speicherbank für den Bildschirm an die Adresse 8192 verschoben werden. Dies erreichen Sie durch Setzen von Bit 3 in Adresse 53272 auf 1. Nun haben Sie einen Bitmap-Modus gewählt, der kein leeres Bild enthält, deshalb muss vorher in den Zeilen 10-30 eine Schleife ausgeführt werden, die Null-Bytes in die Adressen 8192-16383 schreibt. Da diese Prozedur etwa eine Minute dauert, wird eine entsprechende Meldung angezeigt, dass der Benutzer des Programms warten soll. Leider hat der Bitmap-Modus noch eine Eigenschaft, die man beachten muss, wenn man wirklich eine leere Zeichenfläche erzeugen möchte: Der Zeichenspeicher an den Adressen 1024-2023 wird zusätzlich benutzt, um die Hintergrund- und die Pixelfarbe der einzelnen Bitmap-Blöcke zu bestimmen. Hierbei bestimmt das hohe Nibble (Bit 7-4) die Pixelfarbe, und das niedrige Nibble (Bit 3-0) die Hintergrundfarbe. Um also schwarze Pixel auf weißem Grund anzuzeigen, muss in Zeile 60 noch der Wert 1 in die Adressen 1024-2023 geschrieben werden. Die Basisadresse BA des Bitmap-Bildschirms wird anschließend in Zeile 70 auf 8192 festgelegt.

Das Zeichnen der Sinuskurve ist nun nicht mehr so schwer und kann in den Zeilen 80-110 durch eine einfache FOR-Schleife erledigt werden, die zu den x-Werten zwischen 0 und 319 die Funktion f(x)=sin(x) berechnet. Der Abstand zwischen den Werten auf der x-Achse ist 0,5. Allerdings könnten Sie die Originalwerte der Funktion f(x)=sin(x) nicht sehr gut auf dem Bildschirm sehen, denn die y-Werte für f(x)=sin(x) liegen im Intervall [-1,1]. Deshalb wird in Zeile 90 die Sinusfunktion so skaliert, dass man auch etwas sieht. Hierzu wird die Funktion f(x) um 90 Pixel nach unten verschoben, und anschließend um den Faktor 10 gestreckt. Erst nach der Skalierung wird dann das Unterprogramm zum Zeichen eines Pixels mit GOSUB 10000 aufgerufen.

Das Zeichnen eines Pixels ist wie gesagt etwas tricky, und es gibt hier unzählige Varianten im Internet. Ich habe von diesen Varianten einfach die schnellste verwendet, die es in BASIC gibt. Diese schnelle Variante arbeitet wie folgt: Zunächst wird in Zeile 10010 erst einmal Y durch 8 geteilt, und der Komma-Anteil entfernt. Dadurch erhalte ich eine Zeilennummer Z, die auch kompatibel zu den Zeilen des normalen Zeichenbildschirm ist (0<=Z<=24). Die Spalte C, die kompatibel zum Zeichenbildschirm ist, erhalte ich in Zeile 10020 auf dieselbe Weise (C ist hier die Abkürzung von „character“). Leider bilden X und Y ein Vielfaches von 8 Pixeln ab, deswegen ist dann ein Pixel an der Position (45,45) nicht mehr adressierbar. Um dieses Problem zu lösen, enthält XO und YO einen Offset, der jeweils den Modulo von X/8 und Y/8 enthält (der Modulo ist der Rest einer Division). Leider kann BASIC (und auch der 6510) den Modulo nicht direkt berechnen, und Sie müssen einen Trick anwenden: Sie verknüpfen X und Y in Zeile 10030 und 10040 mit einer AND-Maske, die den Wert 7 enthält. Auf diese Weise erhalten Sie auf eine sehr effiziente Weise eine Division Modulo 8.

Die Bestimmung der Adresse, in die Sie das Pixel eintragen müssen, ist nun nicht mehr weiter schwierig: Für eine Zeile von 8 Pixeln Höhe müssen Sie 40*8=320 Bytes reservieren, für einen Bitmap-Block 8 Bytes. Da Sie auch YO noch in Ihre Berechnungen integrieren müssen, erhalten Sie anschließend die Formel in Zeile 10050. Nun muss zum Schluss nur noch das Pixel gesetzt werden. Die geschieht durch eine OR-Verknüpfung mit dem Inhalt der Adresse AD=BA+Z*320+C*8+YOXO muss durch den Dezimalwert des Bits Nr. XO ersetzt werden. Wenn XO also 1 ist, wird XO zu 1, wenn XO 7 ist, wird XO zu 128. Dies leistet dann die Formel XO=2^XO, die in Zeile 10060 noch zusätzlich mit dem aktuellen Inhalt der Speicheradresse AD verknüpft wird. Zugegeben: Das Setzen von Pixeln ist in BASIC immer noch sehr langsam, selbst, wenn Sie die schnellst mögliche Variante verwenden. Vielleicht denken Sie an dieser Stelle wieder darüber nach, die BASIC-Variante später durch eine Maschinensprache-Routine zu ersetzen. In der Tat beschleunigt diese Variante das Setzen von Pixeln erheblich, aber im Endeffekt können Sie hochwertige Grafiken dann doch nur dadurch erstellen, dass Sie ein professionelles Malprogramm benutzen. Leider ist auch dies nicht trivial, denn es gibt auf dem C64 noch keinen Standard für Bitmap-Grafiken. Wenn Sie also Bilder von der Diskette nachladen wollen, dann müssen Sie das entsprechende Dateiformat sehr gut kennen.

 

5.3.2 Einen Kreis zeichnen

 

Kommen wir nun zum nächsten Beispiel-Listing. In diesem Beispiel wird ein Kreis gezeichnet, der den Mittelpunkt (60,60) und einen Radius von 50 Pixeln hat.

 

13-KREIS

10 PRINT"[SHIFT+CLR/HOME]BITTE WARTEN..."

20 FOR I=8192 TO 16383:POKE I,0:NEXT I

30 PRINT"[SHIFT+CLR/HOME]":REM BILDSCHIRM WIEDER LOESCHEN

40 POKE 53265,PEEK(53265) OR 32:REM BITMAP-FLAG AUF 1 SETZEN

50 POKE 53272,PEEK(53272) OR 8:REM BITMAP-BANK WIRD BEI 8192 EINGEBLENDET

60 FOR I=1024 TO 2023:POKE I,1:NEXT I

70 BA=8192

80 FOR W=-[PI] TO [PI] STEP 0.02

90 X=INT(60+50*SIN(W)): Y=INT(60+50*COS(W))

100 GOSUB 10000

110 NEXT W

120 GET A$:IF A$="" THEN 120

130 POKE 53272,21:POKE 53265,27: PRINT"[SHIFT+CLR/HOME] DAS PROGRAMM WURDE BEENDET"

140 END

10000 REM *** SETPIXEL ***

10010 Z=INT(Y/8):REM ZUM ZEICHENBILDSCHIRM KOMPATIBLE ZEILENNUMMER

10020 C=INT(X/8):REM ZUM ZEICHENBLIDSCHIRM KOMPATIBLE ZEICHENPOSITION

10030 YO=Y AND 7:REM Y-OFFSET

10040 XO=7-(X AND 7):REM X-OFFSET (BIT)

10050 AD=BA+Z*320+C*8+YO:REM SPEICHERADRESSE FUER PIXEL-BYTE

10060 POKE AD,PEEK(AD) OR (2^XO): REM PIXEL-BIT SETZEN (MIT OR)

10070 RETURN

 

Das Unterprogramm für das Zeichnen von Pixeln (Zeile 10000-10070) ist dasselbe Unterprogramm, wie im letzten Beispiel. Auch die Routine zum Initialisieren des Bitmap-Modus (Zeile 10-70) hat sich nicht verändert. Allerdings ist der Algorithmus zum Zeichnen eines Kreises (Zeile 80-110) nicht ganz trivial, weil Sie einen zweidimensionalen geometrischen Körper erstellen müssen. In diesem Fall ist dies ein Kreis, für den gilt:

 

x2+y2=r2.

 

Nach dem Satz des Pythagoras ist der x-Anteil der Koordinate für einen Punkt, der auf dem Kreis mit dem Radius r liegt, die Wurzel aus der Differenz von r2 und y2, und der y-Anteil der Koordinate für einen Punkt, der auf diesem Kreis liegt, ist die Wurzel aus der Differenz von r2 und x2 (r ist also hier die Hypotenuse in einem rechtwinkligen Dreieck). Leider führt diese Erkenntnis nicht direkt zum Ziel, denn es soll ja die Menge aller Punkte gezeichnet werden, die vom Kreismittelpunkt den Abstand r haben. Nach Euler gilt nun:

 

Sei M der Mittelpunkt eines Kreises, und die Strecke MR führe zu dem Punkt R, der die Koordinaten (x,y) besitzt. Der Abstand zwischen M und R sei der Kreisradius r, und der Winkel in R sei w. Dann gilt:

x=r*sin(w)

y=r*cos(w)

 

In Zeile 90 wird nun genau diese Formel angewendet, um für einen variablen Winkel (angegeben im Bogenmaß) und einen Kreis mit einem Radius von 50 Pixeln die Koordinaten aller Punkte zu berechnen, die auf dem Kreis liegen. Der Mittelpunkt des Kreises liegt bei M=(60,60). Das Intervall, in dem sich der Winkel ändert, liegt zwischen -PI und PI (PI ist hier die Zahl 3,14), und ändert sich in Schritten von 0,02. Um Zeile 80 korrekt auszuführen, gibt es mehrere Möglichkeiten. Bei BASIC-Versionen über 3.0 können einige Konstanten, wie z.B. die Kreiszahl PI, in eckigen Klammern angegeben werden. BASIC setzt dann an die Stelle des Ausdrucks [PI] die Kreiszahl auf 8 Stellen genau ein. Bei den BASIC-Versionen 2.0 und 3.0 gibt es allerdings das Zeichen π auf der Tastatur, und das Zeichen π erscheint dann auch korrekt im Programm-Listing. Beim Standard-C64 ist dies dann auch so, allerdings nicht bei dem beliebten C64-Nachfolger Plus 4. Dort müssen Sie die Zahl π nicht oft benutzen, weil der Plus 4 Funktionen besitzt, um Kreise direkt zu zeichnen.

 

Hinweis: Bildschirmkoordinaten vs. kartesische Koordinaten

In dem in der Mathematik oft benutzten kartesischen Koordinatensystem befindet sich der Nullpunkt oft in der Mitte des Zeichenblattes, in dem Koordinatensystem, das der C64 für die Pixel benutzt, liegt allerdings der Punkt (0,0) in der linken oberen Ecke, und die Y-Koordinaten erhöhen sich von oben nach unten. Sie müssen also im Bedarfsfall das kartesische Koordinatensystem in das Koordinatensystem des C64-Bildschirms durch folgende Formel umrechnen:

Sei M der Mittelpunkt des Koordinatensystems, der auf die Bildschirmkoordinaten (X0,Y0) abgebildet wird, und (X,Y) ein Punkt im kartesischen Koordinatensystem. Dann gilt:

X‘=X+X0 und

Y‘=Y-Y0,

wobei (X‘,Y‘) der zu dem Punkt (X,Y) im kartesischen Koordinatensystem gehörige Bildschirmpunkt ist.

 

5.3.3 Multicolor-Bitmaps

 

Wie bei den Zeichen auch, können Sie im Bitmap-Modus das Multicolor-Bit in Adresse 53270 auf 1 setzen. Dies führt dann im Bitmap-Modus dazu, dass immer zwei Pixel zu einem Pixelpaar zusammengefasst werden, mit dem Sie eine Farbnummer zwischen 0 und 3 auswählen können. Auch hier ist die Farbnummer für die Hintergrundfarbe 0, wogegen die Farbnummern 1 und 2 die Farben benutzen, die in den Adressen 53282 und 53283 angegeben wurden. Die Hauptfarbe (Farbnummer 3) wird dagegen so ermittelt, wie im Single-Color-Modus, nämlich durch das entsprechende Byte in den Adressen 1024-2023, das zu dem gerade verwendeten Bitmap-Block passt. Dies ist vielleicht auf den ersten Blick etwas verwirrend, aber nur so kann der C64 genug Speicher einsparen, um auch ganze Bilder vollständig anzeigen zu können.

Der Multicolor-Bitmap-Modus wird auch oft als hires 3 (hi resolution 3 color mode) bezeichnet, was allerdings etwas verwirrend ist. Denn im Endeffekt reduziert sich bei hires 3 die horizontale Auflösung auf die Hälfte, also auf 160x200 Pixel. Ich persönlich würde dann sogar von „lores 3“ sprechen, vor allem deshalb, weil mit Tricks quasi wirklich drei Farben mit einer Auflösung von 320x200 Pixeln angezeigt werden können (allerdings eventuell mit Artefakten im Bild). Im Internet gehen die Begriffe „hires 3“ für den Multicolor-Bitmap-Modus und den hochauflösenden „tricky hires 3 mode“ mit der Inkaufnahme von Artefakten durcheinander, was die Sache dann noch schwieriger macht.

 

5.3.3.1 Zeichnen einer Ellipse im Multicolor-Modus

 

Im nächsten Beispiel wird nun eine Ellipse mit drei verschiedenen Farben gezeichnet, die je nach Wert des Winkels W ausgewählt werden (siehe nächste Abbildung). Um den Multicolor-Modus zu nutzen, wird nun die Routine zum Setzen eines bestimmten Bits in einer Bildschirmspeicheradresse zweimal hintereinander gehängt, einmal wird jedoch ein Bit gesetzt, und einmal wird das entsprechende Bit gelöscht. Die Zeichenroutine zum Setzen eines Multicolor-Pixels ruft dann je nach übergebener Farbe die Routinen zum Setzen und Löschen von Bits entsprechend auf.

 

14-ELLIPSE

10 PRINT"[SHIFT+CLR/HOME]BITTE WARTEN..."

20 FOR I=8192 TO 16383:POKE I,0:NEXT I

30 PRINT"[SHIFT+CLR/HOME]":REM BILDSCLIRM WIEDER LOESCHEN

40 POKE 53265,PEEK(53265) OR 32: POKE 53270,PEEK(53270) OR 16

50 POKE 53272,PEEK(53272) OR 8:REM BITMAP-BANK WIRD BEI 8192 EINGEBLENDET

60 FOR I=0 TO 1000:POKE 1024+I,71: POKE 55296+I,0:NEXT I

70 BA=8192

80 FOR W=-[PI] TO [PI] STEP 0.02

90 CL=3

100 IF (W>-[PI]) AND (W<0) THEN CL=1

110 IF (W>0) AND (W<([PI]/2)) THEN CL=2

120 X=INT(60+50*SIN(W)):Y=INT(60+50*COS(W))

130 GOSUB 10000

140 NEXT W

150 GET A$:IF A$="" THEN 150

160 POKE 53272,21:POKE 53265,27: POKE 53270,200

170 PRINT"[SHIFT+CLR/HOME]DAS PROGRAMM WURDE BEENDET"

180 END

10000 REM *** SET PIXEL (CL=FARBE) ***

10010 T=X:X=(2*X)+1

10020 IF (CL AND 1)=1 THEN GOSUB 20000:GOTO 10030

10025 GOSUB 30000

10030 X=X-1:CL=CL/2:IF (CL AND 1)=1 THEN GOSUB 20000:GOTO 10040

10035 GOSUB 30000

10040 X=T

10050 RETURN

20000 REM *** SETBIT ***

20010 Z=INT(Y/8):REM ZUM ZEICHENPEEKILST

R$SCHIRM KOMPATIBLE RETURNEILENNUMMER

20020 C=INT(X/8):REM ZUM ZEICHENBLIDSCHI

RM KOMPATIBLE ZEICHENPOSITION

20030 YO=Y AND 7:REM Y-OFFSET

20040 XO=7-(X AND 7):REM X-OFFSET (BIT)

20050 AD=BA+Z*320+C*8+YO:REM SPEICHERADRESSE FUER PIXEL-BYTE

20060 POKE AD,PEEK(AD) OR (2^XO):REM PIXEL-BIT SETZEN (MIT OR)

20070 RETURN

30000 REM *** CLEARBIT ***

30010 Z=INT(Y/8):REM ZUM ZEICHENBILDSCHIRM KOMPATIBLE RETURNEILENNUMMER

30020 C=INT(X/8):REM ZUM ZEICHENBLIDSCHIRM KOMPATIBLE ZEICHENPOSITION

30030 YO=Y AND 7:REM Y-OFFSET

30040 XO=7-(X AND 7):REM X-OFFSET (BIT)

30050 AD=BA+Z*320+C*8+YO:REM SPEICHERADRESSE FUER PIXEL-BYTE

30060 POKE AD,PEEK(AD) AND (NOT(2^XO)):REM PIXEL-BIT LOESCHEN (MIT AND)

30070 RETURN

 

Die Ellipse selbst wird in den Zeilen 80-130 gezeichnet. Der Winkel W wählt hierbei die Farbe CL aus, und übergibt diese Farbe in Zeile 130 an das PSet-Unterprogramm zum Setzen eines Pixels. Die PSet-Routine beginnt nach wie vor in Zeile 10000, jedoch entspricht hier eine Einheit auf der x-Achse zwei Subpixeln auf dem Bildschirm, die zusammen eine einzige Farbe darstellen. Beim Setzen eines Farbpixels wird immer das rechte Subpixel (also das mit dem niedrigsten Bit-Wert) zuerst gezeichnet, und dann das linke. Wenn ein Subpixel einem 0-Bit entspricht, dann wird das entsprechende Pixel auf dem Bildschirm gelöscht (GOSUB 20000), wenn ein Subpixel einem 1-Bit entspricht, dann wird das entsprechende Pixel auf dem Bildschirm gesetzt (GOSUB 30000). Dieses Vorgehen ist inzwischen so langsam, dass Sie hier gewissermaßen an die Grenze des mit BASIC Machbaren stoßen.

 

5.4 Verschiedene Schreibweisen für Bitfolgen

 

Sie wissen ja: Im sogenannten binären Zahlensystem, das Computer (besser: die in ihnen enthaltenen Prozessoren) intern benutzen, gibt es nur die Ziffern 0 und 1, und aus diesen Ziffern werden in Form von relativ langen Symbolketten Zahlen aufgebaut. Natürlich können Sie dann die Zahlen auch so darstellen, z.B. als

 

1000001

 

für die Zahl 65. Die Zahl 65 im Dezimalsystem kommt hier - wie schon bei den Sprites und Zeichen - dadurch zustande, dass Sie die Stellenwerte der einzelnen Bits addieren:

 

1*26+0*25+0*24+0*23+0*22+0*21+1*20=64+0+0+0+0+0+1=65

 

Der Zahl 65 sieht man aber leider nicht direkt an, wie die entsprechenden Bits verteilt sind. Hinzu kommt noch, dass für den 6510-Prozessor diese Bitfolge in der Tat eine 8-Bit-Zahl ist, für den VIC dagegen sind dies einfach nur Pixel. Eine Bit-Folge muss also stets interpretiert werden, denn sonst hat sie keinen Sinn. Deshalb haben sich auch verschiedene Schreibweisen eingebürgert. Die Schreibweise aus dem Alltag ist die Dezimalschreibweise, die man auch durch eine kleine 10 am Ende der Zahl angibt (man schreibt also 6510). Die entsprechende Binärschreibweise ist 10000012. Allerdings wird diese Schreibweise schnell unübersichtlich, deshalb verwendet man oft Viererblöcke, die Nibbles, die nur halbe Bytes enthalten. Für die Zahl 65 sieht dies dann so aus:

 

6510=0100 00012

 

Wenn man nun hergeht, und die Werte in den einzelnen Nibbles notiert, dann erhält man die beim C64 sehr oft verwendete Hexadezimalschreibweise:

 

6510=0100 00012=4016

 

Für 4016 wird beim C64 (vor Allem in Assemblerprogrammen) auch die Schreibweise $40 benutzt, und für Dezimalzahlen die Schreibweise #65. Dies liegt einfach daran, dass mit dem Standard-Zeichensatz keine Indizes dargestellt werden können. Aber halt: Was macht man, wenn in einem Nibble die durchaus gültige Zahlenfolge 10102 für den Wert 10 steht? Ganz einfach: Den Zahlen 10,11,12,13,14 und 15 werden zusätzlich die Buchstaben A-F zugeordnet. So ist dann auch der höchste Hexadezimalwert für die Zahl #255 der Wert $FF.

 

5.5 Scrolling

 

Vielleicht haben Sie sich schon gefragt, wie Spiele es schaffen, den Bildschirm in alle Richtungen zu bewegen, statt nur nach oben. Sie haben dann vielleicht auch schon folgende BASIC-Zeile ausprobiert:

 

PRINT “[CLR/HOME][25*CURSOR UNTEN]”

 

Auf diese Weise scrollt der Bildschirm aber nach oben, während viele einfache Autorennen zeichenweise nach unten scrollen. Vielleicht haben sie nun die folgende Schleife ersonnen, um das Problem zu lösen:

 

FOR I=1983 TO 1024 STEP -1:POKE I,PEEK(I-40):NEXT I

 

Probieren Sie es ruhig aus, und Sie werden sehen, dass der Bildschirm tatsächlich nach unten wandert, und die obere Zeile durch Klammeraffen ersetzt wird. Aber die BASIC-Scrolling-Routine ist trotzdem noch viel zu langsam. Stellen Sie sich nun vor, die BASIC-Routine wird durch ein Maschinensprache-Pendant ersetzt, das Sie mit SYS 49152 aufrufen können. In diesem Fall erhalten Sie dann das folgende Listing:

 

15-DOWNSCROLL

10 FOR I=49152 TO 49198:READ A:POKE I,A:NEXT I

20 SYS 49152:REM SCROLLING TESTEN

30 END

10000 REM *** SCROLLING-ROUTINE (UNTEN) ***

10010 DATA 169,191,133,250,169,7,133,251,160,0,177,250,160,40,145,250

10020 DATA 56,165,250,233,1,133,250,165,251,233,0,133,251,24,165,251,201

10030 DATA 3,208,228,169,32,162,39,157,0,4,202,16,250,96

 

Auf diese Weise können Sie nun Maschinenprogramme für alle vier Richtungen erstellen, was viele Spiele auch tun. Im Endeffekt erhalten Sie auf diese Weise einen BASIC-Maschinensprache-Hybriden, der genau dort Maschinensprache-Routinen benutzt, wo BASIC zu langsam ist.

 

5.5.1 Smooth Scrolling

 

Zusätzlich zu der Möglichkeit, Maschinensprache-Routinen für das zeichenweise, ruckelige Scrolling zu benutzen, bietet der VIC auch noch die Möglichkeit, den Inhalt des Bildschirms selbst zu verschieben. In vertikaler y-Richtung geht dies über die Adresse 53265 (VIC- Register Nr. 17). Normalerweise steht hier der Wert 27 (Hexadezimal $1B). Wenn nun Bit Nr. 3 gelöscht wird, dann wird der Bildschirm in y-Richtung eng gestellt. Dies geht z.B. durch POKE 53265,19 oder auch durch

 

POKE 53265,PEEK(53265) AND (NOT 8)

 

Der eng gestellte Modus hat also nur 24 Bildschirmzeilen, und normalerweise wird in diesem Modus die obere und untere Zeile nur halb angezeigt. Sie können nun zusätzlich noch den y-Offset verstellen, ab dem das Bild angezeigt wird. Dies geht mit den untersten 3 Bits in Adresse 53265 (VIC-Register Nr. 17). Normalerweise steht hier der Wert 3, was bedeutet, dass eben die halbe erste Zeile noch sichtbar ist. Wenn Sie allerdings hier den Wert 0 eintragen (z.B. mit POKE 53265,16), dann ist die oberste Zeile nicht sichtbar, und die unterste Zeile ganz sichtbar. Im Endeffekt können Sie also pixelweise nach unten scrollen, z.B. wenn Sie die folgende Schleife benutzen:

 

10 FOR I=16 TO 23:POKE 53265,I:NEXT I

20 PRINT”[CLR/HOME][Was immer Sie hier anzeigen wollen, z.B. eine Rennstrecke]“

30 POKE 53265,16:SYS 49152: REM HIER SEI DAS SCROLLING NACH UNTEN ABGELEGT

40 GOTO 10

 

Auch nach links können Sie pixelweise scrollen, indem Sie Adresse 53270 (VIC-Register Nr. 22) benutzen. Normalerweise steht hier der Wert 200 ($B8). Wenn Sie nun Bit Nr. 3 in Adresse 53270 löschen, dann wird der Bildschirm in x-Richtung eng gestellt. Normalerweise steht auch in Adresse 53270 in den untersten 3 Bits ein Verschiebungswert, hier ist es der x-Offset. Hier ist der Offset-Wert normalerweise 0, was bedeutet, dass nur 38 Spalten noch sichtbar sind. Wenn Sie allerdings hier den Wert 7 eintragen (z.B. mit POKE 53265,199), dann ist die rechte Spalte Nr. 40 nicht, und die rechte Spalte ganz sichtbar. Im Endeffekt können Sie also pixelweise nach links scrollen, z.B. wenn Se die folgende Schleife benutzen:

 

10 FOR I=199 TO 192 STEP -1:POKE 53270,I:NEXT I

20 PRINT”[CLR/HOME][Was immer Sie hier anzeigen wollen, z.B. einen Level]“

30 POKE 53270,199:SYS 49152: REM HIER SEI DAS SCROLLING NACH LINKS ABGELEGT

40 GOTO 10

ABER ES GEHT ALLES NICHT WIRKLICH, WEIL DER BILDSCHIRM FLIMMERT

 

Nun haben Sie wirklich ein Problem, und dies ist nur noch mit sehr guten Assemblerkenntnissen lösbar. Mehr noch: Scrolling (vor Allem das weiche, flimmerfreie Scrolling) ist in der Tat eines der vertracktesten Probleme beim C64. Und dies liegt an der Art und Weise, wie der VIC seine Bilder aufbaut, nämlich mit Hilfe des Rasterstrahls. Der Rasterstrahl zeichnet das Bild streng von oben nach unten, und fängt auch bei Zeile 0 an. Wenn dann der Strahl außerhalb des sichtbaren Bildes liegt, wird der Rahmen angezeigt. Dies gilt sowohl in horizontale, als auch in vertikale Richtung. Wo Rahmen und Bild anfangen sollen, kann man in gewissen Grenzen verstellen, z.B. durch die Adressen 53265 und 53270. Alles andere ist aber fest vorgegeben.

Dies hat nun die folgenden Konsequenzen: Wenn Sie eines der VIC-Register ändern, während der Rasterstrahl noch im sichtbaren Bereich ist (also nicht auf dem Rahmen), dann zeichnen Sie quasi eine Zeit lang nur halbe Bilder, und natürlich flimmert dann das Scrolling - zumindest, wenn die Verschiebung in Pixeln gerechnet nicht ein Vielfaches von 8 ist. Sie müssen also das weiche Scrolling so realisieren, dass Sie die Adressen 53265 und 53270 nur dann ändern, wenn der Rasterstrahl die Position 0 hat (oberer Rahmen) oder 255 (unterer Rahmen). Leider hilft dies auch noch nicht so viel, denn beim Scrolling nach unten frischen Sie das Bild von unten nach oben auf. Das heißt dann, dass Ihr Bildaufbau und der Rasterstrahl gegenläufig arbeiten. Auch dies flimmert beim weichen Scrolling, denn zu allem Überfluss ist der Prozessor des C64 nicht fähig, dem Rasterstrahl quasi „vorauszueilen“ und ein vollständiges Bild aufzubauen, während nur der Rahmen gezeichnet wird. Es gibt nun folgende Lösungen, die aber nur in Assembler realisiert werden können:

 

·                 Verwenden von effizienten Byte-Vertauschungsalgorithmen, die das Bild von oben nach unten auffrischen und ggf. die untere Bildhälfte zuerst aufbauen, und danach erst die obere

·                 Verwenden von illegalen OP-Codes, die mehrere Schritte gleichzeitig ausführen können, um dadurch den Kopiervorgang zu beschleunigen (es geht hier im Endeffekt um das Einsparen von Prozessor-Taktzyklen durch OP-Codes, die eigentlich nicht so vorgesehen sind)

·                 Verwenden des Raster-Interrupts für eigene Bildauffrischungsroutinen, ggf. in Kombination mit illegalen OP-Codes und/oder Abschalten des BASIC-ROMs und/oder überflüssiger Interrupt-Quellen (auch dies spart Zeit ein)

·                 Verwenden eines Offscreen-Buffers: Hier wird das eigentliche (veränderte) Bild in einem nicht sichtbaren Speicherbereich abgelegt und anschließend durch eine schnelle Kopierschleife in den sichtbaren Bereich verschoben. Ein Offscreen-Buffer ist quasi der flimmerfreie Königsweg, deshalb bekommt die Methode, die einen Offscreen-Buffer benutzt auch einen extra Namen: Double Buffering. Der große Nachteil von Double Buffering ist der zusätzliche Speicherverbrauch, und auch die Tatsache, dass der Farbspeicher leider nicht verschoben werden kann.