Logo gameLib.de Coding
In diesem Bereich befinden sich die gesammelten Artikel zu den Themen Programmiersprache, Algorithmen und Programmiertechniken im Allgemeinen.

It's magic - Mit Mausgesten zaubern

Tastencombos sind out, als echter Magier fuchtelt man wild durch die Gegend. Allerdings hat nicht jeder Nutzer eine Kamera an seinem Rechner angeschlossen oder ist willens ein paar Stunden lang vor seinem Rollenspiel rumzuhampeln. Eine Alternative zur normalen Maus und Tastatursteuerung wären Mausgesten, mit denen man magische Symbole auf den Schirm zeichnet. Dieser Artikel beschäftigt sich mit den Grundlagen der Erkennung von Mausgesten. Umgesetzt ist das ganze in SDL und C++, entsprechend sind auch Vorkenntnisse damit zum Verständniss des Codes sinnvoll. Eine kleine Einführung in die notwendigen Grundlagen in SDL kannst Du im Artikel SDL und Spieleentwicklung finden. Den Quellcode des Beispiels kannst Du hier downloaden.



Theorie

Die Theorie hinter der Mausgestenerkennung ist einfach. Zunächst einmal wird der Bereich auf dem "gezaubert" wrid in ein grobes Raster unterteilt, um eine Toleranz bei der Formerkennung zu schaffen. Würde man die Formen
Bild
pixelgenau testen, so ergäbe sich ein aufwändigerer Rechenprozess oder der Nutzer müsste auch pixelgenau zeichnen. Zusätzlich wird noch eine Fehlertoleranz eingeführt, da im Eifer des Gefechts mit der Maus nicht die exakte horizontale oder vertikale Ausrichtung eingehalten wird. Denkbar wäre bei der Implementierung, dass man abhängig von der Exaktheit entsprechend starke oder schwache Zaubersprüche zaubern lässt.

Um beliebig auf dem Monitor zeichnen zu können empfiehlt es sich ein mittleres Raster zu nehmen, den Offset wegzurechnen und dann in ein grobes Raster umzurechnen. Dadurch kann man an einer beliebigen Position zeichnen ohne die Dimensionen des Bildschirms einhalten zu müssen.

Beispielcode

Der folgende Code beschränkt sich auf die Grundlagen der Erkennung in vorgegebenen Dimensionen. Eine Offsetberechnung und Vergröberung des Rasters ist nicht schwer umzusetzen, allerdings würde der zusätzliche Code das Verständniss der Grundlagen erschweren.

Für eine einfachere Ausgabe, verwenden wir die IOStreams von C++ und es wird die Standardbibliothek von SDL eingebunden. Wir deklarieren alle notwendigen SDL-Variablen die wir für das Testprogramm benötigen. SDL_Surface für das Fenster und SDL_Event für die Abfrage der Maus.


#include <iostream.h>
#include "SDL.h"

int main()
{
SDL_Surface *fenster;
SDL_Event event;
...


Wir definieren eine Variable als Abbruchbedingung für die "Hauptprogramm-Schleife". Dann die Werte für den Aufbau unseres Rasters. auflX und auflY sind die Auflösung, also die Anzahl der Rechtecke im Fenster. Die FeldGroesse ist die Breite und Höhe eines einzelnen Rechtecks in Pixeln. Wir brauchen das nachher zum Umrechnen der tatsächlichen Pixelkoordinaten auf das Raster. fensterBreite und fensterHoehe sind selbsterklärend.


...
int abbruchbedingung = 0;

int auflX = 5,
auflY = 5;

int FeldGroesse = 80;

int fensterBreite = FeldGroesse * auflX,
fensterHoehe = FeldGroesse * auflY;
...


Zauber steht für die Art des Zaubers (1: Feuerzauber, 2: Eiszauber), der auf der Konsole ausgegeben wird. Dann kommt der wichtige Part, wir basteln uns vordefinierte Muster für die Erkennung. Da die Gesten in einem 5 x 5 grossen Array gespeichert werden, haben die Zauber-Muster auch diese Dimensionen. Der Feuerzauber ist dabei eine Diagonale von links oben nach rechts unten und der Eiszauber ein rechter Winkel durch die rechte obere Ecke.

...
int Zauber = 0;
int Feuer[5][5] =
{{1,1,0,0,0},
{0,1,1,0,0},
{0,0,1,1,0},
{0,0,0,1,1},
{0,0,0,0,1}};

int Eis[5][5] = {{1,1,1,1,1},
{0,0,0,0,1},
{0,0,0,0,1},
{0,0,0,0,1},
{0,0,0,0,1}};
...


Unser Raster muss von Anfang an leer sein, also nur aus Nullstellen bestehen. Deswegen iterieren wir durch die x und y Koordinaten, und füllen das Raster an jeder Position mit einer 0. Dannach initialisieren wir den Grafikmodus von SDL und öffnen ein Fenster.

...
int Raster[auflY][auflX];
for(int y = 0; y < auflY; y++) {
for(int x = 0; x < auflX; x++) {
Raster[y][x] = 0;
}
}

if ( SDL_Init( SDL_INIT_VIDEO ) < 0 ) {
cout << "SDL konnte nicht initialisiert werden: " << SDL_GetError() << endl;
exit(1);
}
atexit(SDL_Quit);

fenster = SDL_SetVideoMode(fensterBreite ,fensterHoehe ,16 ,SDL_SWSURFACE);
if ( fenster == NULL ) {
cout << "Konnte Fenster nicht bauen: " << SDL_GetError() << endl;
exit(-1);
}
...


Wir definieren einen Wahrheitswert um auslesen zu können wann gezaubert wird und wann nicht. Dieser ist zunächst falsch, wenn eine Maustaste gedrückt wird ist er wahr, und wenn man die Maustaste wieder loslässt ist er wieder falsch. Ein wichtiger Punkt sind die hier definierten Fehlertoleranzen anzahlFehlerFeuer und anzahlFehlerEis. Da auf eine Übereinstimmung des Rasters mit den Fehlermustern anhand der X und Y Koordinaten getestet wird, würden ja nur exakte Übereinstimmungen zutreffen. Deshalb die Fehlertoleranzen. Man verringert diesen Wert einfach je nicht zutreffender Übereinstimmung und wenn er kleiner als 0 ist schliesst es ein bestimmtes Muster aus. Es empfiehlt sich bei diagonalen und runden Formen eine etwas höhere Fehlertoleranz zu verwenden, da bei der Diskretisierung der Form Treppeneffekte auftreten (Aliasing) und diese bei jedem durchlauf stark variieren.

...
bool drag = false;

int anzahlFehlerFeuer = 2,
anzahlFehlerEis =2;
...


Die "Hauptprogrammschleife" wird gestartet und läuft so lange, bis unsere Abbruchbedinung auf 1 gesetzt wird. Dann fragen wir mittels SDL die Eingabegeräte und Systemereignisse ab. Wird eine Maustaste gedrückt so wird unser Zauberwert drag auf wahr gesetzt.

...
while(abbruchbedingung != 1)
{
while( SDL_PollEvent( "event ) ) {
switch( event.type ) {

case SDL_MOUSEBUTTONDOWN:
drag = true;
break;
...


Bewegt der Benutzer die Maus und ist der Zauberwert drag wahr, dann wird unser Raster an der Stelle wo sich die Maus darin befindet mit 1 überschrieben. Wir benutzen dazu eine einfache Umrechnung indem wir die realen Pixelkoordinaten durch die Grösse einer Rastereinheit teilen. Damit hätten wir die "gezeichnete" Form gerastert.

...
case SDL_MOUSEMOTION:
if(drag) {
Raster[event.motion.y / FeldGroesse][event.motion.x / FeldGroesse] = 1;
}
break;
...


Der letzte Schritt ist der Vergleich der gerasterten Form mit den vorgegebenen Mustern. Das geschieht dann, wenn der Benutzer die Maustaste wieder loslässt, dadurch hat er im Grunde seinen Zauberspruch abgeschlossen und der Zauberwert drag wird deshalb auch auf falsch gesetzt. Hier zudem auch die Werte der Fehlertoleranzen dekrementiert und dem Wert Zauber nach dem Vergleich mit dem Raster der entsprechende Zauber zugewiesen. Dannach wird das Raster wieder mit Nullen gefüllt um für die nächste Form vorbereitet zu sein. Zur Kontrolle wird sowohl das "gezeichnete" Muster, sowie der Zauberspruch auf der Konsole ausgegeben.

...
case SDL_MOUSEBUTTONUP:
drag = false;
for(int y = 0; y < auflY; y++) {
for(int x = 0; x < auflX; x++) {
if(Feuer[y][x]!=Raster[y][x])
anzahlFehlerFeuer--;
if(Eis[y][x]!=Raster[y][x])
anzahlFehlerEis--;
cout<<Raster[y][x];
}
cout << endl;
}

if(anzahlFehlerFeuer>-1)
Zauber = 1;
anzahlFehlerFeuer = 2;

if(anzahlFehlerEis>-1)
Zauber = 2;
anzahlFehlerEis = 2;

for(int y = 0; y < auflY; y++) {
for(int x = 0; x < auflX; x++) {
Raster[y][x] = 0;
}
}

switch( Zauber ) {
case 1:
cout << "Feuerzauber" << endl;
break;
case 2:
cout << "Eiszauber" << endl; break;
}

Zauber = 0;
cout<<endl;
break;

case SDL_QUIT:
abbruchbedingung = 1;
}
}
}
return 0;
}


Das wars auch schon, solltest Du Fragen, Anregungen oder Kritik haben, einfach eine Mail an info@gamelib.de senden.

Marco Hertwig - www.gamelib.de - 27.08.2005
Impressum

Valid XHTML 1.0 Transitional Valid CSS!