16.11 Eingabe- und Ereignisverarbeitung
Wenn Sie vorhaben sollten, Spiele oder sonstige interaktive Anwendungen zu erstellen, dann benötigen Sie auch eine Verarbeitung der Eingabe wie die Maus, die Tastatur oder den Joystick. Da Sie ja bereits die Möglichkeit hatten oder noch haben, sich in diesem Buch mit der Ereignisverarbeitung unter X oder mit der GTK+-Bibliothek näher vertraut zu machen, dürfte Ihnen das Kapitel relativ einfach fallen. Im Prinzip ist dies wieder nur eine Wiederholung des Themas mit neuen Namen und neuer Syntax. Aber auch ohne diese Vorkenntnisse reißt der Abschnitt keinen mehr vom Hocker.
16.11.1 SDL-Event-Struktur
Am besten arbeiten Sie sich von ganz oben nach unten durch. Ganz oben steht das Event, das bei SDL in einer Struktur namens SDL_Event verpackt wurde. Diese Struktur ist der Anfang der Ereignisverarbeitung bei einem Eintreffen eines Ereignisses, woran alle vorbei müssen, sei dies nun das Neuzeichnen eines Bildschirms, ein Tastendruck, ein Mausereignis oder der Knopfdruck eines Joysticks. Hier die Mutter aller SDL-Events im Überblick:
typedef union {
Uint8 type;
SDL_ActiveEvent active;
SDL_KeyboardEvent key;
SDL_MouseMotionEvent motion;
SDL_MouseButtonEvent button;
SDL_JoyAxisEvent jaxis;
SDL_JoyBallEvent jball;
SDL_JoyHatEvent jhat;
SDL_JoyButtonEvent jbutton;
SDL_ResizeEvent resize;
SDL_ExposeEvent expose;
SDL_QuitEvent quit;
SDL_UserEvent user;
SDL_SysWMEvent syswm;
} SDL_Event;
Anhand der Namen lässt sich schon so einiges herauslesen, um was für Events es sich handelt. Denn letztendlich sind hier alle Strukturvariablen, die mit SDL_ beginnen, auch wieder Strukturen, die exakte Daten eines entsprechenden Events beinhalten.
Intern wird dabei für jedes auftretendes Event ein neues SDL_Event erzeugt und in einen FIFO-Puffer (FIFO = First in First out) abgelegt. Dieser Puffer ist nötig, da sonst bei sehr vielen auf einmal auftretenden Events einige verloren gehen könnten – was bei einem Spiel unangenehme Folgen haben könnte.
Bevor Sie Funktionen kennen lernen, die von diesem FIFO Gebrauch machen, um die Events zu lesen, müssen Sie erst einmal wissen, was für ein Event überhaupt aufgetreten ist. Anhand der Struktur SDL_Event können Sie erkennen, dass es davon eine Menge gibt. Was für ein Event letztendlich aufgetreten ist, findet sich in der Strukturvariablen type von SDL_Event. Dabei müssen Sie nur type auswerten und können anschließend eine der entsprechenden Strukturen auslesen. Welchen Wert (symbolische Konstante) type dabei besitzen kann und auf welche Struktur sich dieser Wert dann bezieht, können Sie der folgenden Tabelle entnehmen:
Tabelle 16.3
Event-type und die entsprechenden Strukturen
Event-type
|
Event-Struktur
|
SDL_ACTIVEEVENT
|
SDL_ActiveEvent
|
SDL_KEYDOWN
SDL_KEYUP
|
SDL_KeyboardEvent
|
SDL_MOUSEMOTION
|
SDL_MouseMotionEvent
|
SDL_MOUSEBUTTONDOWN
SDL_MOUSEBUTTONUP
|
SDL_MouseButtonEvent
|
SDL_JOYAXISMOTION
|
SDL_JoyAxisEvent
|
SDL_JOYBALLMOTION
|
SDL_JoyBallEvent
|
SDL_JOYHATMOTION
|
SDL_JoyHatEvent
|
SDL_JOYBUTTONDOWN
SDL_JOYBUTTONUP
|
SDL_JoyButtonEvent
|
SDL_QUIT
|
SDL_QuitEvent
|
SDL_SYSWMEVENT
|
SDL_SysWMEvent
|
SDL_VIDEORESIZE
|
SDL_ResizeEvent
|
SDL_VIDEOEXPOSE
|
SDL_ExposeEvent
|
SDL_USEREVENT
|
SDL_UserEvent
|
Bevor Sie jetzt den Typ des Events abfragen können, müssen Sie dieses erst noch vom FIFO abholen. Hierfür gibt es zwei gängige Funktionen, die sich von der Funktionalität erheblich unterscheiden.
int SDL_WaitEvent(SDL_Event *event);
int SDL_PollEvent(SDL_Event *event);
Mit SDL_WaitEvent() wird gewartet, bis sich ein Event im FIFO befindet. Befindet sich ein Event im FIFO, wird es von SDL_WaitEvent() abgeholt und aus dem FIFO entfernt. SDL_WaitEvent() gibt bei Erfolg 1 zurück oder 0, wenn beim Warten auf ein Event ein Fehler aufgetreten ist.
Bei der Funktion SDL_PollEvent() hingegen werden die Events abgefragt. Befindet sich ein Event im FIFO, wird es auch hiermit entfernt, und die Funktion SDL_PollEvent() gibt 1 zurück. Befindet sich kein Event im FIFO, wird 0 zurückgegeben und mit der Programmausführung fortgefahren.
Hinweis Da die beiden eben erwähnten Funktionen immer das nächste Event aus dem FIFO entfernen, können Sie, falls nötig, auch die Funktion SDL_PeepEvents() und das Flag SDL_PEEKEVENT verwenden, um nur einen Blick auf die Events im FIFO zu werfen, ohne diese zu entfernen. Des Weiteren ist es auch möglich, wie es für einen FIFO typisch ist, eigene Events dem FIFO hinzuzufügen.
|
Dies können Sie mit der Funktion SDL_PushEvent() erledigen. Beides wird allerdings in diesem Buch nicht benötigt – daher sei hierfür die Dokumentation von SDL zurate gezogen.
Hinweis Neben den hier erwähnten Funktionen gibt es noch eine ganze Palette anderer, die sich auf die Events beziehen.
|
16.11.2 Maus-Events
Angefangen wird mit den Events der Maus. Um jetzt ein Event mit der Funktion SDL_PollEvent() oder SDL_WaitEvent()aus dem FIFO zu holen und auszulesen, müssen Sie erst eine leere Event-Struktur erzeugen. Dies geht recht einfach mit einer simplen Deklaration von:
SDL_Event event;
Als Beispiel wird zum Entnehmen eines Events aus dem FIFO die Funktion SDL_PollEvents() in einer Schleife verwendet:
while(SDL_PollEvent(&event)) {
...
}
Tritt jetzt ein Event auf, füllt die Funktion SDL_PollEvent() (oder auch die Funktion SDL_WaitEvent()) die SDL_event-Struktur mit Informationen. Welche Art von Informationen dies sind, erfahren Sie mit der Strukturvariablen type von SDL_Event (wurde eben besprochen). Hierzu verwenden Sie am besten (ist so üblich, aber nicht die Regel) eine switch()-Fallunterscheidung:
switch(event.type) {
case ... :
case ... :
...
}
Welche Typen (type) von Events hierbei auftreten können, haben Sie bereits in der Tabelle 15.3 sehen können. Da uns in diesem Abschnitt vorerst einmal die Mausereignisse interessieren, wären dies die Event-Typen SDL_MOUSEMOTION, SDL_MOUSEBUTTONDOWN und SDL_MOUSEBUTTONUP. SDL_MOUSEMOTION wird z. B. ausgelöst, wenn eine Bewegung der Maus auf dem Fenster registriert wurde, und die Events SDL_MOUSEBUTTONDOWN oder SDL_MOUSEBUTTONUP werden ausgelöst, wenn ein Maus-Button betätigt bzw. losgelassen wurde.
switch(event.type) {
case SDL_MOUSEMOTON : /* Die Maus hat sich bewegt ;-) */
}
Ein Blick auf die Tabelle der type von Events zeigt Ihnen, dass sich SDL_MOUSEMOTION (ganz wichtiges Event für Ego-Shooter) auf die Struktur SDL_MouseMotionEvent bezieht. Wenn Sie sich jetzt die Struktur dazu ansehen, dann wissen Sie, welche Informationen Sie hieraus ermitteln können:
typedef struct {
Uint8 type;
Uint8 state;
Uint16 x, y;
Sint16 xrel, yrel;
} SDL_MouseMotionEvent;
Die ersten beiden Strukturvariablen haben im Prinzip dieselbe Bedeutung und enthalten den Typen des Ereignisses – in type befindet sich SDL_MOUSEMOTION und in state der Status des Buttons, wenn dieser gerade gedrückt wurde. Die aktuellen Koordinaten finden Sie in x, y, und die relativen Koordinaten zu x, y finden Sie in xrel und yrel.
Natürlich wollen Sie jetzt auch noch wissen, wie Sie an diese Informationen herankommen. Hierzu nochmals ein Ausschnitt der Struktur SDL_Event (schließlich ist die Struktur SDL_MouseMotionEvent ehrenvolles Mitglied):
typedef union {
Uint8 type;
SDL_ActiveEvent active;
SDL_KeyboardEvent key;
SDL_MouseMotionEvent motion; /* Danach suchen wir ... */
SDL_MouseButtonEvent button;
...
...
SDL_SywWMEvent syswm;
} SDL_Event;
Mithilfe der Strukturvariablen motion können Sie jetzt auf die einzelnen Mitglieder der Struktur SDL_MouseMotionEvent zugreifen:
switch(event.type) {
case SDL_MOUSEMOTION:
printf ("Mouse-Motion: ");
printf ("Neue Position (x:%d,y:%d). ",
event.motion.x, event.motion.y);
printf ("Veränderung zur alten Pos.:(%i,%i)\n",
event.motion.xrel, event.motion.yrel);
break;
}
Und ähnlich einfach können Sie die Informationen des type SDL_MOUSEBUTTONDOWN und SDL_MOUSEBUTTONUP abfragen.
switch(event.type) {
case SDL_MOUSEBUTTONDOWN : /* Die Maus wurde geklickt */
}
Das Mitglied der Struktur SDL_Event hierfür ist die Struktur SDL_MouseButtonEvent und folgendermaßen definiert:
typedef struct {
Uint8 type;
Uint8 button;
Uint8 state;
Uint16 x, y;
} SDL_MouseButtonEvent;
In type finden Sie die Information des Events, ob es sich hierbei um SDL_MOUSEBUTTONDOWN oder SDL_MOUSEBUTTONUP handelt. button enthält den Button, der gedrückt wurde (linker, mittlerer, rechter). Hierfür gibt es auch die symbolischen Konstanten SDL_BUTTON_LEFT, SDL_BUTTON_MIDDLE, SDL_BUTTON_RIGHT. Mit state erfahren Sie, ob die Maus gedrückt oder losgelassen wurde (SDL_PRESSED oder SDL_RELEASED) – was im Endeffekt type entspricht. Und in x und y stehen die x-, y-Koordinaten, wo der Button (vom linken oberen Fenstereck aus) gedrückt wurde. Der Mitgliedsname von SDL_MouseButtonEvent in der Struktur SDL_Event lautet button. Somit können Sie die Informationen zum Klicken eines Buttons folgendermaßen ermitteln:
switch(event.type) {
case SDL_MOUSEBUTTONDOWN : /* Die Maus wurde geklickt */
printf ("[Button (%d) an Position (x:%d,y:%d)]\n",
event.button.button, event.button.x, event.button.y);
break;
}
16.11.3 Programmbeispiel – Maus-Event
Genug der Theorie. Im folgenden Beispiel finden Sie einen leeren Weltraum, durch den Sie sich durch Anklicken der Pfeiltasten scrollen können. Anbei werden die Informationen der Maus auch auf der Konsole ausgegeben. Lassen Sie sich von dem vielen Drumherum nicht stören, und befassen Sie sich ausschließlich mit dem Abfangen der Maus-Events in der Funktion eventloop().
Hinweis Auch wenn Ihnen der Code recht komplex erscheinen mag, in Sachen SDL finden Sie dabei nichts Neues – abgesehen davon, dass der Code ein wenig modularer aufgebaut wurde.
|
/* sdl7.c */
#include <SDL/SDL.h>
#include <SDL/SDL_image.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
/* Wie viele Universen sind auf dem Bildschirm? */
#define UNIVERSES 50
struct universum {
int x, y; /* Koordinaten des Universums */
int type; /* Welches Bild wird für die Universen verwendet? */
};
struct universum univ[UNIVERSES];
/* Verschiedene Surfaces */
static SDL_Surface *screen;
static SDL_Surface *left, *up, *right, *down, *background;
/* Farbwert Schwarz */
static Uint32 black;
static int y_scrolling = 0, x_scrolling = 0;
static void RandBackground () {
int i;
int num_types;
num_types = background->w / 16;
for (i = 0; i < UNIVERSES; i++) {
univ[i].x = rand () & 1023;
univ[i].y = rand () & 511;
univ[i].type = rand () % num_types;
}
}
static void DrawBackground (int xn, int yn) {
int i;
for (i = 0; i < UNIVERSES; i++) {
SDL_Rect src;
SDL_Rect dest;
src.x = univ[i].type * 16;
src.y = 0;
src.w = 16;
src.h = 16;
dest.x = (univ[i].x - xn) & 1023 - 16;
dest.y = (univ[i].y - yn) & 511 - 16;
SDL_BlitSurface (background, &src, screen, &dest);
}
}
static void RS_Blit (SDL_Surface * bitmap, Sint16 x, Sint16 y) {
SDL_Rect dest, src;
dest.x = x;
dest.y = y;
dest.w = bitmap->w;
dest.h = bitmap->h;
src.x = 0;
src.y = 0;
src.w = bitmap->w;
src.h = bitmap->h;
SDL_BlitSurface (bitmap, &src, screen, &dest);
SDL_UpdateRects (screen, 1, &dest);
}
static SDL_Surface *load_image (char *file) {
static SDL_Surface *image, *temp;
temp = IMG_Load (file);
image = SDL_DisplayFormat (temp);
if (image == NULL) {
printf ("Konnte Grafik nicht laden: %s\n",SDL_GetError ());
return NULL;
}
SDL_FreeSurface (temp);
return (image);
}
static int eventloop (void) {
SDL_Event event;
while (SDL_PollEvent (&event) != 0) {
switch (event.type) {
case SDL_MOUSEBUTTONDOWN:
if ((event.button.x > up->w)
&& (event.button.x < up->w * 2)
&& (event.button.y > 0)
&& (event.button.y < up->w)) {
y_scrolling -= 12;
printf ("Pfeil nach oben angeklickt ");
printf ("[Button (%d) an Position (x:%d,y:%d)]\n",
event.button.button, event.button.x, event.button.y);
}
else if ((event.button.x > 0)
&& (event.button.x < left->w)
&& (event.button.y > left->h)
&& (event.button.y < left->h * 2)) {
x_scrolling -= 12;
printf ("Pfeil nach links angeklickt\n");
printf ("[Button (%d) an Position (x:%d,y:%d)]\n",
event.button.button, event.button.x, event.button.y);
}
else if ((event.button.x > down->w)
&& (event.button.x < down->w * 2)
&& (event.button.y > down->h * 2)
&& (event.button.y < down->h * 3)) {
y_scrolling += 12;
printf ("Pfeil nach unten angeklickt\n");
printf ("[Button (%d) an Position (x:%d,y:%d)]\n",
event.button.button, event.button.x, event.button.y);
}
else if ((event.button.x > right->w * 2)
&& (event.button.x < right->w * 3)
&& (event.button.y > right->h)
&& (event.button.y < right->h * 2)) {
x_scrolling += 12;
printf ("Pfeil nach rechts angeklickt\n");
printf ("[Button (%d) an Position (x:%d,y:%d)]\n",
event.button.button, event.button.x, event.button.y);
}
else {
printf ("Maus-Button (%d) gedrückt an Position "
" (x:%d,y:%d)]\n",
event.button.button, event.button.x, event.button.y);
}
break;
case SDL_MOUSEMOTION:
printf ("Mouse-Motion: ");
printf ("Neue Position (x:%d,y:%d). ",
event.motion.x, event.motion.y);
printf ("Veränderung zur alten Pos.:(%i,%i)\n",
event.motion.xrel, event.motion.yrel);
break;
case SDL_QUIT:
printf ("Quit event. Bye.\n");
exit (EXIT_SUCCESS);
}
}
return 1;
}
int main (void) {
int done = 1;
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;
}
srand (time (0));
up = load_image ("up.png");
right = load_image ("right.png");
down = load_image ("down.png");
left = load_image ("left.png");
background = load_image ("background1.png");
RandBackground ();
black = SDL_MapRGB (screen->format, 0, 0, 0);
SDL_FillRect (screen, 0, black);
DrawBackground (0, 0);
RS_Blit (up, up->h, 0);
RS_Blit (left, 0, left->w);
RS_Blit (down, down->h, down->w * 2);
RS_Blit (right, down->h * 2, left->w);
SDL_UpdateRect (screen, 0, 0, 0, 0);
while (done) {
done = eventloop ();
SDL_FillRect (screen, 0, black);
DrawBackground (x_scrolling, y_scrolling);
RS_Blit (up, up->h, 0);
RS_Blit (left, 0, left->w);
RS_Blit (down, down->h, down->w * 2);
RS_Blit (right, down->h * 2, left->w);
SDL_Flip (screen);
}
/* Ressourcen wieder freigeben */
SDL_FreeSurface (up);
SDL_FreeSurface (down);
SDL_FreeSurface (right);
SDL_FreeSurface (left);
SDL_FreeSurface (background);
SDL_FreeSurface (screen);
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc `sdl-config --libs` `sdl-config --cflags` -o \
sdl7 sdl7.c -lSDL_image
$ ./sdl7
...
Mouse-Motion: Neue Position (x:20,y:391). Veränderung zur alten Pos.:(-1,0)
Mouse-Motion: Neue Position (x:19,y:391). Veränderung zur alten Pos.:(-1,0)
Maus-Button (1) gedrückt an Position (x:19,y:391)]
Maus-Button (3) gedrückt an Position (x:19,y:391)]
Maus-Button (1) gedrückt an Position (x:19,y:391)]
Mouse-Motion: Neue Position (x:17,y:391). Veränderung zur alten Pos.:(-2,0)
Mouse-Motion: Neue Position (x:16,y:391). Veränderung zur alten Pos.:(-1,0)
Mouse-Motion: Neue Position (x:15,y:391). Veränderung zur alten Pos.:(-1,0)
Maus-Button (3) gedrückt an Position (x:15,y:391)]
Maus-Button (3) gedrückt an Position (x:15,y:391)]
Mouse-Motion: Neue Position (x:16,y:392). Veränderung zur alten Pos.:(1,1)
...
16.11.4 Tastatur-Events
Ähnlich wie bei den Maus-Events können Sie auch bei den Events von der Tastatur vorgehen. Ein solches Event ist aufgetreten, wenn in der Strukturvariablen type mit SDL_KEYDOWN oder SDL_KEYUP gesetzt ist. SDL_KEYDOWN steht für das Drücken einer Taste und SDL_KEYUP für das Loslassen.
switch(event.type) {
case SDL_KEYDOWN : /* Eine Taste wurde gedrückt */
}
Ist also z. B. das Event SDL_KEYDOWN aufgetreten, finden Sie in der Struktur SDL_Event eine Strukturvariable namens SDL_KeyboardEvent, die folgende Mitglieder beinhaltet:
typedef struct {
Uint8 type;
Uint8 state;
SDL_keysym keysym;
} SDL_KeyboardEvent;
In type befindet sich der Event des Tastaturereignisses (SDL_KEYDOWN oder SDL_KEYUP). state entspricht hierbei auch wieder demselben wie type, worin sich im Grunde dieselben Informationen (SDL_PRESSED oder SDL_RELEASED) befinden. Anders sieht es schon mit der Strukturvariablen keysym vom Typ SDL_Keysym aus – was letztendlich eine weitere Struktur darstellt.
Somit müssen Sie bei den Tastatur-Events noch eine Etage tiefer forschen. In der Struktur SDL_Keysym befinden sich wiederum folgende Mitglieder:
typedef struct {
Uint8 scancode;
SDLKey sym;
SDLMod mod;
Uint16 unicode;
} SDL_keysym;
Der erste Wert ist der Scancode – was ein Wert von der Hardware ist, den Sie aber im Grunde nicht benötigen. In sym werden die ASCII-Werte des Tastendrucks gespeichert. Wurde zusätzlich eine Modifier-Taste ((Ş), (Alt), (ş_) ...) gedrückt, finden Sie diesen Wert in mod wieder. Mehrere Tastenwerte eines Modifiers können natürlich mit einem bitweisen ODER verknüpft werden. Für einen internationalen Zeichensatz steht Ihnen noch die Variable unicode zur Verfügung.
Die Werte für SDLKey und SDLMod sind in der Headerdatei SDL_Keysym.h definiert. Werte von SDLKey beginnen alle mit dem Präfix SDLK_ und Werte von SDLMod mit dem Präfix KMOD_. Einige Beispiele für den ASCII-Wert eines Tastendrucks finden Sie in der folgenden Tabelle (mehr entnehmen Sie bitte der Headerdatei SDL_Keysym.h):
Tabelle 16.4
Einige Werte für SDLKey aus der Headerdatei SDL_Keysym.h
SDLKey
|
ASCII-Wert
|
Bezeichnung
|
SDLK_BACKSPACE
|
'\b'
|
backspace
|
SDLK_TAB
|
'\t'
|
tabulator
|
SDLK_CLEAR
|
|
clear
|
SDLK_RETURN
|
'\r'
|
return
|
SDLK_PAUSE
|
|
pause
|
SDLK_ESCAPE
|
'^['
|
escape
|
SDLK_SPACE
|
' '
|
space
|
SDLK_EXCLAIM
|
'!'
|
exclaim
|
SDLK_QUOTEDBL
|
'"'
|
quotedbl
|
SDLK_0
|
'0'
|
0
|
SDLK_1
|
'1'
|
1
|
SDLK_2
|
'2'
|
2
|
SDLK_a
|
'a'
|
a
|
SDLK_b
|
'b'
|
b
|
SDLK_c
|
'c'
|
c
|
SDLK_UP
|
|
up arrow
|
SDLK_DOWN
|
|
down arrow
|
SDLK_RIGHT
|
|
right arrow
|
SDLK_LEFT
|
|
left arrow
|
Natürlich können Sie z. B. auch den ASCII-Wert der linken (Ctrl)-Taste mit SDLK_LCTRL ermitteln. Um aber bestimmte Modifier-Abfragen (SDLMod) zu machen, können Sie folgende symbolische Konstanten verwenden:
Tabelle 16.5
SDL Modifier und ihre Bedeutung
SDL Modifier
|
Bedeutung
|
KMOD_NONE
|
keine Modifiers ausgeführt
|
KMOD_NUM
|
Numlock gedrückt
|
KMOD_CAPS
|
Capslock gedrückt
|
KMOD_LCTRL
|
linke Control-Taste gedrückt
|
KMOD_RCTRL
|
rechte Control-Taste gedrückt
|
KMOD_RSHIFT
|
rechte Shift-Taste gedrückt
|
KMOD_LSHIFT
|
linke Shift-Taste gedrückt
|
KMOD_RALT
|
rechte Alt-Taste gedrückt
|
KMOD_LALT
|
linke Alt-Taste gedrückt
|
KMOD_CTRL
|
eine Control-Taste gedrückt
|
KMOD_SHIFT
|
eine Shift-Taste gedrückt
|
KMOD_ALT
|
eine Alt-Taste gedrückt
|
Wollen Sie jetzt wissen, ob die Pfeil-nach-links-Taste gedrückt wurde, können Sie folgendermaßen vorgehen:
SDL_keysym keysym;
switch (event.type) {
case SDL_KEYDOWN:
keysym = event.key.keysym;
switch (keysym.sym) {
case SDLK_LEFT: /* ASCII-Wert für Pfeil-nach-links gelesen */
...
}
...
Natürlich müssen Sie den Typ SDL_keysym nicht extra deklarieren. Sie können auch über – angefangen von der Struktur SDL_Event, worin der Name von SDL_KeyboardEvent key lautet, worin sich wiederum die Strukturvariable SDL_keysym namens keysym befindet – SDL_keysym auf den ASCII-Wert von SDL_key namens sym zugreifen (ziemlich tief greifend, aber dennoch übersichtlich) – was in der Praxis so aussieht:
switch(event.type) {
case SDL_KEYDOWN:
if(event.key.keysym.sym == SDLK_LEFT)
/* ASCII-Wert für Pfeil-nach-links gelesen */
Bei den Modifiers können Sie ganz ähnlich vorgehen. Wollen Sie z. B. wissen, ob die linke (Ş)-Taste gedrückt wurde, während gleichzeitig eine andere ASCII-Taste gedrückt wurde (bitte nicht verwechseln – bei den Modifiers gilt nicht nur das Drücken einer Modifier-Taste!), gehen Sie folgendermaßen vor:
if(event.key.keysym.mod & KMOD_LSHIFT)
/* die linke Shift-Taste wurde in Kombination mit einer */
/* anderen ASCII-Taste gedrückt */
Benötigen Sie den Namen einer Taste, die gedrückt wurde, können Sie die Funktion
char *SDL_GetKeyName(SDLKey key);
verwenden. Diese gibt Ihnen bei Erfolg den SDL-Namen des Tastendrucks wieder.
16.11.5 Programmbeispiel – Tastatur-Events
Zu den Tastatur-Events soll jetzt das Anwendungsbeispiel, das schon bei den Maus-Events verwendet wurde, erweitert werden. Verändert wurde in diesem Beispiel eigentlich nur die Funktion eventloop() – weshalb auch nur diese hier abgedruckt wird. Erweitern Sie einfach diese Funktion vom Beispiel zuvor um die Tastatur-Events (oder verwenden Sie einfach den Code von der Buch-CD, wenn Sie keine Lust zum Tippen haben). Fügen Sie außerdem in der main()-Funktion noch vor der Event-Schleife Folgendes ein:
...
SDL_EnableKeyRepeat (SDL_DEFAULT_REPEAT_DELAY,
SDL_DEFAULT_REPEAT_INTERVAL);
while (done) {
done = eventloop ();
SDL_FillRect (screen, 0, black);
DrawBackground (x_scrolling, y_scrolling);
RS_Blit (up, up->h, 0);
RS_Blit (left, 0, left->w);
RS_Blit (down, down->h, down->w * 2);
RS_Blit (right, down->h * 2, left->w);
SDL_Flip (screen);
}
...
Damit schalten Sie die Wiederholungsrate der Tastatur ein. Mit dem ersten Parameter geben Sie an, wie lange eine Taste gedrückt werden muss, bevor die Wiederholung des Tastendrucks beginnt. Mit dem zweiten Parameter geben Sie das Intervall an, womit die Wiederholung ausgeführt wird. Beide Angaben erfolgen in Millisekunden. Im Beispiel werden allerdings jeweils Standardwerte verwendet. Mit dieser Funktion sorgen Sie beim Scrollen in einer Richtung dafür, dass beim dauerhaften Drücken der Taste auch weiterhin gescrollt wird und Sie nicht wie wild auf Ihre Pfeiltasten hämmern müssen. Die Tastendrücke, bei denen keine Pfeiltaste gedrückt wurde, werden wieder auf der Konsole ausgegeben.
static int eventloop (void) {
SDL_Event event;
while (SDL_PollEvent (&event) != 0) {
SDL_keysym keysym;
switch (event.type) {
case SDL_KEYDOWN:
keysym = event.key.keysym;
switch (keysym.sym) {
case SDLK_LEFT:
x_scrolling -= 12;
break;
case SDLK_RIGHT:
x_scrolling += 12;
break;
case SDLK_UP:
y_scrolling -= 12;
break;
case SDLK_DOWN:
y_scrolling += 12;
break;
default:
printf ("Taste gedrückt. ");
printf ("SDL keysym ist %i. ", keysym.sym);
printf ("(%s) ", SDL_GetKeyName (keysym.sym));
printf ("linke Umschalttaste %s gedrückt\n",
(event.key.keysym. mod & KMOD_LSHIFT) ?
"ist" : "nicht");
if (keysym.sym == SDLK_ESCAPE) {
printf ("ESCAPE für Ende gedrückt .\n");
exit (EXIT_FAILURE);
}
}
break;
case SDL_MOUSEBUTTONDOWN:
if ((event.button.x > up->w)
&& (event.button.x < up->w * 2)
&& (event.button.y > 0)
&& (event.button.y < up->w)) {
y_scrolling -= 12;
printf ("Pfeil nach oben angeklickt ");
printf ("[Button (%d) an Position (x:%d,y:%d)]\n",
event.button.button, event.button.x, event.button.y);
}
else if ((event.button.x > 0)
&& (event.button.x < left->w)
&& (event.button.y > left->h)
&& (event.button.y < left->h * 2)) {
x_scrolling -= 12;
printf ("Pfeil nach links angeklickt\n");
printf ("[Button (%d) an Position (x:%d,y:%d)]\n",
event.button.button, event.button.x, event.button.y);
}
else if ((event.button.x > down->w)
&& (event.button.x < down->w * 2)
&& (event.button.y > down->h * 2)
&& (event.button.y < down->h * 3)) {
y_scrolling += 12;
printf ("Pfeil nach unten angeklickt\n");
printf ("[Button (%d) an Position (x:%d,y:%d)]\n",
event.button.button, event.button.x, event.button.y);
}
else if ((event.button.x > right->w * 2)
&& (event.button.x < right->w * 3)
&& (event.button.y > right->h)
&& (event.button.y < right->h * 2)) {
x_scrolling += 12;
printf ("Pfeil nach rechts angeklickt\n");
printf ("[Button (%d) an Position (x:%d,y:%d)]\n",
event.button.button, event.button.x, event.button.y);
}
else {
printf ("Maus-Button (%d) gedrückt an Position "
" (x:%d,y:%d)]\n",
event.button.button, event.button.x, event.button.y);
}
break;
case SDL_MOUSEMOTION:
printf ("Mouse-Motion: ");
printf ("Neue Position (x:%d,y:%d). ",
event.motion.x, event.motion.y);
printf ("Veränderung zur alten Pos.:(%i,%i)\n",
event.motion.xrel, event.motion.yrel);
break;
case SDL_QUIT:
printf ("Quit event. Bye.\n");
exit (EXIT_FAILURE);
}
}
return 1;
}
Einen Screenshot kann ich mir hierzu ersparen, da das Bild dasselbe ist wie schon bei der Event-Behandlung der Maus, nur dass Sie sich hier jetzt auch mit der Tastatur durch den Weltraum scrollen können.
16.11.6 Joystick-Events
Joysticks sind bei vielen Spielen nicht wegzudenken. Daher unterstützt SDL auch diese Eingabegeräte – mitsamt den Achsen POV Hats und Trackballs. Um allerdings Joysticks zu verwenden, müssen Sie beim Initialisieren mit SDL_Init() das Flag SDL_INIT_JOYSTICK mit angeben (und natürlich auch entsprechende Kernel-Module laden!).
Joystick-Informationen
Um einen Joystick überhaupt verwenden zu können, müssen Sie diesen erst mit der Funktion SDL_JoystickOpen() öffnen.
SDL_Joystick *SDL_JoystickOpen(int index);
Mit dem index geben Sie die Nummer des Joysticks (beginnend bei 0) an – da es ja hier auch mehrere Joysticks auf dem System geben kann. Bei Erfolg erhalten Sie einen Zeiger auf die SDL_Joystick-Struktur, bei einem Fehler erhalten Sie NULL zurück.
Jetzt können Sie sich einige Informationen zum Joystick auslesen lassen.
const char *SDL_JoystickName(int index);
int SDL_JoystickNumAxes(SDL_Joystick *joystick);
int SDL_JoystickNumBalls(SDL_Joystick *joystick);
int SDL_JoystickNumHats(SDL_Joystick *joystick);
int SDL_JoystickNumButtons(SDL_Joystick *joystick);
Anhand der Namen lässt sich schon erkennen, um was für Informationen es sich hierbei handelt. Einen Zeiger auf den Namen des Joysticks gibt Ihnen die Funktion SDL_JoystickName() unter Angabe des entsprechenden Joystick-Index (erster Joystick = 0; ...) zurück. Die Anzahl der Achsen bekommen Sie mit SDL_JoystickAxes(), die Anzahl der Trackballs mit SDL_JoystickBalls(), die Anzahl der Hats mit SDLJoystickHats() und die Anzahl der Buttons mit SDL_JoystickButtons() zurück. Wobei all diese Funktionen als Parameter einen Zeiger auf die Struktur von SDL_Joystick benötigen, den Sie zuvor mit SDL_JoystickOpen() erhalten haben.
Wenn Sie mit dem Arbeiten (besser dem Spielen) des Joysticks fertig sind, dann sollten Sie diesen mit SDL_JoystickClose() wieder schließen.
void SDL_JoystickClose(SDL_Joystick *joystick);
Hinweis Bei einem Blick in die Dokumentation dürften Ihnen weitere Joystick-Funktionen auffallen. Mit diesen Funktionen besteht die Möglichkeit, den Joystick auch ohne eine Event-Schleife zu behandeln. Allerdings müssen Sie dabei immer selbst die Funktion SDL_JoystickUpdate() aufrufen, was Ihnen bei einer Event-Behandlung abgenommen wird. In der Praxis wird die Joystick-Behandlung via Events empfohlen.
|
Joystick-Events
Am besten kann man mit den Eingabedaten eines Joysticks arbeiten, indem man Events dazu verwendet. Bei einem Blick auf die SDL_Event-Struktur finden Sie hierfür gleich mehrere Einträge:
typedef union {
Uint8 type;
...
SDL_JoyAxisEvent jaxis;
SDL_JoyBallEvent jball;
SDL_JoyHatEvent jhat;
SDL_JoyButtonEvent jbutton;
...
} SDL_Event;
Je nach type der Events, die auftreten können, können Sie das Ereignis des Joysticks auslesen. Da diese Tabelle schon ein paar Seiten zurückliegt, hier nochmals die Auflistung der Joystick-Events:
Tabelle 16.6
Events, die in Verbindung mit einem Joystick stehen
Event-type
|
Event-Struktur
|
Bedeutung
|
SDL_JOYAXISMOTION
|
SDL_JoyAxisEvent
|
Achse des Joysticks wurde bewegt.
|
SDL_JOYBALLMOTION
|
SDL_JoyBallEvent
|
Trackball wurde bewegt.
|
SDL_JOYHATMOTION
|
SDL_JoyHatEvent
|
Hat des Joysticks wurde betätigt.
|
SDL_JOYBUTTONDOWN
SDL_JOYBUTTONUP
|
SDL_JoyButtonEvent
|
Ein Button des Joysticks wurde betätigt bzw. losgelassen.
|
Hinweis Um auch Events vom Joystick verarbeiten zu können, muss dieser zuvor mit SDL_JoystickOpen() geöffnet werden!
|
Wurden z. B. die Achsen (oder auch das Steuerkreuz) des Joysticks betätigt, so tritt das Event SDL_JOYAXISMOTION auf.
switch (event.type) {
case SDL_JOYAXISMOTION: /* Achse bewegt */
}
Die jetzt benötigten Informationen finden Sie in der Struktur SDL_JoyAxisEvent, die wie folgt aussieht:
typedef struct {
Uint8 type;
Uint8 which;
Uint8 axis;
Sint16 value;
} SDL_JoyAxisEvent;
In type finden Sie wie immer das Event, das aufgetreten ist (SDL_JOYAXISMOTION). Auf welchen Joystick sich das Event bezieht, finden Sie in which. Die Nummer des Index der Joystick-Achse befindet sich in axis und der Wert (variiert zwischen –32768 und 32767) in value. Somit können Sie die Informationen aus der Achse folgendermaßen auslesen:
while (SDL_PollEvent (&event)) {
switch (event.type) {
case SDL_JOYAXISMOTION:
printf ("Joystick %d, Achse %d bewegt nach %d\n",
event.jaxis.which, event.jaxis.axis, event.jaxis.value);
}
}
Ähnlich verläuft dies bei Betätigung eines Buttons des Joysticks (feuert aus allen Rohren), was den Event-Typ SDL_JOYBUTTONUP oder SDL_JOYBUTTONDOWN hervorruft. Dieses Event speichert seine Informationen in der Struktur SDL_JoyButtonEvent.
typedef struct {
Uint8 type;
Uint8 which;
Uint8 button;
Uint8 state;
} SDL_JoyButtonEvent;
type und state entsprechend wieder im Grunde demselben. Darin befindet sich die Information, was für ein Event aufgetreten ist (gedrückt oder nicht gedrückt). Von welchem Joystick der Button-Druck kam, befindet sich in which, und welcher Button gedrückt wurde, steht in button.
Auf die andern beiden Events SDL_JOYBALLMOTION und SDL_JOYHATMOTION wird nicht mehr eingegangen, da sich der Vorgang immer recht ähnlich abspielt.
16.11.7 Programmbeispiel – Joystick-Events
Das folgende Programmbeispiel zeigt, wie Sie auf einfache Joystick-Events wie die Achse und die Buttons reagieren können. Im Beispiel habe ich ein einfaches Joypad an den USB-Port gestöpselt.
/* sdl9.c */
#include <SDL/SDL.h>
#include <stdlib.h>
#include <stdio.h>
static SDL_Joystick *js;
static void Joystick_Info (void) {
int num_js, i;
num_js = SDL_NumJoysticks ();
printf ("Es wurde(n) %d Joystick(s) auf dem System gefunden\n",
num_js);
if (num_js == 0) {
printf ("Es wurde kein Joystick gefunden\n");
return;
}
/* Informationen zum Joystick */
for (i = 0; i < num_js; i++) {
js = SDL_JoystickOpen (i);
if (js == NULL) {
printf ("Kann Joystick %d nicht öffnen\n", i);
}
else {
printf ("Joystick %d\n", i);
printf ("\tName: %s\n", SDL_JoystickName(i));
printf ("\tAxen: %i\n", SDL_JoystickNumAxes(js));
printf ("\tTrackballs: %i\n", SDL_JoystickNumBalls(js));
printf ("\tButtons: %i\n",SDL_JoystickNumButtons(js));
SDL_JoystickClose (js);
}
}
}
static int eventloop_joystick (void) {
SDL_Event event;
while (SDL_PollEvent (&event)) {
switch (event.type) {
case SDL_KEYDOWN:
if (event.key.keysym.sym == SDLK_ESCAPE) {
printf ("ESCAPE für Ende betätigt\n");
return 0;
}
break;
case SDL_JOYAXISMOTION:
printf ("Joystick %d, Achse %d bewegt nach %d\n",
event.jaxis.which, event.jaxis.axis, event.jaxis.value);
break;
case SDL_JOYBUTTONUP:
case SDL_JOYBUTTONDOWN:
printf ("Joystick %i Button %i: %i\n",
event.jbutton.which, event.jbutton.button,
event.jbutton.state);
break;
case SDL_QUIT:
return 0;
}
}
return 1;
}
int main (void) {
int done = 1;
if (SDL_Init (SDL_INIT_JOYSTICK | SDL_INIT_VIDEO) != 0) {
printf ("Fehler: %s\n", SDL_GetError ());
return 1;
}
atexit (SDL_Quit);
if (SDL_SetVideoMode (256, 256, 16, 0) == NULL) {
printf ("Fehler: %s\n", SDL_GetError ());
return EXIT_FAILURE;
}
Joystick_Info ();
js = SDL_JoystickOpen (0);
if (js == NULL) {
printf("Konnte Joystick nicht öffnen:%s\n",SDL_GetError());
}
while( done ) {
done = eventloop_joystick ();
SDL_Delay(10);
}
SDL_JoystickClose (js);
return EXIT_SUCCESS;
}
Das Programm bei der Ausführung:
$ gcc `sdl-config --libs` `sdl-config --cflags` -o sdl9 sdl9.c
$ ./sdl9
Es wurde(n) 1 Joystick(s) auf dem System gefunden
Joystick 0
Name: 0603:6875
Axen: 2
Trackballs: 0
Buttons: 8
Joystick 0 Button 0: 0
Joystick 0 Button 1: 0
Joystick 0 Button 2: 0
Joystick 0 Button 3: 0
Joystick 0 Button 4: 0
Joystick 0 Button 5: 0
Joystick 0 Button 6: 0
Joystick 0 Button 7: 0
Joystick 0, Achse 0 bewegt nach -32767
Joystick 0, Achse 1 bewegt nach -32767
Joystick 0 Button 0: 1
Joystick 0 Button 3: 1
Joystick 0, Achse 1 bewegt nach 0
Joystick 0 Button 0: 0
Joystick 0 Button 3: 0
Joystick 0, Achse 1 bewegt nach 32767
Joystick 0, Achse 1 bewegt nach 0
Joystick 0, Achse 0 bewegt nach 0
Joystick 0 Button 2: 1
Joystick 0, Achse 1 bewegt nach -32767
Joystick 0, Achse 0 bewegt nach 32767
Joystick 0, Achse 0 bewegt nach -32767
Joystick 0, Achse 0 bewegt nach 32767
Joystick 0, Achse 1 bewegt nach 32767
Joystick 0, Achse 0 bewegt nach -32767
...
16.11.8 Weitere Events
Es gibt noch einige weitere Events, worauf hier allerdings nicht mehr eingegangen wird. Wie Sie an entsprechende Informationen herankommen können, haben Sie jetzt bereits einige Male gesehen. Fangen Sie immer mit der Struktur SDL_Event an, und graben Sie sich anschließend durch. In den Beispielen wurde recht häufig der Event-Typ SDL_QUIT verwendet, der, wie ich meine, für sich selbst spricht.
|