Spiele-Kurs für Assemblerprogrammierer
(Teil 5)

64'er, Ausgabe 10/Oktober 1989

Heute erfahren Sie, warum die Hintergründe professioneller Spiele so toll aussehen - bis hin zum Schatten des Raumschiffs über fremder Erde.

Die Anwendung selbstdefinierter Zeichensätze bei Spielen liegt darin, die sonst üblichen Schriftzeichen in Grafikzeichen umzudefinieren, aus denen man dann schließlich das Spielfeld zusammensetzt (Bild 1 und 2). Dies hat einige wesentliche Vorteile gegenüber einer Bitmap. Zum einen ist der Speicherbedarf geringer, zum anderen läßt sich ein aus Zeichen zusammengesetztes Spielfeld wesentlich schneller bearbeiten, z.B. verschieben (scrollen).

Bild 1. Für schnelle Bewegung im Spiel sorgt der im Zeichensatz-Format abgelegte Hintergrund.

Bild 2. Hier erkennt man am gleichmäßen Hintergrund, daß dieser aus geändertem Zeichensatz besteht.

Das Video-RAM ist schnell

Nach dem Einschalten des Computers befindet sich dieser automatisch im Textmodus, wobei das Video-RAM bei Adresse 1024 beginnt (Videobasis). Diese Speicherzelle definiert das erste Zeichen auf dem Bildschirm, also das Zeichen in der linken obere Ecke (Zeile 0, Spalte 0). Speicherzelle 1025 definiert das nächste Zeilen (Zeile 0, Spalte 1) etc., bis bei der Speicherzelle 1024 + 39 = 1063 das letzte Zeichen der erste Zeile (Zeile 0, Spalte 39) erreicht ist. Speicherzelle 1064 definiert das erste Zeichen der Folgezeile (Zeile 1, Spalte 0). Betrachten Sie hierzu Bild 3. Numerieren wir die Zeilen von ZL = 0 bis ZL = 24 und die Spalten von SP = 0 bis SP = 39 und nennen wir die Videobasis BA, so errechnet sich die RAM-Adresse AD für Zeile ZL und Spalte SP folgendermaßen:

AD = BA + 40 * ZL + SP

Bild 3. Der Aufbau des Video-RAMs beim C64.

Wie Sie bemerkt haben, können auf dem Bildschirm nur 1000 Zeichen dargestellt werden. Da der Computer aber 1 KByte = 1024 Byte als Video-RAM reserviert, bleiben noch 24 Byte übrig (normalerweise 2024 bis 2047). Die letzten 8 Byte dienen als Sprite-Pointer (siehe Teil 4), die restlichen 16 Byte kann man für andere Zwecke nutzen. Je nachdem, welcher Wert in einer Speicherzelle des Video-RAMs steht, erscheint an der entsprechenden Stelle ein Zeichen auf dem Bildschirm. Tippen Sie z.B. POKE 1024,1 ein, erscheint in der linken oberen Ecke des Bildschirms ein »A«, geben Sie POKE 1064,26 ein, erscheint in der zweite Zeile ein »Z«. Denjenigen Wert, der in die Speicherzelle geschrieben wurde, nennt man Bildschirmcode. Er sagt dem VIC, an welcher Stelle im Zeichensatz das gewünschte Zeichen zu finden ist. Die Bildschirmcodes der verschiedenen Zeichen finden Sie in ihrem C64-Handbuch. Verwechseln Sie diese bitte nicht mit den ASCII-Codes, sie haben nichts miteinander zu tun.

Der Zeichensatz selbst befindet sich für den VIC ab Adresse 4096 ($1000). Hierbei definieren die ersten 8 Byte eine 8 x 8-Punktmatrix, die den Buchstaben mit dem Bildschirmcode 0 (also den Klammeraffen) darstellt. Die nächsten 8 Byte definieren das »A« (Code 1) etc. (siehe Bild 4). Findet der VIC also im Video-RAM den Bildschirmcode 17, so besorgt er sich die Zeichendaten des Zeichens (ein »Q«) aus dem Speicherbereich 4096 + 17 * 8 bis 4096 + 17 * 8 + 7 und stellt sie als Bitmuster auf dem Bildschirm dar. Da die Bildschirmcodes von 0 bis 255 laufen, können 256 Zeichen zu je 8 Byte dargestellt werden, der Zeichensatz belegt somit 256 * 8 = 2048 Byte. Schaltet man durch gleichzeitiges Drücken der Commodore- und Shift-Taste den Kleinzeichensatz ein, so wird der Zeichensatz ab 6144 ($1800) dargestellt. Beide Zeichensätze, von denen stets nur einer zur Zeit verwendet werden kann, belegen zusammen 4 KByte. Der Standardzeichensatz, von dem bisher die Rede war, befindet sich fest in einem ROM eingebrannt.

Bild 4. Der Aufbau eines Zeichensatzes.

Um zu verstehen, wie der VIC an diesen Zeichensatz gelangt, muß man zwischen zwei Perspektiven underscheiden: Erstens die Perspektive, aus der der Prozessor den Speicher sieht und zweitens die Perspektive, aus der der VIC den Speicher sieht.Für den Prozessor ist der Speicherbereich nach dem Einschalten folgendermaßen aufgeteilt:

$0000-$9FFF
RAM
$A000-$BFFF
Basic-ROM
$C000-$CFFF
RAM
$D000-$DFFF
I/O Bausteine
$E000-$FFFF
Kernal-ROM

Für den VIC sieht die Speicheraufteilung so aus:

$0000-$0FFF
RAM
$1000-$1FFF
Zeichensatz-ROM
$2000-$8FFF
RAM
$9000-$9FFF
Zeichensatz-ROM
$A0O00-$FFFF
RAM

Für den VIC existiert also so etwas wie Basic-ROM oder Kernal-ROM nicht. Deshalb kann man z.B. eine Bitmap an die Stelle anlegen, wo für den Prozessor das Basic-ROM steht und der VIC stellt trotzdem nicht den Inhalt des Basic-ROMs, sondern den RAM-Inhalt dar. Andererseits enthält die Tabelle zweimal den Punkt »Zeichensatz-ROM«. An diesen Stellen befinden sich für den VIC die Bitmuster der Zeichen, die man im Normalzustand auf dem Bildschirm zu sehen bekommt. Versuchen Sie, in einem der Bereiche Spritedaten, eine Bitmap oder ähnliches abzulegen, greift der VIC trotzdem auf das Zeichen-ROM zu. Diese Speicherbereiche sind also für Grafikprogrammierung nicht zu gebrauchen.

Wie erstelle ich einen Zeichensatz?

Um den VIC zu veranlassen, einen anderen Zeichensatz als den Standardzeichensatz darzustellen, muß man einen eigenen Zeichensatz im RAM generieren und dem VIC anschließend sagen, wo der neue Zeichensatz zu finden ist. Es stellt sich nun die Frage, in welchem RAM-Bereich man den Zeichensatz am besten speichert. Man kann einen Zeichensatz in jeden Bereich legen, dessen Startadresse ohne Rest durch 2048 teilbar ist (z.B. 8192, 10240, 16384 etc.). Ausnahmen bilden hier die oben beschriebenen Speicherbereiche $1000-$1FFF bzw. $9000-$9FFF. Ferner muß der Zeichensatz in der gleichen Bank wie das Video-RAM liegen. Liegt das Video-RAM wie gewöhnlich in Bank 0, muß der Zeichensatz ebenfalls in Bank 0 liegen. Wie Sie vielleicht wissen, lassen sich die störenden ROMs aus der Sicht des Prozessors ausschalten und an diesen Stellen RAM einblenden, so daß man mehr Speicherplatz zur Verfügung hat. Dies ist allerdings etwas haarig und wird deshalb im nächsten Kursteil beschrieben. Für Basic-Programmierer und diejenigen, die in Assembler noch nie ROMs ein- bzw. ausgeschaltet haben, ist die Speicheraufteilung nach Bild 5 sinnvoll. Die günstigere Speicheraufteilung, die allerdings aufwendiger zu realisieren ist, sehen Sie in Bild 6.

Bild 5. So sieht die Speicheraufteilung für Basic-Programmierer aus.

Bild 6. Optimaler ist der Speicher für Assembler-Programmierer aufgeteilt.

Zunächst verändern wir den Standardzeichensatz des C64. Die Beispiele sind der Einfachkeit halber in Basic geschrieben, eine Umsetzung in Maschinensprache dürfte allerdings kein Problem sein, da das Programm hauptsächlich aus POKE-Befehle besteht. Bevor Sie das Programm Schritt für Schritt eingeben, muß allerdings der Basic-Start nach oben gelegt werden, da der Zeichensatz bei 2048 beginnen soll. Dies geschieht gemäß Kursteil 3 mit der Befehlsfolge:

POKE 43,1:POKE 44,64:POKE 16384,0:NEW

Der Basic-Start liegt nun bei 16385, so daß im Bereich 8192 bis 16383 auch Sprites untergebracht werden können. Um den Standardzeichensatz zu modifizieren, muß man erst einmal an ihn herankommen. Die Speicherbereiche $1000-$1FFF bzw. $9000-$9FFF können dazu nicht herangezogen werden, da sich dort für den Prozessor gewöhnliches RAM befindet. Die Speicherstelle 1 ermöglicht es jedoch, ab Adresse 53248 ($D000) das Zeichensatz-ROM einzublenden. Vorher muß allerdings der IRQ gesperrt werden, da bei eingeschaltetem Zeichensatz-ROM keine Zugriff auf die I/O-Bausteinen erfolgen kann. Da das Sperren des IRQs in Basic nicht durch setzen des I-Flags im Prozessorstatus möglich ist, schaltet man einfach die IRQ-Quelle, den Timer des CIA 1 aus. In Basic sieht das so aus:

10 POKE 56334,PEEK(56334)AND254:REM IRQ AUSSCHALTEN
20 POKE 1,PEEK(1) AND 251:REM ZEICHEN-ROM EINBLENDEN

Starten Sie das Programm aber noch nicht, da der IRQ ausgeschaltet wird und anschließend keine Eingaben mehr möglich wären! In Assembler sperrt man den IRQ natürlich mit »SEI«:

SEI
LDA 1
AND #251
STA 1

Nun kann man die Zeichensatz-Daten ins RAM kopieren und dort modifizieren.

In Basic sieht die Schleife so aus:

30 FOR X=0 TO 2047:POKE 2048+X,PEEK(53248+X):NEXT X

(Anmerkung: Diese Schleife dauert in Basic 32 Sekunden.)

Nachdem der Zeichensatz umkopiert wurde, muß das Zeichen-ROM abgeschaltet und der IRQ wieder freigegeben werden:

40 POKE 1,PEEK(1) OR 4:REM ZEICHEN-ROM ABSCHALTEN
50 POKE 56334,PEEK(56334) OR 1:REM IRQ FREIGEBEN

In Assembler geschieht dies wie folgt:

LDA 1
ORA #4
STA 1
CLI

Grafikzeichen selbst definieren

Der Zeichensatz befindet sich jetzt ab Adresse 2048 ($0800) im RAM-Speicher. Nun kann man die Zeichendaten verändern. Möchte man beispielsweise das »+« in ein »ß« umwandeln, so muß man zunächst seine absolute Adresse im Speicher herausfinden. Dies geht mit folgender Formel:

Adresse = Zeichensatzstart + Bildschirmcode * 8

In unserem Falle also 2048 + 43 * 8 = 2392. Die Zeichendaten des Pluszeichens stehen somit in Speicherbereich 2392-2399. Dort muß jetzt das Bitmuster des »ß« abgelegt werden. Dies besteht, wie schon erwähnt, aus 8 Byte. Ein Byte entspricht einer Zelle der 8 x 8 Punktmatrix. Setzen Sie nun beispielsweise Bit 7 im Byte 0, so wird das erste Pixel in der erste Zeile der Matrix gesetzt. Bei der Berechnung der Bytes können Sie wie in Bild 7 verdeutlicht vorgehen. Belegen Sie nun die Speicherzellen 2392-2399 mit den berechneten Zahlen (60 POKE 2392,60:POKE 2393,102, etc.). Starten Sie das Programm und drücken Sie anschließend die Plustaste, erscheinen wie gewöhnlich die zwei gekreuzten Linien. Da kommt daher, daß wir dem VIC noch nicht gesagt haben, wo der neue Zeichensatz zu finden ist. Dies geschieht in folgenden Schritten:

  1. Bank festlegen
  2. Farbmodus wählen
  3. Startadresse des Zeichensatzes angeben.

Bild 7. Beispiel für die Berechnung von Zeichendaten.

Nach einem Reset ist der VIC schon auf die richtige Bank (Bank 0: 0-16383 bzw. $0000-$3FFF) eingestellt. Dieser Punkt entfällt also. Sollten Sie den Zeichensatz in einer anderen Bank untergebracht haben, so wählen Sie diese wie im Kursteil 3 beschrieben. Beachten Sie dabei, daß das Video-RAM ebenfalls in diese Bank gelegt werden muß. Den Farbmodus wählt man mit Hilfe des VIC-Registers 22 wie folgt:

100 VIC = 53248:REM VIC-BASISADRESSE
110 POKE VIC+22,PEEK(VIC+22) AND 239:REM EINFARB-MODUS

Die Startadresse des Zeichensatzes teilen Sie dem VIC mit folgenden Befehlen mit:

A = (Startaddresse - Bankstart)/2048*2
POKE VIC+24,(PEEK(VIC+24)AND240) OR A

Es ist sinnvoll, den Wert von A im Programm nicht ständig neu zu berechnen. Man setzt normalerweise eine Konstante ein, die zuvor mit einem Taschenrechner berechnet wurde. In unserem Falle wäre der Wert von A (2048-0)/2048*2 = 2.

Zeile 120 lautet dann:

120 POKE VIC+24,(PEEK(VIC+24)AND240) OR 2

Nach dem Starten des Programms greift der VIC nun auf unseren modifizierten Zeichensatz zu. Alle Pluszeichen erscheinen jetzt als »ß«, d.h. allerdings nicht, daß das Pluszeichen seine Funktion bei Rechenoperationen verloren hat, es wird lediglich nicht als »+«, sondern als »ß« dargestellt!

Die Information über die Farbe der dargestellten Zeichen (also über die gesetzten Pixel) bekommt der VIC aus dem Farb-RAM, welches sich ab Adresse 55296 ($D800) in I/O-Bereich befindet. Es ist genauso organisiert wie das Video-RAM, nur daß an Stelle der Bildschirmcodes Farbcodes stehen. Die Hintergrundfarbe (also die Farbe des gelöschten Pixel) wird dagegen für den gesamte Schirm durch den Inhalt von Register 53281 festgelegt. Schreiben Sie nun z.B. mittels POKE 55296,0 eine 0 in die erste Speicherzelle des Farb-RAMs, so wird das linke obere Zeichen auf dem Bildschirm schwarz, da 0 der Farbcode für Schwarz ist (vergleiche Handbuch).

Generell gilt: Beim Auslesen des Farb-RAMs enthält des obere Nibble einen Zufallswert. Um einen korrekten Wert zu erhalten, müssen nach dem Lesen des Farb-RAMs die oberen 4 Bit gelöscht werden, also: X=PEEK(55296)AND15. Dies liegt daran, daß das Farb-RAM ein 4-Bit-Speicher ist und sowohl bei Lesezugriffen als auch Schreibzugriffen nur die unteren 4 Bits berücksichtigt werden.

Nun lassen sich natürlich statt der Schriftzeichen auch Grafikzeichen definieren, wie sie im Standardzeichensatz des C64 schon vorhanden sind. Der Vorteil gegenüber einer Bitmap liegt darin, daß oft benötigte Bitmuster nur einmal im Zeichensatz definiert sein müssen und anschließend nur doch mit dem Bildschirmcode aufgerufen werden. Hierzu benötigt man, wie Sie wissen, nur 1 Byte, während das dazugehörige Bitmuster 8 Byte in Anspruch nimmt. Möchten Sie in Ihrem Spiel bewegte Hintergründe verwenden (Scrolling), so ist ein aus Zeichen aufgebauter Bildschirm eine absolute Notwendigkeit, da nur 1 KByte Video-RAM und nicht 8 KByte Bitmap zu verschieben sind. Das Scrolling ist allerdings erst Thema des nächsten Kursteils.

Nun wäre es etwas eintönig, immer nur einfarbige Zeichen für ein Spiel darstellen zu können. Aus diesem Grund stellt der VIC eine Betriebsart zur Verfügung, die es erlaubt, vierfarbige Zeichen (einschließlich Hintergrund) anzuzeigen. In diesem Modus repräsentieren jeweils 2 Bit ein Pixel. Je nach Kombination der beiden Bits wird das Pixel in der entsprechenden Farbe dargestellt.

In diesem Darstellungsmodus nimmt die Auflösung in X-Richtung ab, da nach wie vor nur 8 Byte für ein Zeichen zur Verfügung stehen. In den Mehrfarbmodus gelangt man durch setzen von Bit 4 im Register 22. Ersetzen wir also die Zeile 110 durch folgende Zeile:

110 POKE VIC+22,PEEK(VIC+22) OR 16:REM MEHRFARBMODUS EIN

Starten Sie das Programm (Zeile 30 können Sie löschen, da der Zeichensatz ja schon kopiert wurde). Wie Sie sehen, hat ein Zeichen jetzt mehr als eine Farbe. Wie funktioniert diese Arbeitsart nun? Wie schon gesagt, richtet sich die Farbe eines Pixels jetzt nach der Bitkombination zweier benachbarter Bits.

In Bild 8 haben wir ein Zeichen, das mit bestimmten Bitkombinationen belegt ist. Daneben sehen Sie, welche Farbverteilung daraus resultiert. Woher der VIC die Farben für die betreffenden Kombinationen nimmt, zeigt folgende Tabelle:

Bitkombination Farbregister
00 (Hintergrundfarbe) 53281
01 53282
10 53283
11 untere 3 Bits im Farb-RAM

Bild 8. Bildkombinationen und resultierende Farbe im Farbmodus.

Aus der Tabelle wird deutlich, daß drei Farben für alle Zeichen auf dem Bildschirm durch drei Speicherzellen bestimmt werden. Die Bitkombination »11« ist dagegen ein Sonderfall. Die Pixel, die durch die Bitkombination »11« definiert sind, bekommen diejenige Farbe, die in der entsprechenden Speicherzelle des Farb-RAMs steht. Diese Farbe kann also für jedes Zeichen auf dem Bildschirm individuell festgelegt werden. Hier ist jedoch ein Einschränkung zu beachten: Die Pixel, die mit »11« definiert wind, können nur die den Farbcodes 0 bis 7 entsprechen. Dardurch wird im Farb-RAM das Bit 3 frei. Es wird dazu benutzt, anzuzeigen, ob das entsprechende Zeichen im Einfarb- bzw. im Mehrfarbmodus dargestellt werden soll. Ist Bit 3 im Farb-RAM gesetzt, erfolgt eine mehrfarbige Darstellung, ist das Bit gelöscht, erfolgt eine einfarbige Darstellung, obwohl sich der Computer im Mehrfarbmodus befindet. Man kann also beide Farbmodi mischen!

Hier ein Beispiel:

Sie haben, wie zuvor beschrieben, den Computer in den Mehrfarbmodus gebracht. Tippen Sie nun irgendeinen Buchstaben ein. Es erscheint ein unleserliches Zeichen, da der Zeichensatz für den Einfarbmodus bestimmt ist. Setzen Sie nun mit Hilfe der Tastenkombination CTRL+1 die Cursorfarbe auf Schwarz, und, tippen Sie erneut ein Zeichen ein. Es erscheint im Einfarbmodus. Nach dem Einschalten hat der Cursor die Farbe »Hellblau«, das entspricht dem Farbcode 14. Bei einer binären 14 ist das Bit 3 gesetzt: 1110. Bei der Cursorfarbe »Schwarz« (Farbcode 0) ist das Bit 3 gelöscht, die Darstellung dieser Zeichen erfolgt also im Einfarbmodus.

WICHTIG: Sie können die obigen Basic-Programme auch downloaden:

Aufbau von Hintergründen

Da die Programmierung selbstdefinierter Zeichen sowohl im Einfarb- also auch im Mehrfarbmodus auf die beschriebene Weise sehr mühselig ist, gibt es natürlich Utilities. Ein solches Utility stellt der Character-Editor des Spielegenerators »Master-Tool« dar, der in der 64'er 1/88 veröffentlicht wurde. Er legt seinen Zeichensatz grundsätzlich ab 2048 ab. Hat man einen Zeichensatz erstellt und abgespeichert, so kann das abgespeicherte File anschließend mit LOAD "FILE",8,1 an diese Stelle des Speichers geladen werden. Für diejenigen, denen dieser Zeichensatz-Editor nicht zur Verfügung steht, ist auf der Programmservice-Diskette ein einfacher Zeichensatz-Editor gespeichert.

Baut man geometrische Objekte aus selbstdefinierten Grafikzeichen auf, so empfehlt es sich, die Zeichen so zu definieren, daß sie universell anwendbar sind. Man kann auf diese Weise verschiedene Objekte aus den gleichen Zeichen zusammensetzen.

Schatten mit Sprites

Möchte man mit Sprite-Hintergrund-Kollisionen arbeiten, so ist folgendes zu bemerken: Die Bitkombinationen 00 und 01 rufen keine Kollision hervor. Man setzt also diejenigen Teile, die eine Kollision hervorrufen sollen, aus den Bitkombinationen 10 bzw. 11 zusammen. Es kommt häufig vor, daß sich ein Sprite vor einem aus selbstdefinierten Zeichen aufgebauten Hintergrund bewegen soll. Einen eindrucksvollen Effekt erzielt man, wenn dieser Sprite einen Schatten wirft. Dieser Schatten wird ebenfalls durch einen Sprite dargestellt. Nun hat ein Schatten normalerweise die Eigenschaft, zu verschwinden, wenn er auf Himmel oder weit entfernte Objekte fällt. Um dies zu erreichen, geht man folgendermaßen vor: Man setzt die Objekte, auf denen der Schatten nicht abgebildet werden soll, aus den Bitmuster »11« bzw. »10« zusammen, also aus Bitmustern, die vom VIC als »Vordergrund« angesehen werden. Die Teile, auf denen der Schatten erscheinen soll, setzt man aus den Bitmustern »01« und »00« zusammen. Nun gibt man dem Schattensprite eine Farbe, die einem Schatten einigermaßen gerecht wird, etwa Grau 1 (Farbcode 11) und setzt das entsprechende Bit im Sprite-Hintergrund-Prioritätsregister.

Dies hat zur Folge, daß der Schatten hinter Gebilden, die aus »11« bzw. »10« bestehen, verschwindet, da diese Kombinationen für den VIC als Vordergrund gelten, und daß der Schatten auf Teilen, die aus »00« bzw. »01« bestehen, abgebildet wird, da diesen Kombinationen für den VIC Hintergrund darstellen. Anders ausgedrückt: Diejenigen Objekte, die als »Vordergrund« erscheinen sollen, erhalten »Hintergrund-Bitkombinationen«. Achtung! Hardwaremäßige Kollisionserkennung ist bei einer solchen Zuordnung nicht mehr möglich.

In diesem Kursteil haben Sie gelernt, wie man einen Zeichensatz erstellt. Im nächsten Kurs wird erläutert, wie man mit Hilfe eines solches Zeichensatzes bewegliche Hintergründe aufbaut und schnell und flimmerfrei bewegt (Softscrolling).

(H. Rosenfeld/aw)