Bis jetzt haben wir zwar schon ein Fenster erstellt und in dieses Fenster mit OpenGL ein Dreieck hineingezeichnet. Wir haben das ganze auch schon perspektivisch gerendert und auch Objekte durch verschiedenste Transformationen in die richtige Form gebracht. Damit können wir jetzt schon einiges anstellen, aber wir haben bis jetzt noch keine Möglichkeit der Interaktion mit dem Programm kennen gelernt. Und darauf kommt es bei einem Spiel doch an. Denn ein Spiel das sich selbst spielt wird selten Erfolg haben :).
Damit wir unser Programm auch im Vollbildmodus starten können, sollten wir zumindest in der Lage sein, festzustellen ob zum Beispiel "Escape" gedrückt ist, um dann unser Programm zu beenden. Mit dem "X" im normalen Fenster wird das schließlich nicht gehen, da es im Vollbildmodus ja eigentlich kein Fenster mehr gibt. Deshalb werden wir in diesem Tutorial lernen mit Hilfe der SDL eine einfache Eingabebehandlung von Tastatur und Maus vorzunehmen. Joystickeingaben wären zwar auch möglich, aber auf die werden wir erst später zurückkommen.
1. Tastatureingabe
Zuerst beschäftigen wir uns einmal mit dem wohl wichtigstem Eingabegerät des Computers, nämlich mit der Tastatur. Grundsätzlich können wir Eingaben von der Tastatur bzw. eigentlich von allen Eingabegeräten auf zwei Arten erhalten. Entweder wir fragen nach ob eine bestimmte Taste gerade gedrückt ist, oder wir fangen die vom Betriebssystem bei einer Änderung des Zustandes einer Taste automatisch versendeten Nachrichten ab und kommen so zu unserer Eingabe. Bei der Eingabe können wird auch nach gepufferter und ungepufferter Eingabe unterscheiden. Bei der gepufferten Eingabe fangen wir die Nachrichten ab wodurch sicher keine Eingaben verloren gehen können, da anders als bei der ungepufferten Eingabe, die wir durch das Abfragen der benötigten Tasten in jedem Frame erhalten können, auch bei Änderungen zwischen zwei Frames eine Nachricht verschickt wird und wir die Eingaben somit trotzdem erhalten.
1.1. Nachrichtentypen
Zuerst versuchen wir uns einmal daran, die Nachrichten abzufangen. Zur Behandlung von Tastatureingaben gibt es zwei Nachrichtentypen:
- SDL_KEYDOWN
- SDL_KEYUP
Wie die Namen schon aussagen wird SDL_KEYDOWN versendet, wenn eine Taste gedrückt worden ist und SDL_KEYUP wenn eine Taste wieder losgelassen worden ist. SDL_KEYDOWN kann auch öfters versendet werden, nämlich dann wenn wir mit SDL_EnableKeyRepeat Keyrepeating aktivieren, d.h. wenn eine Taste länger als eine bestimmte Zeit gedrückt wird, würden wir weitere SDL_KEYDOWN Nachricht bekommen solange bis die Taste wieder losgelassen wird.
Wie bei allen SDL-Nachrichten gibt es auch dafür wieder eine Struktur. Sie heißt 'SDL_KeyboardEvent' und ist unter dem Variablennamen key Teil des Union 'SDL_Event'. Wenn wir also wieder unsere bereits bewährte Funktion zur Behandlung der Nachrichten hernehmen, dann können wir leicht die beiden neu kennengelernten Nachrichten abfangen:
bool pollEvents() { SDL_Event event; while( SDL_PollEvent(&event) ) // Nächste Nachricht holen { switch(event.type) { case SDL_KEYUP: // Keyboardnachrichten abfangen (kein break -> SDL_KEYUP case SDL_KEYDOWN: // und SDL_KEYDOWN werden gleich behandelt) if( event.key.keysym.sym != SDLK_ESCAPE ) break; // Alles außer [ESC] ignorieren case SDL_QUIT: return false; // Programm sollte beendet werden default: break; } } return true; }
Wir brauchen also nur unser 'switch' um die entsprechenden Nachrichtentypen erweitern. In der 'SDL_KeyboardEvent'-Struktur gibt es unter dem Variablennamen 'keysym' noch eine weitere Struktur 'SDL_keysym' in der genauere Informationen zur gedrückten oder losgelassenen Taste stehen:
typedef struct SDL_keysym{ Uint8 scancode; // Hardware spezifischer Scancode SDLKey sym; // SDL virtueller Tastencode SDLMod mod; // Aktuelle Modifizierer Uint16 unicode; // Buchstabe in Unicode übersetzt } SDL_keysym;
Da der scancode hardwareabhängig ist werden wir ihn nicht verwenden. Das Feld das wir jedoch fast immer verwenden werden ist sym, in dem ein virtueller Tastencode steht. Dieser ist auf jeder Hardware gleich und vom Typ 'SDLKey', welches ein enum mit einer Liste von Konstanten für die einzelnen Tasten ist. In unserem Codebeispiel verwenden wir zum Beispiel 'SDLK_ESCAPE' für die Taste [ESC]. Alle Konstanten beginnen mit 'SDLK_' gefolgt von der Bezeichnung der Tasten. Für Ziffern sind das einfach die entsprechenden Ziffern und für die Buchstaben die entsprechenden kleinen(!) Buchstaben, also zum Beispiel 'SDLK_3' oder 'SDLK_p'. Eine Auflistung weiterer Tasten finden wir unter anderem im Wiki auf delphigl.com.
Wenn wir also wieder zu dem Codeausschnitt weiter oben zurückgehen dann sehen wir, dass bei einem Tastaturevent - egal ob eine Taste gedrückt oder losgelassen wird - der Wert des virtuellen Tastencodes getestet wird. Wenn er mit dem Tastencode der [ESC]-Taste übereinstimmt dann wird das 'break' übersprungen und auch noch der Code der Behandlung einer 'SDL_QUIT'-Nachricht ausgeführt. Dadurch erreichen wir, dass bei einem Druck auf die [ESC]-Taste das Programm beendet wird. 'SDL_KEYUP' müssten wir eigentlich gar nicht behandeln, aber damit wir auch das einmal kennen lernen habe ich das in den Democode mit eingebaut.
Wenn uns noch interessiert welche Tastenmodifizierer (zb. [Alt], [Strg], ...) beim Drücken der für die Nachricht verantwortlichen Taste aktiviert oder gedrückt waren steht dies als spezielles Bitmuster in der Variable mod. Auch hierfür gibt es wieder ein paar Konstanten, die auch im Wiki auf delphigl.com beschrieben sind. 'KMOD_RSHIFT' steht zum Beispiel für die rechte Shift-Taste. Um zu überprüfen ob jetzt ein Modifizierer aktiviert war können wir das einfach mit dem bitweisen "und"-Operator (&) herausfinden:
if( event.key.keysym.mod & KMOD_RSHIFT ) cout << "[SHIFT] (Rechts) ist aktiviert." << endl;
Um also zum Beispiel das kopieren von Text in die Zwischenablage zu ermögliche sollten wir [STRG]+[C] abfangen. Wenn wir jetzt also noch zusätzlich diese Nachricht behandeln wollen, müsste der betreffende Teil der Nachrichtenbehandlung in etwa so ausschauen:
switch(event.type) { case SDL_KEYUP: // Keyboardnachrichten abfangen (kein break -> SDL_KEYUP case SDL_KEYDOWN: // und SDL_KEYDOWN werden gleich behandelt) if( event.key.keysym.sym == SDLK_c // Taste [C] gedrückt && event.key.keysym.mod & KMOD_CTRL ) // [STRG] gedrückt { copyTextToClipboard(); } if( event.key.keysym.sym != SDLK_ESCAPE ) break; // Alles außer [ESC] ignorieren case SDL_QUIT: return false; // Programm sollte beendet werden default: break; }
Die Funktion 'copyTextToClipboard' müssten wir dann natürlich noch entsprechend implementieren, aber das hat nicht mehr wirklich mit dem Thema dieses Tutorials zu tun, so dass wir uns das jetzt ersparen :P.
1.2. Ungepufferte Eingabe
Wie wir sehen können ist das Abfangen von Tastaturnachrichten kein großes Problem. Die zweite Moglichkeit die Tastatur zu behandeln ist die ungepufferte Eingabe, bei der wir mit SDL_GetKeyState einen "Schnappschuss" der Zustände aller Tasten erhalten. Das gleiche gibt es auch für die Modifizierer mit der Funktion SDL_GetModState. Da bei diesen Abfragen aber immer nur die momentanen Zuständ der Tasten abgefragt werden und Änderungen zwischen zwei Funktionsaufrufen nicht erkannt werden sollten wir diese Methode nach Möglichkeit vermeiden.
2. Eingabe mit der Maus
Seit der Erfindung der Maus gibt es praktisch keinen Computer mehr der ohne sie auskommt, und genauso schaut es auch bei Spielen aus. Denn wie sollte man zum Beispiel ein Strategiespiel oder einen Egoshooter ohne eine Maus spielen. Ich kann es mir zumindest nicht vorstellen :).
Und deshalb möchte ich euch auch die Verwendung der Maus nicht länger vorenthalten. Also beginnen wir am Besten gleich damit. So schwer ist es nicht, und wir können dann nachher sehr viel damit machen.
2.1. Weitere Nachrichten
Die übliche Behandlung erfolgt gleich wie bei der Tastatur über vom Betriebssystem verschickte Nachrichten. Einerseits gibt es wieder Nachrichten für das Drücken und Loslassen der Tasten und eine weitere Nachricht die verschickt wird wenn sich die Maus bewegt hat. Diese neuen Nachrichten heißen wie folgt:
- SDL_MOUSEBUTTONDOWN
- SDL_MOUSEBUTTONUP
- SDL_MOUSEMOTION
2.1.1. Die Tasten
Wie wir nur unschwer erraten können, dienen die ersten beiden Nachrichtentypen zur Benachrichtigung über gedrückte oder losgelassenen Maustasten. Die zuständige Struktur im 'SDL_Event' Union ist unter der Variablen button ansprechbar und ist vom Typ 'SDL_MouseButtonEvent', was nichts weiter als eine Struktur ist, die wie folgt aufgebaut ist:
typedef struct{ Uint8 type; Uint8 button; Uint8 state; Uint16 x, y; } SDL_MouseButtonEvent;
Wichtig ist dabei für uns hauptsächlich die Variable button, in der die ID des gedrückten oder losgelassenen Buttons steht. Die IDs liegen dabei im Bereich von 1 bis 255. Praktischerweise gibt es aber für die häufigsten Tasten auch ein paar vordefinierte Konstanten:
- SDL_BUTTON_LEFT
- SDL_BUTTON_MIDDLE
- SDL_BUTTON_RIGHT
- SDL_BUTTON_WHEELUP
- SDL_BUTTON_WHEELDOWN
Wie wir unschwer erkennen können handelt es sich dabei um die linke, rechte und mittlere Maustaste und noch die Drehung des Mausrades nach oben oder unten. Ja, die Bewegung des Mausrades wird als drücken von je einer Taste für die entsprechende Richtung mitgeteilt. Vermutlich liegt das daran, dass die meisten Mausräder spürbare Rasten besitzen, und daher beim Drehen auch eher einem mehrfachen Drücken eines Knopfes entspricht als einer kontinuierlichen Bewegung. Mit diesem Wissen können wir die Maustasten behandeln, so dass wir uns gleich an einer Erweiterung des vorigen Beispiel versuchen:
switch(event.type) { case SDL_MOUSEBUTTONDOWN: if( event.button.button == SDL_BUTTON_LEFT ) std::cout << "[" event.button.x << "|" << event.button.y << "]" << " Linke Maustaste gedrückt." << std::endl; break; case SDL_KEYUP: // Keyboardnachrichten abfangen (kein break -> SDL_KEYUP case SDL_KEYDOWN: // und SDL_KEYDOWN werden gleich behandelt) /* ... */ if( event.key.keysym.sym != SDLK_ESCAPE ) break; // Alles außer [ESC] ignorieren case SDL_QUIT: return false; // Programm sollte beendet werden default: break; }
Die Behandlung läuft als genau gleich wie bei der Tastatur auf und wir können sogar auch die Postion des Klicks abfragen. Das das nützlich sein kann ist glaube ich offensichtlich.
2.1.2. Und mit Bewegung
Neben den Tasten interessiert uns bei der Maus aber auch noch ihre Bewegung, durch die sie ja ein so beliebtes Eingabegerät geworden ist. Genau für diesen Zweck ist der dritte Nachrichtentyp SDL_MOUSEMOTION gemacht. Eine Nachricht von diesem Typ wird jedesmal verschickt wenn sich die Position der Maus geändert hat.
typedef struct{ Uint8 type; Uint8 state; Uint16 x, y; Sint16 xrel, yrel; } SDL_MouseMotionEvent;
Auch bei dieser Struktur sollten die Variabelennamen selbsterklärend sein. In x und y steht die aktuelle Position des Cursors in Pixelkoordinaten vom linken, oberen Fensterrand, oder im Vollbildmodus dem linken, oberen Bildschirmrand. Die Koordinaten nehmen auch hier wieder wie sonst auch von links oben nach rechts unten zu. In xrel und yrel bekommen wir dann auch noch die relative Bewegung der Maus zur vorherigen Position. In state finden wir dann noch den Status aller Maustasten aus dem wir mit Hilfe von geeigneten Bitmasken den Status aller einzelnen Tasten extrahieren können:
switch(event.type) { case SDL_MOUSEMOTION: std::cout << "Die Maus wurde um (" << event.motion.xrel << "|" << event.motion.yrel << ") Pixel bewegt und " << "befindet sich jetzt an der Position (" << event.motion.x << "|" << event.motion.y << ")\n" << "Dabei waren folgende Tasten gedrückt: " << (event.motion.state & SDL_BUTTON_LMASK ? "[Links] " : "") << (event.motion.state & SDL_BUTTON_RMASK ? "[Rechts] " : "") << (event.motion.state & SDL_BUTTON_MMASK ? "[Mitte] " : "") << std::endl; break; /* ... */ }
3. Ausblick
Mit dem jetzigen Wissensstand sollte es möglich sein einfache, interaktive Spiele zu erstellen. Weitere Tutorials werde ich verfassen sobald ich dazu Zeit habe. Angedacht habe ich beispielsweise eine Kamerasteuerung für 2D und auch 3D, aber genaueres hängt sowohl von meinen aktuellen Interessen als auch den mir geschrieben Vorschlägen hab. Bleibt nur noch mein Rat bis das nächste Tutorial so weit ist: "Üben, üben, üben!" ;-).
Wünsche, Anregungen und Kritik bitte über das Kontaktformular oder direkt an Diese E-Mail-Adresse ist gegen Spambots geschützt! Sie müssen JavaScript aktivieren, damit Sie sie sehen können. an mich schicken.