SDL: Bilder und Ereignisse


Ganz so toll war ja unsere erste SDL-Anwendung nicht, doch das soll sich nun ändern, denn jetzt lernen wir wie man mit der SDL Bilder anzeigt und auf Ereignisse reagieren kann. Doch machen wir uns lieber erstmal klar auf welche Art man Bilder auf den Bildschirm bzw. ins Anwendungsfenster bekommt und was Ereignisse sind.

Beginnen wir mit ersterem: Um ein Bild auf den Bildschirm zu bringen werden zwei SDL_Surface-Objekte benötigt. Das erste repräsentiert das Bild (der Pointer image), das zweite einen sichtbaren Bildschirmbereich (der Pointer screen). Je nachdem, wie screen mit SDL_SetVideoMode initialisiert wurde, ist screen ein Fenster oder der Vollbildschirm.

Die SDL liefert uns Funktionen, mit welchen wir ganz einfach ein Bild auf der Festplatte in ein SDL_Surface laden können. Diese SDL_Surface ist nicht sichtbar.

Um sie sichtbar zu machen, wird sie auf die andere Oberfläche (screen) geblittet (sozusagen kopiert).

Nun weiter im Text, was sind Ereignisse? Ereignisse sind fast alles was in einer grafischen Oberfläche passiert. Ein Mausklick ist ein Ereignis und auch das Drücken oder Loslassen einer Taste. So kann man alles was am Computer durch den Benutzer passiert als Ereignis ansehen. Das lässt sich dann auch nutzen um unsere Anwendung erst dann zu beenden, wenn ein bestimmtes Ereignis eingetreten ist. Genug Theorie, hier der Quellcode:

Quellcode

Bearbeiten
 #include <stdlib.h>
 #include <SDL/SDL.h>
 int main(int argc, char *argv[])
 {
    SDL_Surface *screen, *image;
    SDL_Event event;
    int done = 0;
    if (SDL_Init(SDL_INIT_VIDEO) == -1) {
        printf("Can't init SDL:  %s\n", SDL_GetError());
        exit(1);
    }
    atexit(SDL_Quit); 
    screen = SDL_SetVideoMode(640, 480, 16, SDL_HWSURFACE);
    if (screen == NULL) {
        printf("Can't set video mode: %s\n", SDL_GetError());
        exit(1);
    }
    image = SDL_LoadBMP("tux.bmp");
    if (image == NULL) {
        printf("Can't load image of tux: %s\n", SDL_GetError());
        exit(1);
    }
    SDL_BlitSurface(image, NULL, screen, NULL);
    SDL_FreeSurface(image);
    SDL_UpdateRect(screen, 0, 0, 0, 0);
    while (done == 0) {
        while (SDL_PollEvent(&event)) {
            switch(event.type) {
            case SDL_QUIT:
                done = 1;
                break;
            }
        }
    }
    return 0;
 }

Bei Windowsusern ist es manchmal vorgekommen, dass das Programm beim Ausführen nicht richtig funktioniert hat. Starten Sie in diesem Fall die .exe selbst, sie befindet sich im selben Ordner, in den die SDL.dll hineingehört. Einige Entwicklungswerkzeuge bieten z.B. die Möglichkeit das Arbeitsverzeichnis für eine Debug-Sitzung festzulegen. Wenn das Bild "tux.bmp" dann ebenfalls in diesem Verzeichnis gespeichert ist, könnte das o.g. Problem behoben sein.

 
Tux -> Für das Programm bitte das Bild in ein Bitmap mit dem Namen "tux.bmp" umwandeln.

Erläuterungen

Bearbeiten

Variablendeklaration

Bearbeiten

In den ersten Zeilen ist nichts Neues dabei. Doch schnell fällt auf, dass ich gleich zwei Pointer auf ein SDL_Surface deklariere. Der Erste (screen) ist der altbekannte für den Bildschirminhalt. Der Zweite (image) soll auf die Adresse des Bildes, dass wir in den Speicher laden werden, gerichtet sein.

Danach kommt dann auch schon unser zweiter neuer Datentyp mit dem Namen SDL_Event, in der Variable event werden wir die Ereignisse speichern. Nun kommt noch eine Variable die uns angibt ob die Ausführung der Anwendung abgeschlossen ist.

Ein Bild aus einer Datei laden

Bearbeiten

Dann dauert es wieder einige Zeilen Code bis wir zur ersten neuen SDL-Funktion in diesem Quellcode stoßen. SDL_LoadBMP heißt die Nette und sie ist dafür zuständig ein Bild aus einer Bitmap-Datei, deren Dateiname als Parameter übergeben wird, in den Speicher zu laden. Die Funktion gibt die Speicheradresse des Bildes zurück. Diese Adresse wird dem Pointer image zugewiesen. Natürlich kommt auch hier wieder eine Fehlerabfrage rein, denn es kann schon leicht passieren dass man sich z.B. beim Dateinamen des Bildes vertippt, und somit das Bild nicht geladen werden kann.

Oberflächeninhalte durch blitten in andere kopieren

Bearbeiten

Aber jetzt komme ich wie versprochen wieder auf das Blitten zu sprechen, denn um ein Bild darzustellen muss es ja erst auf den Bildschirm kommen. Diese Kopieroperation des Blittens wird mit der Funktion SDL_BlitSurface erledigt, die als Parameter zuerst die Adresse des zu blittenen Surfaces erwartet, danach die Adresse einer Variable der Struktur SDL_Rect, auf die ich später zu sprechen komme, die die Größe und Position des Bereiches angibt, der vom Bild herauskopiert wird. Da wir hier NULL angeben wird einfach das gesamte Bild herauskopiert. Als dritten Parameter will die Funktion die Adresse der Zieloberfläche, welche wir ihr durch übergeben des Pointers auf den Bildschirm zeigen und als vierter Parameter kommt dann wieder die Adresse einer Variable vom Typ SDL_Rect zum Einsatz, die angibt in welchen Bereich, von welcher Größe der herauskopierte Bereich hineinkopiert werden soll. Auch hier geben wir wieder NULL an, womit einfach der gesamte Bildschirminhalt genommen wird. So richtig verstehen können wir die SDL_BlitSurface-Funktion mit dieser Erklärung bis jetzt zwar noch nicht, doch werde ich im nächsten Kapitel, das auch auf die Struktur SDL_Rect eingeht, eine genauere Erklärung einschieben.

Nicht Benötigtes aus dem Speicher entfernen

Bearbeiten

Nachdem wir jetzt das Bild von Tux auf den Bildschirm kopiert haben können wir den Speicher nun von dem Bild des Pinguins befreien, und das machen wir durch die Funktion SDL_FreeSurface, der wir als Parameter die Adresse der Oberfläche des Bildes angeben. Doch leider wäre jetzt Tux immer noch nicht zu sehen, da die Bildschirmoberfläche erst aktualisiert werden muss. Dies machen wir per SDL_UpdateRect, welche als ersten Parameter die zu aktualisierende Bildschirmoberfläche erwartet, als zweiten und dritten die Koordinaten des oberen linken Punktes des Rechteckigen Bereiches, welcher aktualisiert wird und als vierten und fünften die Höhe und Breite dieses Bereiches. Durch setzen der letzten vier Parameter auf 0 bringen wir die SDL_UpdateRect-Funktion jedoch dazu den kompletten Bildschirm zu aktualisieren.

Hauptschleife und Unterschleife

Bearbeiten

Das Bild ist jetzt zwar schon zu sehen, doch müssen wir die Anwendung noch dazu bringen sich selbst solange laufen zu lassen bis ein bestimmtes Ereignis eintritt. Dazu machen wir zwei ineinander verschachtelte while-Schleifen, die solange durchlaufen werden, bis das Programm abgebrochen wird durch schließen des Fensters oder STRG-C in der Konsole.

Die Funktion SDL_PollEvent reagiert auf Ereignisse und speichert diese in der als Parameter übergebenen Adresse einer Variable (deshalb &) vom Datentyp SDL_Event. Wenn ein neues Ereignis vorliegt und in der übergebenen Adresse gespeichert wurde, gibt die Funktion 1 zurück, wenn kein neues Ereignis vorliegt gibt sie 0 zurück.

Wenn der Nutzer also auf das Kreuz zum Beenden in der rechten oberen Ecke der Titelleiste klickt wird ein Ereignis der Art SDL_QUIT vom Betriebsystem an die Anwendung geschickt und von SDL_PollEvent in die Variable event gespeichert. Diese wird von uns durch die switch-Anweisung abgefragt. Enthält die Variable den Wert SDL_QUIT, so wird die Variable done auf 1 gesetzt und damit die Anwendung beendet.

Das ganze ist eine Art busy waiting, also eine riesige Rechenzeitverschwendung. Also besser man schmeißt die while-Schleifen raus, und nimmt dafür den Befehl

 SDL_Delay(20000);  // Wartet 20 Sekunden, zum Anschauen des Ergebnisses

rein. Dann kann man das Bild 20 Sekunden lang anschauen.

Voila, nun haben wir eine Anwendung mit der SDL geschrieben die ein Bild von Tux lädt, es anzeigt und danach darauf wartet, dass es beendet wird. Im nächsten Kapitel wird es dann darum gehen nicht nur auf das Beenden der Anwendung durch Klick aufs X einzugehen sondern auch aufs drücken einiger Tasten, dann wird noch das Double Buffering mit der SDL besprochen sowie das Bewegen von Bildern und das Transparentmachen von Tux' pinkem Hintergrund.