Sprites


Nahezu jedes Spiel (und auch viele andere Programme) auf jedem System benutzten Sprites. Das sind kleine "Bildchen" mit einer (meist) einheitlichen Weite, die vielseitig eingesetzt werden. Beispielswiese das Spiel "Snake": Eure Schlange besteht aus Sprites, die je nach eurer Größe aneinandergereiht werden. Ebenso sind die Äpfel Sprites.

Daher ist es für jeden Programmieren unausweichlich zu wissen, wie man Sprites definiert und mit ihnen umgeht. Ich setzte hier BASIC-Erfahrung voraus. Dort werdet ihr sicherlich auch Sprites benutzt haben, meist in der Form von einfachen .PIC-Variablen, die ihr mit Befehlen wie 'RclPic' an eine Stelle des Displays gesetzt habt.

Natürlich bietet auch TIGCC uns Sprite-Funktionen an. Wenn wir uns auf die Funktionen aus sprites.h beschränken, können wir Sprites mit einer Weite von 8, 16 und 32 Pixeln verwenden. Die Höhe kann jeweils beliebig gewählt werden. Sehen wir uns einmal an, wie ein Sprite definiert wird:

   // Dies ist ein 8x8-Sprite:
   unsigned char SuperSprite[8] = {
      0x3C,0x42,0xA5,0x81,0xA5,0x99,0x42,0x3C

   }


Hier sehen wir Hexadezimalzahlen (siehe voriges Kapitel), zusammengefasst in einem Array des Typs unsigned char. Die Zahlen sehen zuerst einmal nichtssagend aus, klarer wird die ganze Sache jedoch, wenn wir statt Hexadezimalzahlen Binärzahlen verwenden:

   // Dies ist ein 8x8-Sprite, diesmal in Binärschreibweise:
   unsigned char SuperSprite[8] = {
      0b00111100,
      0b01000010,
      0b10100101,
      0b10000001,
      0b10100101,
      0b10111101,
      0b01000010,
      0b00111100
   }


Wir sehen jetzt also eine Ansammlung von Einsen und Nullen. Versucht euch, für jede 1 einen schwarzen Pixel und für jede 0 einen weißen (also leeren) Pixel vorzustellen. Das sollte dann das Bild eines Smileys ergeben. Hierr ist die Tatsache sehr praktisch, dass wir eine 4-stellige Binärzahl zu einer Hexadezimal-Ziffer zusammengfügen können. Für die erste Zeile:
0b0011 ergibt ín Hexadezimal 3 (1*2^0+1*2^1+0*2^2+0*2^3)
0b1100 ergibt in Hexadezimal C (dezimal 12) (0*2^0+0*2^1+1*2^2+1*2^3)
Das ergibt genau unseren ersten Wert, 0x3C.
Und weshalb unsigned char? Nun, eine 8-stellige Binärzahl bzw. eine 2-stellige Dezimalzahl kann maximal den Wert 255 erreichen, also ist der Typ char ausreichend. Für größere Sprites benötigen wir also dann logischerweise größere Variablen, aber dazu später.

Um diesen Sprite zu verwenden, gibt's wie immer erst mal ein kleines Beispiel mit diesem Sprite (nennt es 'sprites1'):

// C Source File
// Created 16.01.2005; 23:04:15

#include 

#define SPRITEHEIGHT 8

// Hier ist unser Smiley:
unsigned char SuperSprite[8] = {
   0x3C,0x42,0xA5,0x81,0xA5,0x99,0x42,0x3C
};


// Haupt-Funktion
void _main(void)
{
   // Die Variablen für die Sprite-Position und den Tastencode
   short xpos = 0, ypos = 0, key;
   
   ClrScr();
   
   // Anzeigen des Sprites
   Sprite8(xpos, ypos, SPRITEHEIGHT, SuperSprite, LCD_MEM, SPRT_XOR);
   
   do {
      key = ngetchx();
      
      // Löschen des Sprites
      Sprite8(xpos, ypos, SPRITEHEIGHT, SuperSprite, LCD_MEM, SPRT_XOR);
            
      if (key == KEY_UP && ypos)      // Bewegung nach oben
         ypos--;
      else if (key == KEY_DOWN && ypos < LCD_HEIGHT-SPRITEHEIGHT)   // Bewegung nach unten
         ypos++;
      else if (key == KEY_RIGHT && xpos < LCD_WIDTH-SPRITEHEIGHT)   // Bewegung nach rechts
         xpos++;
      else if (key == KEY_LEFT && xpos)   // Bewegung nach links   
         xpos--;
         
      // Anzeigen des Sprites an der neuen Position
      Sprite8(xpos, ypos, SPRITEHEIGHT, SuperSprite, LCD_MEM, SPRT_XOR);
            
      } while (key != KEY_ESC);
}


Na gut, wie immer erst einmal ausprobieren. Wie wir sehen, beweght sich der Sprite je nach Tastendruck einer Richtungstaste über den Bildschirm.

Gehen wir's also an.

#define SPRITEHEIGHT 8


#define ist eine Präprozessor-Direktive. Nach dieser Zeile wird jedes vorkommen von SPRITEHEIGHT mit dem Wert 8 ersetzt. Das ist nützlich, wenn wir viele Zahlenwerte haben, die häufiger vorkommen. So können wir sie erstens leichter ändern und behalten dazu einen besseren Überblick.

// Hier ist unser Smiley:
unsigned char SuperSprite[8] = {
   0x3C,0x42,0xA5,0x81,0xA5,0x99,0x42,0x3C
};


Ihr wundert euch wahrscheinlich, wieso die Definition der Sprite-Variablen ausserhalb de _main -Funktion stattfindet. Nun, eine Variable, die nicht in einer Funktion steh, wird als 'globale Variable' bezeichnet, das heißt jede Funktion kann auf sie zugreifen. Da wir hier nur eine Funktion habe, wäre das nicht nötig gewesen, aber somit habt ihr wieder was gelernt ;).

Die nächsten paar Zeilen sollten klar sein; interressant wird's ab hier:

   // Anzeigen des Sprites
   Sprite8(xpos, ypos, SPRITEHEIGHT, SuperSprite, LCD_MEM, SPRT_XOR);


Hier steht ein für uns unbekannter Befehl, der Sprite8-Befehl. Damit können wir den Sprite anzeigen lassen. Das erste Argument ist die x-Position, das zweite die y-Position. Danahc kommt die Höhe des Sprites, die wir am Anfang definiert haben. Es folgt die Sprite-Variable und als nächstes die Addresse. LCD_MEM hat den Wert 0x4C00, welcher die Addresse des LCD Screens im Speicher des Rechners darstellt. Wird der Sprite zu dieser Addresse kopiert, erscheint er auf dem Display (zu Addressen usw. kommen wir in späteren Lektionen noch). SPRT_XOR ist der Modus, in dem der Sprite dargestellt wird, in diesem Fall also XOR. Das heisst, sind der zuzeichnende Pixel und der der bereits vorhanden ist, gleich, wird er weiß gezeichnet. Sind sie verschieden, wird der Pixel schwarz.

      if (key == KEY_UP && ypos)      // Bewegung nach oben
         ypos--;
      else if (key == KEY_DOWN && ypos < LCD_HEIGHT-SPRITEHEIGHT)   // Bewegung nach unten
         ypos++;
      else if (key == KEY_RIGHT && xpos < LCD_WIDTH-SPRITEHEIGHT)   // Bewegung nach rechts
         xpos++;
      else if (key == KEY_LEFT && xpos)   // Bewegung nach links   
         xpos--;


Hier werten wir die Eingabe des Users aus und erhöhen/vermindern je nach bedarf die x- oder die y-Position. && heisst 'AND', also führen wir die Aktion erst aus, wenn beide Prüfungen erfolgreich sind. key == KEY_UP && ypos heisst demnach, der User muss die [HOCH]-Tasrte gedrückt halten und ypos muss größer als 0 sein, da es einen Error verursachen würde, würden wir über die Bildschirmgrenzen hinwegzeichnen.
key == KEY_DOWN && ypos < LCD_HEIGHT-SPRITEHEIGHT: Die Aktion wird nur ausgeführt, wenn [RUNTER] gedrückt wurde und die y-Position kleiner als LCD_HEIGHT-SPRITEHEIGHT ist, also um 8 kleiner als die Bildschirmhöhe. Wir hätten auch 128-SPRITEHEIGHT schreiben könne, doch was, enn der User das Programm auf einem TI89 benutzt, dessen Bildschitm lediglich 100 Pixel hoch ist?. Deshalb gibt es in TIGCC Pseudo-Konstanten wie LCD_HEIGHT, die je nach Taschenrechnermodell variieren.
LCD_WIDTH beschreibt die Weite des Displays, ansonsten sind die horizontalen Bewegungen genauso wie die vertikalen.

Danach wird der Sprite neu gezeichnet, diesmal mit den neuen Positionen. Er wurde ja vor der Auswertung der Eingabe gelöscht.

      } while (key != KEY_ESC);


Das sollte klar sein: Drückt der User [ESC], wird die Schleife verlassen und, da nichts mehr nahc der Schleife steh, das Programm beendet.

Das war jetzt vielleicht etwas viel auf einmal, aber der Schwierigkeitsgrad wird ja von Lektion zu Lektion erhöht, um euch ständig zu fordern und die Sache interessant zu machen :D. Sprites sind sehr wichtig und wir werden in Zukunft viel mit ihnen arbeiten, also stellt sicher, das ihr dieses Kapitel verstanden habt!