Rheinwerk Computing < openbook > Rheinwerk Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Linux-UNIX-Programmierung von Jürgen Wolf
Das umfassende Handbuch – 2., aktualisierte und erweiterte Auflage 2006
Buch: Linux-UNIX-Programmierung

Linux-UNIX-Programmierung
1216 S., mit CD, 49,90 Euro
Rheinwerk Computing
ISBN 3-89842-749-8
gp Kapitel 16 SDL
  gp 16.1 Was ist und kann SDL?
  gp 16.2 SDL installieren
  gp 16.3 SDL-Anwendungen erstellen
  gp 16.4 SDL initialisieren und Videomodus einstellen
    gp 16.4.1 Programmbeispiel – SDL initialisieren und Videomodus einstellen
  gp 16.5 Direkt auf den Bildschirm zeichnen
    gp 16.5.1 Programmbeispiel – direkt auf den Bildschirm zeichnen
  gp 16.6 Bitmap laden und anzeigen
    gp 16.6.1 Programmbeispiel – Bitmap laden und anzeigen
  gp 16.7 Ein anderes Grafikformat laden und anzeigen
  gp 16.8 Der rechteckige Bildbereich von SDL
  gp 16.9 Farbenschlüssel, Transparenz und Alpha-Blending
    gp 16.9.1 Alpha-Blending
  gp 16.10 Animation
    gp 16.10.1 Programmbeispiel – Animation
  gp 16.11 Eingabe- und Ereignisverarbeitung
    gp 16.11.1 SDL-Event-Struktur
    gp 16.11.2 Maus-Events
    gp 16.11.3 Programmbeispiel – Maus-Event
    gp 16.11.4 Tastatur-Events
    gp 16.11.5 Programmbeispiel – Tastatur-Events
    gp 16.11.6 Joystick-Events
    gp 16.11.7 Programmbeispiel – Joystick-Events
    gp 16.11.8 Weitere Events
  gp 16.12 Audio
    gp 16.12.1 Programmbeispiel – Audio
  gp 16.13 Ausblick


Rheinwerk Computing

16.10 Animation  downtop

Spätestens jetzt werden Sie wohl auch das Bedürfnis haben, Bilder, die Sie anzeigen können, auf dem Bildschirm zu bewegen (natürlich in 2D).

Das Prinzip ist im Endeffekt immer dasselbe – auch wenn es bei steigendem Umfang schon recht komplex werden kann. Zuerst benötigen Sie immer eine Spielfläche, ein einfaches Koordinatensystem, worauf Sie anschließend die Grafik bewegen können. Des Weiteren wird ein Koordinatensystem benötigt, das die x- und y-Koordinaten der Grafik beinhaltet und aus dem ersichtlich ist, wo sich diese im Augenblick befindet.

Die Koordinaten der linken oberen Ecke sind somit (0/0) und der rechten unteren Ecke (639/479) (bei Verwendung einer Auflösung von 640 x 480 Bildpunkten). Alles, was also vom Ausgangspunkt (0/0) rechts liegt, erhöht den x-Wert, und alles, was darunter liegt, erhöht den y-Wert.

Da Sie im folgenden Beispiel einen Ball verwenden, sprechen wir nun auch konkret von einem Ball als Grafik. Neben der aktuellen Position des Balls benötigen Sie auch die Position, in welche Richtung und in welcher Geschwindigkeit sich der Ball beim nächsten Bildwechsel bewegt. In einer Struktur verpackt sieht es in etwa so aus:

typedef struct ball {
    int x, y;           /* Aktuelle Position             */
    int dx, dy;         /* Bewegung für nächste Position */
} ball_t;

Des Weiteren benötigen Sie eine Funktion, um den Wert des Balls mit einem Startwert zu initialisieren. Dann braucht es eigentlich nur noch eine Funktion, die den Ball auf dem Bildschirm zeichnet, und eine, welche die nächste neue Position des Balls berechnet. Alles in allem sind dies eigentlich nur Funktionen, die relativ wenig mit SDL zu tun haben und Ihre Gedanken recht strapazieren können.


Hinweis   Mir sei bitte verziehen, dass ich den Teil SDL mehr SDL-lastig halte und weniger tief in die Spieleprogrammierung und deren Algorithmen eingehe. Die Beispiele, die hier allerdings verwendet werden, stellen keine allzu großen Anforderungen an den geübten C-Programmierer dar.


Somit stellt eine Animation zunächst nichts anderes dar als ein dauerndes Neuzeichnen des Bildschirms. Ziel bei der Programmierung von Animationen sollte es immer sein, den Bildschirm möglichst schnell und häufig neu zu zeichnen, ohne dass das Auge davon etwas mitkriegt. Dass dies nicht immer einfach ist, dürfte klar sein. Jeder neu gezeichnete Bildschirm wird im Augenblick als Frame bezeichnet, und die Anzahl der Bilder, die in einer bestimmten Zeiteinheit neu gezeichnet werden, wird als Framerate bezeichnet. Und je höher diese Framerate ist, umso weniger bekommt das Auge etwas vom Bildwechsel mit.

Sie könnten mit den jetzigen Kenntnissen auch schon eine Animation mit einer Grafik erstellen. Allerdings würde es bei rechenintensiveren Aufgaben garantiert zu schlechten Framerates kommen. Daher sollten hier noch zwei Möglichkeiten erwähnt werden, um die Framerate Ihrer Anwendung zu erhöhen.

Wie Sie ein Surface (Framebuffer) auf dem Bildschirm darstellen können, wissen Sie ja bereits. Wenn Sie hierbei allerdings eine enorme Menge von Grafiken darstellen wollen und bei einer Animation immer wieder neu in den Framebuffer zeichnen und wiederholen müssen, kann es je nach Hardware passieren, dass das Zeichnen ins Ruckeln gerät. Den Effekt kennen Sie sicherlich bei neueren Spielen mit etwas älterer Hardware, wenn enorm viele Grafiken und Effekte auf einmal am Bildschirm dargestellt werden sollen. Um dieses Ruckeln der einzelnen Frames zu vermeiden (bzw. zu verringern), verwendet man die doppelte Frame-Pufferung (Double-Buffering). Wie der Name schon sagt, stehen Ihnen hiermit zwei Zeichenebenen zur Verfügung. Dabei befindet sich in einem Frame das Bild, das gerade auf dem Bildschirm dargestellt wird, und im anderen Frame die nächste Zeichnung, die bereits aufgebaut wird; ein Mini-Stack der Grafikkarte, wenn Sie so wollen. Wenn der Bildaufbau fertig ist, müssen die beiden Bilder vertauscht werden. Dann findet sich das zuvor noch in der Grafikkarte vorhandene Frame auf dem Bildschirm, und das alte Frame wird wieder in der Grafikkarte zum Neuzeichnen des nächsten Frames verwendet.


Hinweis   Einen Haken hat die Sache allerdings, die Grafikkarte muss das Tauschen der beiden Frames – auch als Flipping bekannt – unterstützen.


Den doppelten Framebuffer können Sie ganz einfach mit der Funktion SDL_SetVideoMode() und dem Flag SDL_DOUBLEBUF aktivieren. Anschließend müssen Sie noch zum Tauschen der beiden Frames die Funktion SDL_Flip() verwenden.

int SDL_Flip(SDL_Surface *screen);

Unterstützt die Hardware nun die doppelte Frame-Pufferung, führt diese Funktion einen Flip aus und kehrt zurück. Unterstützt Ihre Hardware keinen doppelten Framebuffer, ist der Aufruf dieser Funktion gleichwertig mit einem Aufruf von SDL_UpdateRect(screen, 0, 0, 0, 0). Bei Erfolg gibt SDL_Flip() 0, sonst bei einem Fehler –1 zurück. Ansonsten müssen Sie sich um nichts zu kümmern, da SDL Ihnen den Verwaltungsaufwand des doppelten Framebuffers abnimmt. Sie verwenden also einfach die Funktion SDL_Flip() anstatt zuvor SDL_UpdateRect(). Mehr ist dazu nicht nötig.

Eine zweite erhebliche Geschwindigkeitsbremse stellt das dauernde Konvertieren zwischen den Pixeln des Bildformates (z. B. JPG, PNG, ...) und den Pixeln des tatsächlich auf dem Bildschirm gezeichneten Wertes dar. Bei jedem neuen Frame wird praktisch das Format der Grafik in das korrekte Format für das Blitting konvertiert. Das ist ziemlich zeitaufwändig und wirkt sich zudem negativ auf die Framerate aus.

Dieses Problem können Sie mit der Funktion SDL_DisplayFormat() umgehen. Damit wird der Pixel einer Grafik in ein optimales Format für das Blitting konvertiert.

SDL_Surface *SDL_DisplayFormat(SDL_Surface *surface);

Die Funktion erwartet als Parameter ein Surface (das der Grafik) und gibt darauf ein neues Surface zurück, das nicht mehr konvertiert werden muss und Ihnen somit zum schnellen Blitting gleich zur Verfügung steht. Bei einem Fehler wird NULL zurückgegeben. Beachten Sie außerdem, dass das alte Surface unangetastet bleibt und Sie es, sofern Sie das Originalformat nicht mehr benötigen, wieder freigeben – da Sie sonst ein Speicherleck haben.

Außerdem zerstört diese Funktion den Alpha-Kanal des Bildes (nicht das Pre-Alpha-Blending) – da das Alpha-Blending ebenfalls eine Geschwindigkeitsbremse für ein schnelles Blitting ist –, sofern keine RLE-Beschleunigung unterstützt wird. Wenn Sie den Alpha-Kanal ebenfalls mitkonvertieren wollen, können Sie die Funktion SDL_DisplayFormatAlpha() verwenden, die, außer dass der Alpha-Kanal mitkonvertiert wird, genauso anzuwenden ist wie SDL_DislplayFormat().


Rheinwerk Computing

16.10.1 Programmbeispiel – Animatiotoptop

Beim Einstellen des Videomodus wurde beim folgenden Programm die doppelte Frame-Pufferung verwendet. Daraufhin werden eine Hintergrundgrafik und zwei Bälle geladen. Anschließend werden diese Grafiken entsprechend mit SDL_DisplayFormat() konvertiert. Es wurden außerdem die Transparenz und das Alpha-Blending verwendet. Insgesamt durchlaufen Sie in diesem Beispiel 1000 Frames. Der Bildschirm wird also tausendmal neu gezeichnet. Dabei können Sie, wenn Sie wollen, eine Zeitfunktion einbauen, womit Sie die durchschnittliche Framerate pro Sekunde berechnen können. Um ein Gefühl für die Framerate zu bekommen, sollten Sie ein wenig an dem Programm experimentieren. Verwenden Sie am besten mehrere Bälle oder eine schnellere Ausführgeschwindigkeit der Bälle. Versuchen Sie es mit und ohne Alpha-Blending. Sie werden dabei schnell bemerken, wo hier und da die Zeit verloren geht. Ein Blick auf top zeigt, dass das Beispiel je nach System bis zu 80 % der CPU in Anspruch nehmen kann. Aus diesem Grund empfiehlt es sich auch, ein Delay() – eine Verzögerung – einzubauen, sofern noch andere wichtige Anwendungen zur gleichen Zeit ausgeführt werden sollen. Ähnliches wurde bereits im Kapitel der X-Programmierung gemacht. Dazu müssen Sie allerdings nicht extra eine Funktion schreiben, sondern können die Funktion SDL_Delay() verwenden, die Ihnen die SDL-Bibliothek zur Verfügung stellt. Damit können Sie die Anwendung für mindestens 10 ms unterbrechen (10 Millisekunden werden garantiert, alles andere darunter nicht). Im Beispiel hatte ein SDL_Delay(10) eine Entlastung der CPU um 50 % zur Folge, ohne dass dabei das Bild bemerkbar geruckelt hätte. Behalten Sie dabei nicht nur Ihre Anwendung im Auge, sondern vor allem auch das X-Fenster, das für die Ausführung der Anwendung am meisten Rechenleistung benötigt.


Hinweis   Sofern Sie bei diesem Beispiel nicht auf die Grafiken der Buch-CD zurückgreifen wollen – oder gerne selbst kreativ werden wollen –, müssen Sie bei diesem Listing darauf achten, dass die Hintergrundgrafik (hier background.png) mindestens die Größe hat, die Sie mit den ersten beiden Parametern von SDL_SetVideoMode() angegeben haben (im Beispiel sind das 640 * 480 Pixel).

Ansonsten haben Sie eine Art Schmiereffekt (HOM = Hall Of Mirrors ist der Fachbegriff), was ja durchaus nett anzusehen ist – aber hier nicht beabsichtigt war (testen Sie es ruhig).


/* sdl6.c */
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <stdio.h>
#include <stdlib.h>
#define BALLS        5
#define MAX_SPEED1   6
#define MAX_SPEED2   10
typedef struct ball {
  int x, y;        /* Aktuelle Position */
  int dx, dy;      /* Bewegung für nächste Position */
}ball_t;
/* Array für die Bälle */
static ball_t balls1[BALLS];
static ball_t balls2[BALLS];
/* Verschiedene Surfaces */
static SDL_Surface *screen;
static SDL_Surface *ball;
static SDL_Surface *ball2;
/* Für Alpha-Blending von ball2 */
static int intensiv = 0;
/* Startwerte für die Bälle */
static void init_balls1 (void) {
  int i;
  for (i = 0; i < BALLS; i++) {
    balls1[i].x = rand () % screen->w;
    balls1[i].y = rand () % screen->h;
    balls1[i].dx = (rand () % (MAX_SPEED1 * 2)) - MAX_SPEED1;
    balls1[i].dy = (rand () % (MAX_SPEED1 * 2)) - MAX_SPEED1;
    balls2[i].x = rand () % screen->w;
    balls2[i].y = rand () % screen->h;
    balls2[i].dx = (rand () % (MAX_SPEED2 * 2)) - MAX_SPEED2;
    balls2[i].dy = (rand () % (MAX_SPEED2 * 2)) - MAX_SPEED2;
  }
}
/* Bewegt jeden Ball anhand des Bewegungsfeldes dx, dy */
static void move_balls1 (void) {
  int i;
  for (i = 0; i < BALLS; i++) {
    /* Ball um entsprechenden dy-, dx-Wert verschieben */
    balls1[i].x += balls1[i].dx;
    balls1[i].y += balls1[i].dy;
    /* Ball an der linken oder rechten Seite des */
    /* Bildschirms angekommen ...                */
    if (balls1[i].x < 0 || balls1[i].x > screen->w - 1)
      balls1[i].dx = -balls1[i].dx;
    /* Ball an der oberen oder unteren Seite des */
    /* Bildschirms angekommen ...                */
    if (balls1[i].y < 0 || balls1[i].y > screen->h - 1)
      balls1[i].dy = -balls1[i].dy;
    /* Das gleiche Spiel nochmals mit der zweiten Sorte */
    /* von Bällen ...                                  */
    balls2[i].x += balls2[i].dx;
    balls2[i].y += balls2[i].dy;
    if (balls2[i].x < 0 || balls2[i].x > screen->w - 1)
      balls2[i].dx = -balls2[i].dx;
    if (balls2[i].y < 0 || balls2[i].y > screen->h - 1)
      balls2[i].dy = -balls2[i].dy;
  }
}
/* Jeden Ball einzeln auf das (Haupt-)Surface zeichnen */
static void draw_balls1 (void) {
  int i;
  SDL_Rect src, src2, dest;
  for (i = 0; i < BALLS; i++) {
    /* Daten vom Ball - Quelle */
    src.x = 0;
    src.y = 0;
    src.w = ball->w;
    src.h = ball->h;
    /* Wohin den Ball kopieren - Ziele */
    dest.x = balls1[i].x - ball->w / 2;
    dest.y = balls1[i].y - ball->h / 2;
    dest.w = ball->w;
    dest.h = ball->h;
    /* Quelle nach Ziel kopieren */
    SDL_BlitSurface (ball, &src, screen, &dest);
    /* Daten von Ball - Quelle */
    src2.x = 0;
    src2.y = 0;
    src2.w = ball2->w;
    src2.h = ball2->h;
    /* Wohin den Ball kopieren - Ziele */
    dest.x = balls2[i].x - ball2->w / 2;
    dest.y = balls2[i].y - ball2->h / 2;
    dest.w = ball2->w;
    dest.h = ball2->h;
    /* Für Alpha-Blending-Effekt des Balls */
    if (intensiv < 255)
      intensiv++;
    else
      intensiv = 0;
    /* Alpha-Blending setzen */
    SDL_SetAlpha(ball2, SDL_SRCALPHA | SDL_RLEACCEL, intensiv);
    /* Quelle nach Ziel kopieren */
    SDL_BlitSurface (ball2, &src2, screen, &dest);
  }
}
int main (void) {
  SDL_Surface *temp, *temp2;
  SDL_Surface *background;
  SDL_Rect src, dest;
  int frames;
  if (SDL_Init (SDL_INIT_VIDEO) != 0) {
    printf ("Konnte SDL nicht initialisieren: %s\n",
       SDL_GetError ());
    return EXIT_FAILURE;
  }
  atexit (SDL_Quit);
  screen = SDL_SetVideoMode (640, 480, 16,
                             SDL_DOUBLEBUF | SDL_HWSURFACE);
  if (screen == NULL) {
    printf ("Kann Videomodus nicht setzen: %s\n",
       SDL_GetError ());
    return EXIT_FAILURE;
  }
  temp = IMG_Load ("background.png");
  background = SDL_DisplayFormat (temp);
  if (background == NULL) {
    printf("Konnte Grafik nicht laden: %s\n", SDL_GetError ());
    return EXIT_FAILURE;
  }
  SDL_FreeSurface (temp);
  temp = IMG_Load ("ball1.png");
  if (temp == NULL) {
    printf ("Konnte Grafik nicht laden: %s\n",SDL_GetError ());
    return EXIT_FAILURE;
  }
  temp2 = IMG_Load ("ball2.png");
  if (temp2 == NULL) {
    printf ("Konnte Grafik nicht laden: %s\n",SDL_GetError ());
    return EXIT_FAILURE;
  }
  /* Farbenschlüssel für Transparenz der beiden Bälle setzen */
  SDL_SetColorKey (temp, SDL_SRCCOLORKEY | SDL_RLEACCEL,
                   (Uint16) SDL_MapRGB (temp->format, 0, 0, 0));
  SDL_SetColorKey (temp2,SDL_SRCCOLORKEY | SDL_RLEACCEL,
                   (Uint16) SDL_MapRGB (temp2->format, 0, 0, 0));
  /* Alpha-Blending setzen */
  SDL_SetAlpha (temp, SDL_SRCALPHA | SDL_RLEACCEL, 100);
  SDL_SetAlpha (temp2, SDL_SRCALPHA | SDL_RLEACCEL, 0);
  ball = SDL_DisplayFormat (temp);
  if (ball == NULL) {
    printf ("Konnte Grafik nicht konvertieren: %s\n",
       SDL_GetError ());
    return EXIT_FAILURE;
  }
  SDL_FreeSurface (temp);
  ball2 = SDL_DisplayFormat (temp2);
  if (ball2 == NULL) {
    printf ("Konnte Grafik nicht konvertieren: %s\n",
       SDL_GetError ());
    return EXIT_FAILURE;
  }
  SDL_FreeSurface (temp2);
  /* Einzelne Bälle mit Werten initialisieren */
  init_balls1 ();
  for (frames = 0; frames < 1000; frames++) {
    /* Hintergrund zeichnen */
    src.x = 0;
    src.y = 0;
    src.w = background->w;
    src.h = background->h;
    dest = src;
    SDL_BlitSurface (background, &src, screen, &dest);
    /* Alle Bälle neu zeichnen */
    draw_balls1 ();
    /* doppelten Framebuffer tauschen */
    SDL_Flip (screen);
    /* nächste Position für die Bälle holen */
    move_balls1 ();
    SDL_Delay(10); /* Verschnaufpause für die CPU */
  }
  /* Ressourcen wieder freigeben */
  SDL_FreeSurface (background);
  SDL_FreeSurface (ball);
  SDL_FreeSurface (ball2);
  return EXIT_SUCCESS;
}

Das Programm bei der Ausführung:

$ gcc `sdl-config --libs` `sdl-config --cflags` -o sdl6 sdl6.c -lSDL_image
$ ./sdl6

Abbildung
Hier klicken, um das Bild zu vergrößern

Abbildung 16.4    Animierte Bälle mit Transparenz und Alpha-Blending


 << zurück
  
  Zum Rheinwerk-Shop
Neuauflage: Linux-UNIX-Programmierung
Neuauflage:
Linux-UNIX-
Programmierung

bestellen
 Ihre Meinung?
Wie hat Ihnen das Openbook gefallen?
Ihre Meinung

 Buchtipps
Zum Rheinwerk-Shop: Linux-Server






 Linux-Server


Zum Rheinwerk-Shop: Das Komplettpaket LPIC-1 & LPIC-2






 Das Komplettpaket
 LPIC-1 & LPIC-2


Zum Rheinwerk-Shop: Linux-Hochverfügbarkeit






 Linux-
 Hochverfügbarkeit


Zum Rheinwerk-Shop: Shell-Programmierung






 Shell-
 Programmierung


Zum Rheinwerk-Shop: Linux Handbuch






 Linux Handbuch


 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und der Schweiz
Info





Copyright © Rheinwerk Verlag GmbH 2006
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.
Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


Nutzungsbestimmungen | Datenschutz | Impressum

Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de

Cookie-Einstellungen ändern