SDL-Tutorial

Aus Spieleprogrammierer-Wiki
Wechseln zu: Navigation, Suche

Dieses Tutorial soll dem Leser einen ersten Einblick in SDL verschaffen. Behandelt werden einfache Grafik- und Textausgabe, Eingabe und Sound. Dabei wird die Version 1.2 der Bibliothek benutzt.

Inhaltsverzeichnis

Einleitung

Voraussetzungen für das Tutorial

Um das Tutorial erfolgreich mitverfolgen zu können, sollte man ein solides Grundwissen in C/C++ besitzen sowie natürlich einen einsatzbereiten C++-Compiler. Der im Tutorial gezeigte Quellcode wurde mit GCC und Visual C++ getestet.

Was ist SDL?

SDL steht für "Simple DirectMedia Layer" und ist eine portable Open Source-Multimedia-Bibliothek. "Portabel" bedeutet, dass Spiele, die (ausschließlich) auf SDL setzen, ohne Probleme auf verschiedenen Plattformen wie Windows, Linux oder Mac OS kompiliert und ausgeführt werden können. SDL bietet dem Programmierer eine sehr umfangreiche Funktionssammlung für viele Bereiche, die zur Entwicklung von Spielen wichtig sind (Grafik, Sound, Eingabe, Netzwerk, Threading, ...). Die Bibliothek ist in C geschrieben, kann aber natürlich auch unter C++ genutzt werden. Außerdem gibt es noch verschiedene Bindings für andere Sprachen, darunter auch C#, Java und Python. Die Lizenz, unter der SDL 1.2 angeboten wird, ist GNU LGPL 2.

Viele Spiele, darunter auch einige kommerzielle, verwenden SDL (eine umfangreiche Liste findet man hier). Darunter befindet sich auch das beliebte Multi-Plattform-Spiel "Angry Birds".

Wo bekomme ich SDL, und wie installiere ich es?

SDL kannst du auf der offiziellen SDL-Webseite unter http://www.libsdl.org/ herunterladen (Download-Seite). Hier findest du auch zusätzliche Informationen zu SDL. Am wichtigsten ist die Dokumentation zu SDL, die du auf jeden Fall parallel zu diesem Tutorial lesen solltest, da sie die einzelnen Funktionen viel detaillierter beschreibt. Hier soll dir nur ein grober Überblick gegeben werden.

Installation unter Windows

Besuche die SDL-Webseite und lade das Development-Paket herunter. Nach dem Entpacken musst du noch (wie bei jeder Bibliothek) dafür sorgen, dass der Compiler die Header- und Bibliothekdateien von SDL finden kann, indem du dem Compiler die entsprechenden Verzeichnisse mitteilst.

Installation unter Linux

Bei den meisten Linux-Distributionen befinden sich die Development-Pakete für SDL schon im Repository. Diese müssen einfach nur über den Paketmanager installiert werden. Das gleiche gilt auch für die Zusatzbibliotheken, die wir im Verlaufe dieses Tutorials noch benutzen werden. Das sind SDL_image, SDL_mixer und SDL_ttf. Wer diese Methode wählt, kann die Installationsanweisungen im Tutorial ignorieren, denn alles sollte ohne weitere Installation direkt funktionieren. Wenn du die Pakete im Repository nicht findest oder sie veraltet sind, dann kannst du sie von der SDL-Webseite herunterladen und installieren.

SDL einbinden

Um SDL-Funktionen nutzen zu können, muss natürlich die entsprechende Header-Datei eingebunden werden. Diese heißt SDL.h. Die Verzeichnisstrukturen unter Windows und Linux unterscheiden sich ein wenig, darum benötigen wir ein #ifdef:

// SDL-Header einbinden
#ifdef WIN32
#include <SDL.h>
#else
#include <SDL/SDL.h>
#endif

Das alleine reicht natürlich nicht, sondern es muss auch noch die Bibliothekdatei(en) gelinkt werden. Unter Windows heißen sie SDL.lib und SDLmain.lib, unter Linux braucht man nur die SDL.a (Linken mit dem GCC-Kommandozeilenparameter -lSDL). Unter Windows muss auch die SDL.dll für das Betriebssystem auffindbar sein (entweder im Ausführungsverzeichnis, dem Verzeichnis der ausführbaren Datei oder in Windows\System32).

Das erste Fenster

In diesem Abschnitt werden wir ein einfaches Fenster mit SDL erstellen. Das klingt zwar etwas unspektakulär, ist aber normalerweise der erste Schritt in einer SDL-Anwendung.

Erstellen des Fensters

Als erstes müssen wir SDL durch Aufrufen von SDL_Init initialisieren. Dieser Funktion übergibt man einen Parameter, der beschreibt, welche Subsysteme initialisiert werden sollen. In unserem ersten Beispiel ist dies nur SDL_INIT_VIDEO, was für das Grafiksystem steht. Ein Rückgabewert von -1 steht für einen Fehler, dessen lesbare Beschreibung dann mit SDL_GetError abgefragt werden kann. Am Ende des Programms sollte man SDL_Quit aufrufen.

#include <iostream>
 
int main(int argc, char* argv[])
{
    // SDL mit dem Grafiksystem initialisieren.
    if(SDL_Init(SDL_INIT_VIDEO) == -1)
    {
        // Ups, das hat nicht funktioniert!
        // Wir geben die Fehlermeldung aus.
        std::cerr << "Konnte SDL nicht initialisieren! Fehler: " << SDL_GetError() << std::endl;
        return -1;
    }

Als Nächstes werden wir ein Fenster erstellen, damit wir einen Bereich haben, in dem wir beispielsweise Grafiken darstellen können. Dies geschieht durch SDL_SetVideoMode. Der Rückgabewert ist ein Zeiger auf ein SDL_Surface-Objekt, das einen zweidimensionalen Zeichenbereich darstellt. Hier können wir später grafische Ausgaben machen.

// Hier erzeugen wir ein Fenster der Größe 800x600 Pixel mit 32-Bit Farbtiefe und Double Buffer.
// Wenn Vollbildmodus erwünscht ist, fügen wir noch das Flag SDL_FULLSCREEN mittels |-Operator hinzu.
SDL_Surface* screen = SDL_SetVideoMode(800, 600, 32, SDL_DOUBLEBUF);
if(!screen)
{
    // Fehler! Fehlermeldung ausgeben und Programm beenden.
    std::cerr << "Konnte SDL-Fenster nicht erzeugen! Fehler: " << SDL_GetError() << std::endl;
    return -1;
}
 
// Nun setzen wir noch den Titel des Fensters.
SDL_WM_SetCaption("SDL-Beispielprogramm", "SDL-Beispielprogramm");

Der Funktion SDL_WM_SetCaption erwartet als ersten Parameter den Fenster-Titel. Der zweite Parameter ermöglicht es, einen anderen Text zu verwenden, wenn das Fenster minimiert ist und nur in der Task-Leiste zu sehen ist. Damit haben wir ein Fenster erstellt.

Die Event-Schleife

Würde man das bisher gezeigte Programm laufen lassen, so würde das Fenster sofort geschlossen und das Programm beendet. Was noch fehlt, ist die so genannte Event-Schleife oder der Main-Loop. Diese Schleife läuft so lange, wie die Anwendung aktiv ist. Sie verarbeitet Ereignisse wie Benutzereingaben und würde im Falle eines Spiels dafür sorgen, dass das Spiel aktualisiert und grafisch dargestellt wird. Die Event-Schleife kann man minimalistisch wie folgt realisieren:

bool run = true;
 
// die Event-Schleife
while(run)
{
    // Wir holen uns so lange neue Ereignisse, bis es keine mehr gibt.
    SDL_Event event;
    while(SDL_PollEvent(&event))
    {
        // Was ist passiert?
        switch(event.type)
        {
        case SDL_QUIT:
            // Das Programm soll beendet werden.
            // Wir merken uns das und brechen beim nächsten Mal die Schleife ab.
            run = false;
            break;
        }
    }
 
    // Hier würden bei einem Spiel die eigentliche Spiellogik berechnet
    // und Grafiken angezeigt werden.
}
 
// SDL herunterfahren
SDL_Quit();

Alle SDL-Events werden durch ein Objekt vom Typ SDL_Event dargestellt. Dessen Feld type gibt die Art des Ereignisses an. Mit SDL_PollEvent kann man das nächste zu verarbeitende Ereignis abfragen, falls es eins gibt (das erkennen wir am Rückgabewert). Das einzige Ereignis, auf das wir im obigen Code reagieren, ist SDL_QUIT. Dieses Ereignis wird gesendet, wenn der Benutzer das Fenster schließt. Wir reagieren darauf, indem wir die Event-Schleife abbrechen und das Programm dadurch beenden. Dabei rufen wir noch SDL_Quit auf, um SDL herunterzufahren.

Damit ist unser erstes SDL-Programm fertig. Auf der Linux-Kommandozeile benutzen wir folgende Kommandos, um das Programm zu kompilieren und auszuführen (vorausgesetzt, der Quellcode wurde als SDL-Tutorial.cpp abgespeichert):

g++ SDL-Tutorial.cpp -o SDL-Tutorial -lSDL
./SDL-Tutorial

Ein Bild mit der Tastatur bewegen

Nachdem wir nun schon ein SDL-Fenster erstellt haben, werden wir in diesem Abschnitt einen kleinen Pinguin anzeigen und diesen mit den Pfeiltasten durchs Fenster fliegen lassen. Außerdem soll das Programm mit einer weiteren Taste beendet werden, damit sie auch im Vollbildmodus ohne Probleme benutzt werden kann.

SDL_image

Da SDL von Haus aus nur BMP-Dateien laden kann (dieses Bilddateiformat beherrscht keine besonders gute Kompression und auch keine Transparenz), werden wir eine weitere Bibliothek namens SDL_image verwenden. SDL_image findet man unter http://www.libsdl.org/projects/SDL_image/. Hier einfach das mit "-devel" gekennzeichnete Paket herunterladen. Dieses muss genau wie SDL selbst entpackt werden, außerdem muss der Compiler die Header-Datei SDL_image.h und die Bibliothekdatei SDL_image.lib bzw. SDL_image.a finden können (unter Linux linken mit -lSDL_image). Unter Windows muss zudem die SDL_image.dll auffindbar sein sowie einige weitere DLLs für die einzelnen Bilddateiformate (die DLLs befinden sich im lib-Unterverzeichnis des Pakets).

// SDL- und SDL_image-Header einbinden
#ifdef WIN32
#include <SDL.h>
#include <SDL_image.h>
#else
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#endif

Bilder laden

SDL Example Tux.png

Wir werden nun ein Bild laden, das später mit den Pfeiltasten über den Bildschirm bewegt werden soll. Als Bild kannst du den Pinguin rechts nehmen. Speichere die Datei als SDL_Example_Tux.png im Verzeichnis deines Programms. Bilder lädt man mit Hilfe der Funktion IMG_Load, der man den Dateinamen des Bilds übergibt. Zurück bekommt genau wie bei SDL_SetVideoMode einen Zeiger auf ein SDL_Surface-Objekt oder einen Nullzeiger, falls das Laden nicht funktioniert hat. Die Funktion unterstützt unter Anderem die Dateiformate JPEG und PNG. Folgenden Code können wir hinter die SDL-Initialisierung schreiben:

// [nach der SDL-Initialisierung] Hier laden wir unser Bild mit SDL_image.
SDL_Surface* image = IMG_Load("SDL_Example_Tux.png");
if(!image)
{
    // Fehler!
    std::cerr << "Konnte das Bild nicht laden! Fehler: " << IMG_GetError() << std::endl;
    return -1;
}

Ein geladenes Bild kann (und sollte) mit SDL_FreeSurface wieder freigegeben werden, wenn es nicht mehr benötigt wird.

Bilder anzeigen

Nun soll das Bild auf dem Bildschirm angezeigt werden. Das Surface-Objekt, das den Bildschirm bzw. das Fenster repräsentiert, haben wir vom SDL_SetVideoMode-Aufruf erhalten und in der Variablen screen gespeichert. Der richtige Ort für das Zeichnen ist die Event-Schleife.

Zunächst füllen wir mit SDL_FillRect den Bildschirm mit Weiß, damit unser Bild später beim Bewegen keine "Spuren" hinterlässt. Diese Funktion erwartet ein Surface-Objekt, einen Zeiger auf ein SDL_Rect-Objekt, das den zu füllenden rechteckigen Bereich durch die Koordinaten der linken oberen Ecke x und y sowie die Breite w und die Höhe h beschreibt (ein Nullzeiger bedeutet, dass das ganze Bild gefüllt werden soll), und zuletzt eine Farbangabe. Mit SDL_MapRGB können wir eine RGB-Farbangabe (Rot-, Grün- und Blau-Anteil, jeweils von 0 bis 255) mischen und in das korrekte Farbformat für ein gegebenes Bild umwandeln.

Anschließend kopieren wir das Bild image auf den Bildschirm screen. Das funktioniert mit SDL_BlitSurface. Diese Funktion erwartet folgende Parameter:

SDL_Surface* src
Das Bild, das gezeichnet werden soll.
SDL_Rect* srcrect
Angabe, welcher Teil des Bilds gezeichnet werden soll. Ein Nullzeiger bedeutet, dass das gesamte Bild gezeichnet wird.
SDL_Surface* dst
Das Bild, auf das gezeichnet werden soll.
SDL_Rect* dstrect
Angabe, an welcher Stelle das Bild gezeichnet werden soll. Nur die Felder x und y des Rechtecks werden beachtet. Das zu zeichnende Bild src wird mit seiner linken oberen Ecke an dieser Stelle gezeichnet.

Wenn wir fertig mit dem Zeichnen sind, rufen wir noch SDL_Flip(screen); auf, damit unsere Änderungen sichtbar werden. Durch die Initialisierung von SDL mit einem Double Buffer werden alle Zeichenoperationen nämlich erst einmal in einem unsichtbaren Speicherbereich ausgeführt. Ansonsten würde man ein ständiges Flackern bemerken.

Screenshot des bewegbaren Pinguins in einem SDL-Fenster.
// [in der Event-Schleife] Zuerst übermalen wir den Bildschirm mit Blau.
SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 0, 0, 255));
 
// Hier zeichnen wir unser Bild an der Position (100, 50).
SDL_Rect imagePosition;
imagePosition.x = 100;
imagePosition.y = 50;
SDL_BlitSurface(image, 0, screen, &imagePosition);
 
// Dann aktualisieren wir den Bildschirm, damit wir auch etwas sehen.
SDL_Flip(screen);

Wir können nun schon das Programm laufen lassen und sehen einen Pinguin links oben auf dem Bildschirm. Beachte, dass die Transparenz (Alphakanal) in der PNG-Grafik von SDL automatisch berücksichtigt wird. Ansonsten, oder auch bei der Verwendung einer JPEG-Grafik, hätte der Pinguin einen unschönen rechteckigen weißen Hintergrund.

Tastatur-Eingabe

In unserer Event-Schleife haben wir bislang nur auf ein einziges Ereignis reagiert, nämlich auf SDL_QUIT. Es gibt aber noch weitere interessante Ereignisse, auf die wir reagieren können. Wenn der Benutzer eine Taste auf der Tastatur drückt, wird das Ereignis SDL_KEYDOWN ausgelöst. Dies geschieht genau in dem Moment, wo die Taste heruntergedrückt wird. Solange sie gedrückt bleibt, werden keine weiteren SDL_KEYDOWN-Ereignisse ausgelöst. Erst dann, wenn eine Taste wieder losgelassen wird, erhalten wir das Ereignis SDL_KEYUP. Über event.key.keysym.sym (ja, ziemlich lang) finden wir heraus, welche Taste es war. Unter SDL gibt es eine Reihe von vordefinierten Werten, die mit SDLK_ beginnen, und zwar für jede Taste auf der Tastatur einen. SDLK_LEFT, SDLK_RIGHT, SDLK_UP und SDLK_DOWN sind die Werte für die Pfeiltasten. In der SDL-Dokumentation gibt es eine Tabelle aller Tasten-Codes.

Es ist eine gute Idee, sich ein globales bool-Array anzulegen, in dem man sich für jede Taste merkt, ob sie gerade gedrückt ist. Es gibt insgesamt SDLK_LAST viele Tasten-Codes, also muss das Array so viele Einträge haben. Wenn wir ein SDL_KEYDOWN-Ereignis verarbeiten, setzen wir den Eintrag für die jeweilige Taste auf true. Bei SDL_KEYUP setzen wir ihn auf false.

// [vor der Event-Schleife] In diesem Array merken wir uns, welche Tasten gerade gedrückt sind.
bool keyPressed[SDLK_LAST];
memset(keyPressed, 0, sizeof(keyPressed));
 
// [in der Event-Schleife bei der Verarbeitung der Ereignisse]
switch(event.type)
{
// ...
case SDL_KEYDOWN:
    keyPressed[event.key.keysym.sym] = true;
    break;
case SDL_KEYUP:
    keyPressed[event.key.keysym.sym] = false;
    break;
}

Verschieben des Bilds mit der Tastatur

Zuvor haben wir die Position unseres Pinguins einfach immer auf (100, 50) gesetzt. Wenn man das Bild mit der Tastatur verschieben können soll, dann muss die Variable imagePosition erst einmal außerhalb der Event-Schleife deklariert werden, denn sonst würde sie in jedem Durchlauf wieder neu erzeugt. Dann fragen wir ab, welche der Pfeiltasten gedrückt sind, und bewegen das Bild entsprechend. Dabei achten wir darauf, dass das Bild den Bildschirm nicht verlässt. Außerdem reagieren wir auf die Escape-Taste mit dem Beenden des Programms:

// [vor der Event-Schleife] Position des Bilds initialisieren.
SDL_Rect imagePosition;
imagePosition.x = 100;
imagePosition.y = 50;
 
// [in der Event-Schleife nach der Verarbeitung der Ereignisse] Bild je nach gedrückten Tasten bewegen.
if(keyPressed[SDLK_LEFT]    && imagePosition.x > 0)                     --imagePosition.x;  // Bild nach links bewegen.
if(keyPressed[SDLK_RIGHT]   && imagePosition.x + image->w < screen->w)  ++imagePosition.x;  // Bild nach rechts bewegen.
if(keyPressed[SDLK_UP]      && imagePosition.y > 0)                     --imagePosition.y;  // Bild nach oben bewegen.
if(keyPressed[SDLK_DOWN]    && imagePosition.y + image->h < screen->h)  ++imagePosition.y;  // Bild nach unten bewegen.
if(keyPressed[SDLK_ESCAPE])                                             run = false;        // Programm beenden.
 
// Bildschirm mit Blau füllen und dann das Bild zeichnen.
SDL_FillRect(screen, 0, SDL_MapRGB(screen->format, 0, 0, 255));
SDL_BlitSurface(image, 0, screen, &imagePosition);

Die jeweils zweite Bedingung in den if-Abfragen verhindert, dass das Bild aus dem sichtbaren Bereich herausbewegt wird. Dazu wird auf die Felder SDL_Surface::w und SDL_Surface::h zugegriffen, welche die Breite und Höhe eines Bilds enthalten.

Die Geschwindigkeit, mit der sich das Bild bewegt, hängt davon ab, wie schnell dein Rechner die Grafik anzeigen kann. Es ist absolut kein guter Stil, ein Bild so zu bewegen, wie wir es in diesem Beispielprogramm tun. Normalerweise würde man die Bewegungsgeschwindigkeit an die Bildrate koppeln. Dies ist jedoch nicht SDL-spezifisch und wird deshalb hier auch nicht behandelt. SDL bietet jedoch Funktionen zum Messen der Zeit.

Sound und Musik

Wir können inzwischen ein Fenster erzeugen, Bilder anzeigen, Bilder laden und diese bewegen. Ein wichtiger Aspekt für Spiele fehlt noch, nämlich Sound und Musik.

SDL_mixer und Audio-Initialisierung

Um leicht und schnell Sound und Musik abspielen zu können, werden wir eine weitere Bibliothek namens SDL_mixer verwenden. Sie erlaubt es uns, auf einfache Weise Sounds und Musik in gängigen Dateiformaten zu laden und abzuspielen. Die Bibliothek kann unter http://www.libsdl.org/projects/SDL_mixer/ heruntergeladen werden. Die Installation und Einbindung erfolgt analog zu SDL_image. Mit der zusätzlichen Header-Datei sieht der Anfang unseres Programms dann wie folgt aus:

// SDL-, SDL_image- und SDL_mixer-Header einbinden
#ifdef WIN32
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_mixer.h>
#else
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <SDL/SDL_mixer.h>
#endif

Um SDL_mixer benutzen zu können, müssen wir SDL mit Audiounterstützung initialisieren. Dazu übergeben wir der SDL_Init-Funktion ein weiteres Flag, nämlich SDL_INIT_AUDIO. Das sieht dann so aus:

// SDL mit dem Grafik- und Audiosystem initialisieren.
if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) == -1)
{
    // Ups, das hat nicht funktioniert!
    // Wir geben die Fehlermeldung aus.
    std::cerr << "Konnte SDL nicht initialisieren! Fehler: " << SDL_GetError() << std::endl;
    return -1;
}

Als Nächstes müssen wir SDL_mixer mittels Mix_OpenAudio starten. Dazu sind einige Parameter erforderlich, die im Wesentlichen die Qualität des Klangs festlegen. Dazu zählen die Anzahl der Kanäle (1 = Mono, 2 = Stereo, ...), die Anzahl der Bits für ein Audio-Sample (normalerweise werden 16 Bits genutzt) und die Abtastfrequenz (normalerweise 44100 Hz oder 48000 Hz). Zuletzt folgt noch die Größe eines fertig gemischten Audioblocks. In der Dokumentation wird eine Größe von 4096 Bytes empfohlen, und dieser Empfehlung folgen wir:

// [nach der SDL-Initialisierung] SDL_mixer starten.
const int samplingFrequency = 44100;    // 44100 Hz Abtastfrequenz
Uint16 audioFormat = AUDIO_S16SYS;      // 16 Bits pro Sample
const int numChannels = 2;              // 2 Kanäle = Stereo
const int chunkSize = 4096;             // ein guter Wert ...
if(Mix_OpenAudio(samplingFrequency, audioFormat, numChannels, chunkSize) == -1)
{
    std::cerr << "Konnte SDL_mixer nicht starten! Fehler: " << Mix_GetError() << std::endl;
    return -1;
}

Sounds und Musik laden

Nun werden wir eine WAV-Datei und eine OGG-Datei laden (Download der Beispieldateien hier und hier). WAV-Dateien sind normalerweise unkomprimiert und eignen sich deshalb eher für kurze Soundeffekte, während OGG-Dateien verlustbehaftet komprimiert sind und meistens für längere Musikstücke benutzt werden. Die WAV-Datei laden wir mittels Mix_LoadWAV und erhalten einen Zeiger auf ein Mix_Chunk-Objekt zurück, das den geladenen Sound repräsentiert:

// WAV-Datei laden.
Mix_Chunk* sound = Mix_LoadWAV("SDL_Example_Sound.wav");
if(!sound)
{
    std::cerr << "Konnte WAV-Datei nicht laden! Fehler: " << Mix_GetError() << std::endl;
    return -1;
}

Als nächstes werden wir noch eine OGG-Datei laden, um diese als Hintergrundmusik zu verwenden. Diesmal rufen wir Mix_LoadMUS auf, die uns einen Mix_Music-Zeiger liefert. Diese Funktion ist dafür gedacht, längere Musikstücke zu laden. Musik wird anders gehandhabt als Soundeffekte, weil sie stückweise aus der Datei gelesen und dekodiert wird (ein komplettes Musikstück würde unnötig viel Platz im Arbeitsspeicher benötigen). Die folgenden Dateiformate werden unterstützt: WAV, MOD, MIDI, OGG, MP3 und FLAC.

// OGG-Datei als Musik laden.
Mix_Music* music = Mix_LoadMUS("SDL_Example_Music.ogg");
if(!music)
{
    std::cerr << "Konnte OGG-Datei nicht laden! Fehler: " << Mix_GetError() << std::endl;
    return -1;
}

Jetzt haben wir eine WAV-Datei und eine OGG-Datei geladen, die wir abspielen können.

Abspielen von Sound und Musik

Die Musik starten wir direkt vor der Event-Schleife. Dazu reicht ein Aufruf von Mix_PlayMusic. Der Funktion übergeben wir zuerst das abzuspielende Musikstück und dann die Anzahl der Wiederholungen (-1 spielt die Musik in einer Endlosschleife, 0 spielt sie genau einmal, 1 spielt sie zweimal, ...).

// [vor der Event-Schleife] Musik abspielen.
Mix_PlayMusic(music, -1);

Der Sound soll abgespielt werden, wenn der Benutzer die Leertaste drückt. Dazu erweitern wir die Verarbeitung des entsprechenden SDL_KEYDOWN-Ereignisses um einen Aufruf von Mix_PlayChannel, wenn es sich bei der gedrückten Taste um die Leertaste handelt. Dieser Funktion übergeben wir als ersten Parameter -1 (siehe Dokumentation), dann den abzuspielenden Sound und schließlich die Anzahl der Wiederholungen. Der Rückgabewert gibt den Mix-Kanal an, auf dem der Sound abgespielt wird. Normalerweise würde man sich diesen Wert merken, um den Sound später (während er abgespielt wird) noch pausieren/fortsetzen zu können oder um seine Lautstärke zu regeln.

// [bei der Verarbeitung der Ereignisse]
case SDL_KEYDOWN:
    keyPressed[event.key.keysym.sym] = true;
    if(event.key.keysym.sym == SDLK_SPACE)
    {
        // Sound abspielen
        Mix_PlayChannel(-1, sound, 0);
    }
    break;

Einige weitere hilfreiche Funktionen, die man sich noch anschauen sollte:

Text anzeigen

In diesem Abschnitt werden wir lernen, wie man Texte mit SDL darstellen kann. Das ist hilfreich, um beispielsweise die Punktzahl in einem Spiel anzuzeigen.

SDL_ttf und Initialisierung

Wir werden nun eine weitere Zusatzbibliothek namens SDL_ttf benutzen, um Texte anzuzeigen. Wie der Name der Bibliothek bereits verrät, ermöglicht sie das Laden von TTF-Schriftarten (True Type Font). Den Download findet man unter http://www.libsdl.org/projects/SDL_ttf/, und auch hier gilt in Bezug auf die Installation wieder das gleiche wie bei den anderen Bibliotheken.

Es muss die Datei SDL_ttf.h eingebunden werden:

// SDL-, SDL_image-, SDL_mixer- und SDL_ttf-Header einbinden
#ifdef WIN32
#include <SDL.h>
#include <SDL_image.h>
#include <SDL_mixer.h>
#include <SDL_ttf.h>
#else
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <SDL/SDL_mixer.h>
#include <SDL/SDL_ttf.h>
#endif

Zur Initialisierung der TTF-Bibliothek rufen wir TTF_Init auf:

// SDL_ttf initialisieren.
if(TTF_Init() == -1)
{
    std::cerr << "Konnte SDL_ttf nicht initialisieren! Fehler: " << TTF_GetError() << std::endl;
    return -1;
}

Am Ende des Programms können wir noch TTF_Quit aufrufen.

Laden einer Schriftart

Als Nächstes müssen wir eine Schriftart laden. In unserem Beispielprogramm wird das die Schriftart FreeSans.ttf sein, die man hier herunterladen kann. Eine Schriftart wird mit TTF_OpenFont geladen und, wenn sie nicht mehr benötigt wird, mit TTF_CloseFont wieder entladen. Der erste Parameter ist der Name der TTF-Datei. Der zweite Parameter ist die Schriftgröße in Punkt. Der Rückgabewert ist ein Zeiger auf ein TTF_Font-Objekt, das wir später natürlich noch benötigen.

// Schriftart "FreeSans.ttf" laden.
TTF_Font* font = TTF_OpenFont("FreeSans.ttf", 30);
if(!font)
{
    std::cerr << "Konnte Schriftart nicht laden! Fehler: " << TTF_GetError() << std::endl;
    return -1;
}

Text in ein Bild zeichnen

Nun werden wir mit der geladenen Schriftart einen Text schreiben. Das Darstellen von Text mit SDL_ttf funktioniert nach folgendem Prinzip: Der Text wird zunächst in ein separates Bild geschrieben, und dieses Bild wird dann (genau wie der Pinguin) auf dem Bildschirm sichtbar gemacht. Der Grund dafür ist, dass der erste Schritt wesentlich rechenintensiver ist als der zweite. Man muss ihn nur dann ausführen, wenn sich der darzustellende Text geändert hat. Wenn der Text beispielsweise eine Punktzahl darstellt, dann muss er nur dann komplett neu generiert werden, wenn die Punktzahl verändert wurde.

Wir rufen TTF_RenderTextBlended auf, um das Bild mit dem Text zu generieren. Der erste Parameter ist die Schriftart, die wir zuvor mit TTF_OpenFont geladen haben. Der zweite Parameter ist der eigentliche Text, und zwar in ISO-8859-1 ("Latin-1") kodiert. Dieser Zeichensatz enthält auch die deutschen Umlaute. Wer Unicode unterstützen möchte, um beispielsweise auch chinesische Zeichen darstellen zu können, der sei auf die SDL_ttf-Dokumentation verwiesen. Der dritte Parameter von TTF_RenderTextBlended ist die Farbe des Texts in Form eines SDL_Color-Objekts, das die Attribute r, g und b besitzt. Wenn alles funktioniert hat, liefert die Funktion uns als Rückgabe einen SDL_Surface-Zeiger auf das Bild, das den geschriebenen Text enthält. Es liegt in der Verantwortung des Programmierers, dass dieses Bild mit SDL_FreeSurface wieder freigegeben wird, wenn es nicht mehr benötigt wird.

// Einen Text zeichnen.
SDL_Color textColor = {255, 127, 31};
SDL_Surface* text = TTF_RenderText_Blended(font, "Willkommen bei SDL! Söndérzeîchèn fünktíönìérèn äüch.", textColor);
Screenshot des Beispielprogramms mit einem Schriftzug.

Um den Text nun darzustellen, rufen wir, genau wie zuvor schon beim Zeichnen des Pinguins, die Funktion SDL_BlitSurface auf. Wenn der Text den Pinguin verdecken soll, dann muss dies nach dem Zeichnen des Pinguins geschehen.

// [nach dem Zeichnen des Pinguins] Den Text ausgeben.
SDL_Rect textPosition;
textPosition.x = 10;
textPosition.y = 5;
SDL_BlitSurface(text, 0, screen, &textPosition);

Herunterladen des kompletten Beispielprogramms

Das komplette Beispielprogramm kannst du hier herunterladen. Das Archiv enthält eine Visual C++ 2010-Projektmappe für die Windows-Benutzer (einfach mit Visual C++ öffnen, kompilieren und ausführen) und ein Makefile für die Linux-Benutzer. Wie unter Linux üblich wird das Programm dann wie folgt kompiliert und ausgeführt:

cd Verzeichnis, wohin du entpackt hast
make
./SDL-Tutorial

Für die Windows-Benutzer gibt es außerdem einen zusätzlichen Download, der alle benötigten Bibliotheken und DLLs enthält. Das zweite Archiv muss in denselben Ordner entpackt werden wie das erste Archiv.

Meine Werkzeuge
Namensräume
Varianten
Aktionen
Navigation
Werkzeuge