Logo gameLib.de Coding
In diesem Bereich befinden sich die gesammelten Artikel zu den Themen Programmiersprache, Algorithmen und Programmiertechniken im Allgemeinen.

SDL und Spieleentwicklung

Dieser Artikel befasst sich mit der Spielentwicklung mit SDL. Ich habe den Artikel und das folgende Tutorial auf einem Debian-Linux mit dem Compiler g++ und der Grafiksoftware Gimp erstellt, es ist aber auch ohne weiteres auf andere Systeme übertragbar. Den Quellcode und die zusätzlichen Dateien könnt Ihr hier herunterladen. Um den Artikel zu verstehen würde ich dies auch empfehlen. Des Weiteren solltest Du rudimentäre Kentnisse in C oder einer C-ähnlichen Sprache haben um die Funktionsweise des Programms zu verstehen.

Einführung

SDL steht für Simple Direct Media Layer und ist eine freie Multimedia-Bibliothek für verschiedene Plattformen. Zu den Plattformen gehören alle Unix-Derivate somit auch Linux, aber auch MacOS, Windows, die Dreamcast-Konsole, die XBox und Andere. SDL ist eine API (Application Programming Interface), also eine Schnittstelle und bietet dem Entwickler eine Plattform zur Ansteuerung von Grafik, Audio und Steuerung.
Bild
SDL ist durch die Vielzahl an unterstützten Plattformen geeignet um portierbare Anwendungen zu schreiben. SDL stellt zwar keine direkten Funktionen für 3D-Grafik zur Verfügung, allerdings lässt sich damit OpenGL einbinden. Jedoch ist das nicht der Inhalt dieses Artikels.

Der Vorteil ist, das man sich durch SDL keine Gedanken um die Ansteuerung der Hardware und des Betriebssystems machen muss, sondern sich direkt auf die Programmierung konzentrieren kann. Ein Vorteil der Portierbarkeit ist, das man nur einmal den Code schreibt und dieser problemlos zusammen mit den SDL-API's für andere Platformen compiliert werden kann ohne etwas am Programmcode selbst ändern zu müssen. Wenn Du also zum Beispiel ein Spiel für Linux entwickelt hast, kannst du es ohne Weiteres mit der MacOs-Bibliothek zusammen auf einem System auf welchem MacOs läuft kompilieren.

SDL ist für jeden frei verfügbar und steht unter der GNU Lesser General Public License. SDL ist in C geschrieben und funktioniert ohne spezielle Anbindung mit C++, bietet aber Anbindung an über 10 Sprachen. Darunter Java, Python, PHP, Perl usw.

Durch die Anbindung an essentielle Multimediafunktionen, ist SDL geradezu prädestiniert zur Spieleentwicklung. SDL hat auch eine gewisse Erfolgsgeschichte bei der Spielentwicklung zu bieten. Es wrude von Sam Lantinga entwickelt, einem Entwickler bei Loki Software, die Spiele wie Soldier of Fortune, Unreal Tournament, SimCity, Tribes und Andere kommerziell zu Linux portiert haben. Sam Lantinga arbeitet heute für die bekannte Spieleschmiede Blizzard Entertainment.

Sowohl auf der deutschsprachigen Webseite als auch auf der englischsprachigen Webseite von SDL, könnt Ihr Demos und Spiele zum Thema SDL runterladen.

Installation

Für das folgende Tutorial sind lediglich die Pakete libsdl und sdl_image erforderlich. Es empfiehlt sich jedoch alle Pakete dafür herunterzuladen. Unter Debian sind die Paketquellen schon standardmäßig in die Paketverwaltung eingepflegt, ihr müsst also nur nach libsdl suchen. Dabei solltet Ihr die normalen Pakete sowie diejenigen mit der Endung -dev, also die Entwicklerpakete, installieren.

Diejenigen die kein Debian verwenden können sich die Pakete von der deutschsprachigen Webseite als auch von der englischsprachigen Webseite runterladen.

Kleines Tutorial

Das folgende Tutorial wurde in C geschrieben. Für größere Projekte würde ich jedoch C++ empfehlen. Beim Kompilieren des Codes, müsst Ihr darauf achten das Ihr die SDL-Bibliotheken mit einbindet. Die Pfade dafür könnt Ihr nach der Installation der SDL-Pakete mittels sdl-config --libs und sdl-config --cflags herausfinden. Auf meinem System liefert sdl-config --libs die Ausgabe -L/usr/lib -lSDL -lpthread und sdl-config --cflags liefert -I/usr/include/SDL folglich wird der Code mit Befehlszeile g++ -o testdatei -L/usr/lib -lSDL -lpthread -I/usr/include/SDL -lSDL_image quelldatei.c kompiliert. Warum wir -lSDL_image noch zusätzlich einbinden wird gleich aus dem Code ersichtlich, das -o gibt an das die kompilierte Datei testdatei heissen soll. Ich werde im folgenden nur auf die SDL-spezifischen Codefragmente eingehen.

Aber nun zu dem eigentlichen Code. Zunächst einmal müssen die C-Standardbibliothek und SDL selbst sowie SDL_image eingebunden werden. SDL.h beinhaltet die Standardfunktionen von SDL und SDL_image.h Funktionen die Grafik betreffen. time.h brauchen wir für den Zufallszahlengenerator.

#include <stdlib.h>
#include "SDL.h"
#include "SDL_image.h"
#include "time.h"
...


SDL arbeitet für die Grafik mit sogenannten Surfaces. Ein geöffnetes Fenster ist ein Surface, aber auch ein Bitmap das wir in das Fenster setzen ist ein Surface. Deswegen deklarieren wir uns folgende Surfaces, display für das Fenster selbst und die anderen für die restlichen grafischen Elemente in unserem kleinen Spiel.

...
int main()
{
     SDL_Surface *display, *image, *image2, *kristall, *ausgang, *player, *enemy;
...


Wir brauchen um geladene Grafiken auf dem Display vervielfältigen und kopieren zu können, rechteckige Elemente, mit denen wir aus den Bildern die wir später laden umgehen. Ein kleines d vor dem jeweiligen Element steht dabei für destination (Ziel) und ein kleines s für source (Quelle). Um uns die Rechtecke bereitzustellen deklarieren wir sie über den SDL-eigenen Datentyp SDL_Rect.

...
SDL_Rect drect, srect;
SDL_Rect dplayer, splayer;
SDL_Rect denemy, senemy;
...


Damit wir die Maus oder die Tastatur verwenden können, brauchen wir sogenannte events. Ein event ist ein Eingabeereigniss, welches bei Auftreten Zusatzinformationen in sich trägt. Zum Beispiel welche Maus- oder Keyboardtaste gedrückt wurde. Um einen event verwenden zu können deklarieren wir uns ihn über den SDL-eigenen Datentyp SDL_Event.

SDL_Event event;


Als nächstes sagen wir SDL das das Video-Modul initialisiert werden soll. Das geschieht mittels der Funktion SDL_Init() als Parameter übergeben wir SDL das Flag SDL_INIT_VIDEO. Und Ihr habt es bestimmt schon erraten, SDL_INIT_VIDEO steht eben genau für "Grafikausgabe initialisieren". Es gibt noch weitere solcher Flags wie SDL_INIT_AUDIO, die in unserem Tutorial aber keine Rolle spielen. In dem Code-Fragment fragen wir auch noch gleichzeitig ab, was uns SDL_Init denn zurückgibt, wäre es kleiner als 0, so würde das bedeuten das ein Fehler bei der Initialisierung aufgetreten ist. Eine mehr oder weniger genaue Fehlermeldung können wir uns mittels SDL_GetError() ausgeben lassen. SDL_GetError() gib immer den zuletzt aufgetretenen Fehler aus.

...
if ( SDL_Init( SDL_INIT_VIDEO ) < 0 ) {
     fprintf(stderr, "SDL konnte nicht initialisiert werden: %s\n", SDL_GetError());
     exit(1);
}
...


Die folgende Zeile solltet Ihr immer verwenden, damit mögliche SDL-Prozesse nicht im Hintergrund weiterlaufen und Speicher verschwenden nachdem das Programm beendet wurde.

atexit(SDL_Quit);


Hier verwenden wir nun das Surface display um ein Fenster zu erzeugen. Die Funktion SDL_SetVideoMode() erwartet die Parameter Fensterbreite und Fensterhöhe in Pixel, Farbtiefe in Bit und ein Flag welches angibt ob Software oder Hardware-Rendering verwendet werden soll. SDL_SWSURFACE wie in unserem Beispiel steht für Software-Rendering. Das Gegenstück dazu wäre SDL_HWSURFACE. Falls die Zuweisung nicht geklappt haben sollte, würde der Variablen display der Wert NULL zugewiesen und wir würden dem Fehler mit SDL_GetError() wieder auf die Spur kommen.

...
display = SDL_SetVideoMode(fensterBreite ,fensterHoehe ,16 ,SDL_SWSURFACE);
if ( display == NULL ) {
      fprintf(stderr, "Konnte Fenster nicht bauen: %s\n", SDL_GetError());
     exit(-1);
}
...


Als nächstes initialisieren wir das Surface image mittels IMG_Load(). Nun haben wir das Bild tile01.jpg im Speicher. In unserem kleinen Spiel sind das die Wände. Das Abfangen möglicher Fehler funktioniert genau wie in den Codefragmenten zuvor.

image = IMG_Load("tile01.jpg");
if ( image == NULL ) {
     fprintf(stderr, "Bild konnte nicht geladen werden: %s\n", SDL_GetError());
     exit(-1);
}
...


Weiter oben hatte ich beschrieben, das SDL einen eigenen Datentyp SDL_Rect verwendet, um mit Bildern umgehen zu können. Dieser Datentyp ist ein struct, der noch weiter Parameter enthält. Mitunter die x- und y- Koordinaten sowie die Breite (w) und Höhe des Rechteckes. Diese Werte initialisieren wir jetzt. Bei der Zuweisung der Breite und Höhe können wir auf die Werte aus dem Surface auslesen, welches wir für das Bild, hier tile01.jpg, verwenden.

...
srect.x = 0;
srect.y = 0;
srect.w = image->w;
srect.h = image->h;

drect.x = 0;
drect.y = 0;
drect.w = image->w;
drect.h = image->h;
...


An dieser Stelle im Code wird das "Level" aufgebaut. Im den Quelldateien (den Du ja runtergeladen hast ;) ) wird die genaue Funktionsweise ersichtlich. Wichtig an dieser Stelle ist die Funktion SDL_BlitSurface(), die nichts anderes macht als unser Bild (image respektive "tile01.jpg"), auf das Fenster (display) zu kopieren. Die Positions und Auschnittsangaben haben wir zuvor durch die zwei Rechtecke srect und drect definiert. Allerdings ist bis zu diesem Schritt nur im Speicher hin- und herkopiert worden, ein Ergebnis ist noch nicht sichtbar. Damit sich das ändert rufen wir die Funktion SDL_UpdateRects() auf und aktualisiern damit den Bereich unseres Zielrechteckes drect auf dem Surface display. Wir könnten auch das komplette display aktualisieren (mittels SDL_Flip(display)), allerdings empfiehlt es sich aus Performancegründen immer nur den Teilbereich, den man auch verändert hat zu aktualisieren.

...
drect.x = tilesize * x;
drect.y = tilesize * y;
if( map[y][x] == 1){
     SDL_BlitSurface(image, , display, &drect);
}
else {
     SDL_BlitSurface(image2, , display, &drect);
}

if( map[y][x] == 2){
     SDL_BlitSurface(kristall, &srect, display, &drect);
}

if( map[y][x] == 3){
     SDL_BlitSurface(ausgang, &srect, display, &drect);
}

SDL_UpdateRects(display, 1, &drect);
...


Nun kommen wir zum Abfangen von Eingaben. Am Anfang des Codes hatten wir ein SDL_Event namens event deklariert. Wie schon erwähnt lassen sich durch dieses Ereignis alle Eingaben ob von Maus oder Tastatur abfangen. Dazu gibt es die Funktion SDL_PollEvent um auftretende Ereignisse aus dem Speicher zu lesen. Wir überprüfen dann was für ein event.type aufgetreten ist und sortieren in unserem Fall nach SDL_KEYDOWN (also Taste gedrückt) aus. Dann hangeln wir uns noch durch die event-Struktur zu event.key.keysym.sym um zu überprüfen welchen ASCII-Wert der jeweiligen Taste zugeordnet ist. In unserem Fall die 276 also der Pfeil nach links.

...
while( SDL_PollEvent( &event ) ) {
switch( event.type ) {
     case SDL_KEYDOWN:
          if(event.key.keysym.sym == 276 && (map[playerYPos][playerXPos-1] != 1)) { // nach links
          playerXPos--;
}
...


Zu den Events gehört auch zum Beispiel die Aktion wenn auf das Schliessen-Symbol des Fensters gedrückt wird. Deswegen fangen wir den event.type SDL_QUIT hier ab.

...
     case SDL_QUIT:
          quit = 1;
          break;
...


Zum Schluss müssen wir noch den Speicher freigeben den wir mit unseren Grafiken belegt haben.

...
SDL_FreeSurface(image);
SDL_FreeSurface(image2);
SDL_FreeSurface(player);
SDL_FreeSurface(enemy);
SDL_FreeSurface(kristall);
SDL_FreeSurface(ausgang);
exit(0);
}


Ich hoffe mit diesem kleinen Tutorial einen Überblick über die Systematik wie man SDL im Zusammenhang mit Spieleentwicklung verwenden kann ein wenig näher gebracht zu haben. Auch wenn das gezeigte Beispiel kein Killer-Game ist, so bleiben die Grundlagen für grössere Projekte die Gleichen. Für Fragen, Anregungen oder Kritik einfach eine Mail an info@gamelib.de.

Marco Hertwig - www.gamelib.de - 22.08.2005
Impressum

Valid XHTML 1.0 Transitional Valid CSS!