SDL-Tutorial #1 - Erste Schritte


von Nicolai 'Prefect' Hähnle
angepasst auf diese Homepage von Ricco Clemens

 

Was erwartet Euch?

In diesem Tutorial, das sich über mehrere Kapitel erstrecken wird, versuche ich Euch SDL näher zu bringen.
Ihr solltet auf jeden Fall die Sprache C und Eure Programmiertools einigermaßen beherrschen, auch wenn ich mich mit überkomplizierten Konstrukten zurückhalte.

Was ist SDL überhaupt?

SDL steht für Simple DirectMedia Layer und ist eine Opensource-Bibliothek für die Spieleprogrammierung. Die Homepage findet ihr auf http://www.libsdl.org/.

Man kann SDL vom Standpunkt des Programmierers recht gut mit DirectX vergleichen, obwohl die beiden sehr verschieden sind. SDL hat gegenüber DirectX zwei entscheidende Vorteile:
1. SDL ist Opensource, das heißt man kann sich den Quellcode der Bibliothek selbst ansehen.
2. SDL ist plattformübergreifend. Wenn man einmal ein Spiel unter Windows programmiert hat muß man es lediglich neu kompilieren und es läuft auch unter Linux und anderen Betriebssystemen.

Eigentlich ist SDL aber etwas grundlegend anderes als DirectX: SDL ist eine Bibliothek, die auf verschiedenen Betriebssystemen die zugrundeliegenden APIs so vor dem Programmierer (also Euch) versteckt, daß ein und derselbe Quellcode ohne großen Portierungsaufwand auf verschiedenen Betriebssystemen kompilier- und lauffähig ist.

Unter Windows verwendet SDL zum Beispiel selbst DirectX (weshalb SDL auch nicht wirklich mit DirectX vergleichbar ist), um auf die Hardware zuzugreifen, wie im folgenden Schema zu sehen ist:

Kein Bild im Orginal vorhanden... :(

Was man an diesem Schema auch sieht: SDL führt einen zusätzlichen Schritt auf dem Weg zwischen dem Spiel und der Hardware ein. Das wirkt sich minimal auf die Performance aus. Alles in allem ist dieser Unterschied allerdings so gering, daß sich die Vorteile von SDL durchaus auszahlen.

Installation von SDL

Bevor man überhaupt mit dem Programmieren anfängt muß man natürlich gewisse Vorbereitungen treffen. Ich will die nötigen Schritte für Windows und für Linux schildern. SDL unterstützt zwar auch andere Betriebssysteme, aber die verwende ich selbst nicht - wenn jemand einen entsprechenden Abschnitt hinzufügen kann wäre ich dankbar.

Installation unter Windows

Unter Windows kann man bei SDL eigentlich gar nicht von einer Installation reden. Holt Euch einfach das Developmentpaket für MSVC++ von der SDL-Webseite (http://www.libsdl.org/ - Download - Development Libraries) und entpackt es irgendwohin. Dieses Paket enthält übrigens auch eine Onlinedokumentation der SDL-Funktionen und -Strukturen im HTML-Format (auf englisch). Wenn Ihr wollt könnt Ihr jetzt noch die SDL.DLL aus dem LIB-Verzeichnis in das \WINDOWS\SYSTEM-Verzeichnis kopieren. Andernfalls müßt Ihr die SDL.DLL nachher in das Verzeichnis kopieren, in das dann auch die kompilierte .EXE-Datei kommt.

Installation unter Linux

Zunächst solltet Ihr überprüfen, ob SDL vorinstalliert ist - auf manchen Distributionen ist das vielleicht der Fall. Dazu öffnet Ihr eine Shell und führt sdl-config aus. Typischerweise ergibt sich folgendes Bild:

prefect@leprechaun:~ > sdl-config --version 1.2.2

Hier ist also die Version 1.2.2 von SDL installiert. Sollte der Befehl 'sdl-config' nicht gefunden werden ist SDL auch nicht installiert.

Wenn SDL nicht installiert ist oder Eure Version veraltet ist müßt Ihr SDL installieren. Dies geht im Normalfall recht schnell:

1. Ladet den Quellcode von http://www.libsdl.org/ runter. Es gibt zwar auch eine binäre Distribution, aber bei Linux sind binäre Pakete so eine suspekte Sache...

2. Entpackt das Quellcodearchiv. Dateien im .tar.gz-Format kann man in einer Shell per

tar xzvf dateiname.tar.gz
entpacken.

3. Der Rest der Installation ist ein typischer autoconf/automake-Prozess:
- Wechselt in das Verzeichnis, in dem sich der Quellcode befindet.
- Führt './configure' aus. Damit wird SDL unter dem Verzeichnis /usr/local/* installiert. Oft empfiehlt es sich, './configure --prefix=/usr' zu verwenden - damit wird SDL später unter /usr/* installiert, und ist damit für Programme leichter zu finden.
- Führt 'make && make install' in der Shell aus.

Sobald diese Prozesse abgeschlossen sind ist SDL installiert.

Einrichtung der Kompilierumgebung

Nachdem SDL installiert ist könnt Ihr aber nicht sofort loslegen. Der Compiler weiß ja nichts von SDL, und deswegen muß ihm erstmal beigebracht werden, was es mit SDL auf sich hat. Die notwendigen Schritte sind natürlich wiederum bei jedem Compiler anders.

Projekte unter MSVC++

A. Projekt einrichten
1) Startet MSVC++ und geht auf den Menüpunkt Datei->Neu...
2) Wählt dann aus dem Projekte-Tabulator die Option "Win32-Konsolenanwendung" aus, gebt den Verzeichnis- und Projektnamen ein und klickt auf "Ok".
3) Im nächsten Dialogfenster wählt Ihr "Ein leeres Projekt" aus und klickt auf "Fertigstellen".
4) Bestätigt das nächste Dialogfenster einfach mit "Ok".

Nun habt Ihr bereits ein einfaches, leeres Projekt. Jetzt müßt Ihr erstmal die SDL-Einstellungen vornehmen.

B. SDL-Bibliothek einbinden
1) Wählt im Arbeitsbereich (dieses eingebettete Fenster im linken Teil der IDE) den Tabulator "Dateien" aus. Rechtsklickt auf " Dateien" und wählt "Dateien zu Projekt hinzufügen..." aus.
2) Wechselt in dem jetzt erscheinenden Dialogfenster, in das LIB-Verzeichnis Eurer SDL-Installation. Wählt als Dateityp "Bibliothekdateien (.lib)" aus. Es sollten zwei Dateien (SDL.lib und SDLmain.lib) erscheinen.
3) Markiert "SDL.lib" und klickt auf "Ok".
Die Bibliothek sollte jetzt im Arbeitsbereich erscheinen.

Als nächstes muß dem Compiler gesagt werden, wo die SDL-Headerdateien zu finden sind.

C. Include-Verzeichnis hinzufügen
1) Wählt die Menüoption Extras->Optionen.. aus und wechselt auf den Tabulator "Verzeichnisse".
2) Wählt Verzeichnisse anzeigen für: "Include-Dateien" aus, falls dies nicht schon der Fall ist.
3) Fügt im großen Feld das INCLUDE-Verzeichnis Eurer SDL-Installation hinzu (einfach per Doppelklick auf das leere Rechteck).

Jetzt könnt Ihr das Projekt ganz normal bearbeiten.

Hinweis: Falls Ihr die SDL.DLL nicht ins \WINDOWS\SYSTEM-Verzeichnis kopiert habt müßt Ihr die SDL.DLL später in das Temporärverzeichnis kopieren, indem sich die kompilierte .EXE-Datei befindet (z.B. sdl-tut\tut1\Debug).

Projekte mit GCC (auch MingW32)

Wenn Ihr mit gcc arbeitet, solltet Ihr Makefiles verwenden, da SDL-Programme eventuell mehrere Optionen benötigen, um richtig kompiliert zu werden. Ich zeige hier einmal die Minimalisten-Makefile, die in den ersten Tutorials verwendet wird.
Für größere Projekte müssen natürlich ausgefeiltere Mechanismen geschaffen werden. Ideal wäre die Verwendung von Tools wie autoconf/automake.

Eine sehr einfache Makefile sieht typischerweise so aus:

tut1: tut1.c gcc -Wall -o tut1 tut1.c

Diese Makefile sagt dem make-Programm folgendes:
- immer, wenn tut1.c verändert wurde muß tut1 neu erstellt werden
- um tut1 zu erstellen, soll 'gcc -Wall -o tut1 tut1.c' aufgerufen werden

Die Compileroption '-Wall' weißt gcc an, bei jeder Kleinigkeit eine Warnung auszugeben. Das mag zwar nerven, auf Dauer erspart es einem aber manche mühsame Fehlersuche, denn Fehlerquellen werden früher entdeckt.

Nun muß man nur noch in einer Shell 'make' eingeben, und das make-Programm überprüft automatisch, ob irgend etwas neu kompiliert werden muß - und tut dies dann auch falls nötig. Dadurch erspart man sich das lästige Tippen der immer gleichen Befehle.

Leider reicht diese minimalistische Makefile nicht für SDL-Programme - SDL benötigt nämlich zusätzliche Include- bzw. Headerdateien und Bibliotheken. Hier hilft uns aber das nützliche Programm 'sdl-config' weiter. Dieses Programm informiert uns über die zusätzlichen Compileroptionen, die benötigt werden, um ein SDL-Programm zu kompilieren. Führt dazu in einer Shell folgendes aus:

prefect@leprechaun:~ > sdl-config --libs
 -L/usr/lib -Wl,-rpath,/usr/lib -lSDL -lpthread
prefect@leprechaun:~ > sdl-config --cflags
-I/usr/include/SDL -D_REENTRANT

Entsprechend erweitern wir unsere minimalistische Makefile - sie sieht nun wie folgt aus:

tut1: tut1.c
             gcc -L/usr/lib -Wl,-rpath,/usr/lib -lSDL -lpthread -I/usr/include/SDL \
                -D_REENTRANT -o tut1 tut1.c

Hier habe ich einfach die Ausgabe von 'sdl-config' zum Compileraufruf hinzugefügt. Bei Euch gibt 'sdl-config' vielleicht andere Optionen aus - in dem Fall müßt Ihr natürlich auch die Makefile anpassen.

Der Backslash '\' funktioniert in Makefiles übrigens genauso wie in C-Quellcode - er sorgt dafür, daß das make-Programm die darauffolgende Zeile direkt an die vorhergehende anfügt. Nach dem '\' darf kein weiteres Zeichen mehr stehen.

So - die Makefile ist fertig. Jetzt fehlt nur noch das eigentliche Programm ;)

Hallo Welt

Im Folgenden werde ich ein winziges Programm vorstellen, das lediglich SDL initialisiert und dann auf einen Tastendruck wartet, nur um sich selbst zu beenden.

#include <STDLIB.H>
#include <STDIO.H>
#include <SDL.H>

Hier werden die obligatorischen Header eingebunden. SDL.h selbst lädt alle anderen Header, die Teil von SDL sind. SDL.h ist also die einzige Headerdatei, die Ihr in Eurem Programm direkt einfügen müßt.

#ifdef _WIN32
#undef main
#endif
int main()
{
    SDL_Surface *screen; /* screen und running werden später verwendet */
    int running;

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

    atexit(SDL_Quit);

Die drei Präprozessorbefehle sind zumindest bei mir nötig, um SDL-Programme auf Windows erfolgreich zu kompilieren, da der Windows-Build Probleme aufweist.
Solltet Ihr eine neuere Version von SDL haben, könntet Ihr probehalber die Präprozessorbefehle entfernen und dafür die Bibliothek SDLmain.lib zum Projekt hinzufügen.
Da Windows für mich lediglich eine Nebenplattform ist kann ich das Problem nur schlecht weiter analysieren, und ich wäre für Anregungen dankbar.

Der Befehl SDL_Init() muß in jedem SDL-Programm vor allen anderen SDL-Funktionen aufgerufen werden.

Der einzige Parameter gibt an, welche Teile von SDL verwendet werden. Für dieses Beispiel brauchen wir nur den Graphikteil - der sogenannte Ereignisteil der sich um Tastatur und Maus kümmert wird immer automatisch mitinitialisiert.

Andere Werte, die man per ODER-Verknüpfung angeben kann, sind: SDL_INIT_TIMER, SDL_INIT_AUDIO, SDL_INIT_CDROM, SDL_INIT_JOYSTICK

Das Gegenstück zu SDL_Init() ist SDL_Quit(). SDL_Quit() muß immer am Ende eines Programms aufgerufen werden. Ansonsten kann es z.B. vorkommen, daß der Bildschirmmodus nicht richtig zurückgesetzt wird. Es könnten auch Memoryleaks entstehen, da Speicher nicht ordnungsgemäß freigegeben wird.

Natürlich könntet Ihr SDL_Quit() auch immer manuell aufrufen wenn sich das Programm beendet. Dann wäre es aber notwendig, bei jedem Aufruf von exit() auch SDL_Quit() aufzurufen, und das wird mit der Zeit unübersichtlich.

Deshalb empfiehlt es sich, SDL_Quit mit atexit() zu registrieren. atexit() ist eine simple Funktion der Standard-C-Bibliothek und sorgt dafür, daß die übergebene Funktion beim Programmende aufgerufen wird - ganz egal, ob sich das Programm ordnungsgemäß am Ende von main() oder über exit() verabschiedet. Man kann mit atexit() beliebig viele solcher "Aufräumfunktionen" registrieren.

     screen = SDL_SetVideoMode(640, 480, 0, 0);
    if (!screen) {
        fprintf(stderr, "Konnte Bildschirmmodus nicht setzen: %s\n",
            SDL_GetError());
        exit(1);
    }

Hier wird mit SDL_SetVideoMode() ein einfacher Bildschirmmodus gesetzt. Die ersten beiden Parameter geben die Bildschirmauflösung (640x480) an. Der dritte Parameter steht für die Farbtiefe. Wenn hier eine 0 übergeben wird verwendet SDL die momentane oder beste Farbtiefe. Der letzte Parameter ist der flags-Parameter, mit dem eine Menge Optionen verändert werden können.

In diesem Beispiel wechselt SDL nicht in den Vollbildmodus. Dazu müßte das Flag SDL_FULLSCREEN im vierten Parameter übergeben werden. Andere Flags können z.B. verwendet werden, um Double-Buffering anzuschalten. Auf die wichtigsten dieser Flags werde ich in späteren Kapiteln auf jeden Fall eingehen.

Nun folgt das, was sich später einmal zum wichtigsten Teil des Programms mausern wird:

    running = 1;
    while(running) {
        SDL_Event event;

        while(SDL_PollEvent(&event)) {
            switch(event.type) {
            case SDL_KEYDOWN:
                running = 0;
                break;
            case SDL_QUIT:
                running = 0;
                break;
            }
        }
    }

Das ist die Programmschleife. Diese Schleife ruft SDL_PollEvent() auf, um verschiedene Ereignisse abzufragen. Alle möglichen Dinge können Ereignisse hervorrufen: Mausbewegungen und -klicks, Tastatureingaben, etc...

Dieses Programm reagiert - wie bereits gesagt - nur auf Tastendrücke (SDL_KEYDOWN) und auf die Aufforderung, sich zu beenden (SDL_QUIT). Ein SDL_QUIT-Ereignis wird z.B. erzeugt, wenn der Nutzer auf das Schließen-Icon des Fensters klickt. In beiden Fällen beendet sich das Programm.

Es gibt noch viel mehr Ereignistypen, und auf die wichtigsten werde ich noch zu sprechen kommen.

SDL_PollEvent() kehrt immer sofort zum Aufrufer zurück, auch wenn gar kein Ereignis vorhanden ist. Deshalb ist diese Programmschleife eine Busy-Loop, die den Prozessor immer voll auslastet, es sei denn es laufen noch andere Programme. Manchmal ist das nicht wünschenswert, und für solche Fälle gibt es die Funktion SDL_WaitEvent(). Mehr Informationen dazu gibt's in der SDL-Dokumentation. Bei Spielen ist aber die Prozessorauslastung unwichtig, und wir wollen ja möglichst hohe Framerates erreichen. Deswegen habe ich auch hier SDL_PollEvent() verwendet.

Es mag auf den ersten Blick auch nicht ersichtlich sein, warum zwei ineinander verschachtelte while()-Schleifen verwendet werden. Das liegt daran, daß die äußere Schleife später genau einmal pro Frame durchlaufen wird. Es ist aber durchaus möglich, daß mehrere Ereignisse innerhalb eines Frames auftreten. Würde man SDL_PollEvent() nicht in eine Schleife stecken könnte die Ereigniswarteschlange irgendwann voll werden und überlaufen.

        return 0;
}

So, das war's. Den Quellcode zu diesem kleinen Programm gibt's unten auch zum Download.

Ein Wort zu Bildschirmmodi

Leider haben einige Plattformen des öfteren Probleme, wenn es um Bildschirmmodi geht. Besonders X-Windows wechselt die Auflösung und Farbtiefe nur recht ungern. Es kann durchaus passieren, daß SDL z.B. nicht in einen 256-Farben-Vollbildmodus wechseln kann, obwohl die Hardware selbst damit eigentlich gar keine Probleme haben müßte.

Versucht also möglichst immer, für den Spieler einen Rückzugsweg bereitzuhalten (und wenn's nur Fenstermodus ist). In diesem Tutorial werde ich - sofern möglich - immer die Standardfarbtiefe verwenden. Manchmal - z.B. wenn es um Palette- Funktionen geht - ist das natürlich nicht möglich. SDL_ListModes() kann verwendet werden, um die vorhandenen Videomodi im Voraus aufzulisten.

Ende gut, alles gut!

Damit ist das erste Kapitel zu Ende. In den nächsten Kapiteln werde ich Schritt für Schritt neue Funktionen von SDL vorstellen, bis irgendwann einmal alle Teile von SDL ausführlich erklärt sind.
Vielleicht wird ja auch ein kleines Spiel daraus.

Dann war da noch

Der Quellcode zu diesem Tutorial sowie die Makefile und MSVC++-Projektdateien sind zum Download verfügbar (tar gzipped, 2.1kB; zipped, 2.6kB)

Dieses Tutorial ist Copyright (c) 2001 Nicolai 'Prefect' Hähnle. Es darf zu nicht-kommerziellen Zwecken beliebig in ungekürzter und unmodifizierter Form vervielfältigt werden, sofern alle dazugehörigen Dateien, ebenfalls unmodifiziert, mitgeliefert werden.
Der mitgelieferte Sourcecode ist Public Domain.

Ihr könnt mich unter prefect_@gmx.net erreichen. Außerdem lese ich die Coding-Foren von http://www.thewall.de/ und hänge zuviel in #thewall.de auf irc.gamesnet.net rum...


 
Fortsetzung