Airhockey

Aus toolbox_interaktion
Wechseln zu: Navigation, Suche

Semesterarbeit des Fachbereichs Elektrotechnik WS 14/15

AirH SpielAktion.jpg

Beschreibung

Die Idee war es das Spiel Airhockey interaktiv umzusetzen. Statt eines teuren und unflexiblen Airhockey-Tischs soll ein herkömmlicher Tisch verwendet werden können. Zudem ist es möglich weitere Spiele zu entwickeln und auf dem gleichen Aufbau zu spielen, zum Beispiel Billard Pro. Der Grundaufbau ist neben der Tischplatte ein darüber angebrachter DLP-Projektor und eine RGB-Kamera wahlweise auch eine einfache Webcam. Zudem benötigt man natürlich auch noch einen PC oder Laptop um das Programm auszuführen. Die beiden Schläger werden durch die Handbewegungen der Spieler gesteuert, dazu benötigt jeder Spieler einen anders farbigen Handschuh. Aufgrund des gewählten Spiellayouts sollten die Handschuhe nicht blau sein.

Spielprinzip

Spielkonfiguration

Nach dem Starten der AirHockey.exe muss die Handschuhfarbe der beiden Spieler angelernt werden. Dies geschieht durch Drücken der Tasten „1“ (Spieler 1), Taste „2“ (Spieler2) und des jeweiligen Selektieren des Handschuhes per Mausklick. Nachdem beide Farben angelernt wurden kann der Spielmodus durch Taste „3“ aktiviert werden. Mit der Taste „4“ kann man sich die beiden Binärbilder der detektierten Bereiche anzeigen lassen, um etwa die Kamera zu justieren.

Spielablauf

Gewonnen hat der Spieler der mindestens 3 Tore geschossen und einen Vorsprung von 2 Punkten hat. Zu Beginn des Spieles befindet sich der Puck in der Spielfeldmitte. Erzielt ein Spieler ein Tor wird der Puck vor dem gegenüberliegenden Spieler platziert. Um das Spiel zurückzusetzen muss Backspace gedrückt werden, es werden daraufhin die Punkte auf 0 gesetzt und der Puck in der Spielmitte platziert.

Hardware

AirH Aufbau.JPG

Kamera

Verwendet wurde eine IDS UI-1225LE-C mit einer Auflösung von 768x420. Diese wurde mit einem beweglichen Stativ an die Deckentraversen angebracht und auf das Spielfeld ausgerichtet. Da die maximale Kabellänge von USB-Leitungen begrenzt ist wurde ein USB zu RJ45 Converter verwendet.

Projektor

Der DLP Projektor wurde ebenfalls an die Deckenkonstruktion montiert und das HDMI Signal musste aufgrund der Leitungslänge ebenfalls auf RJ45 gewandelt werden.

Software

Als Entwicklungsumgebung wurde Code::Blocks 13.12 verwendet. Um die benötigten Funktionalitäten möglichst einfach einzubinden haben wir OpenFrameworks genutzt. Über den Projektgenerator kann man die benötigten Erweiterungen in ein Projekt einbinden.

Spielfeld

Zuerst wird in der Funktion draw() das Spielfeld gezeichnet. Hierzu werden nur die Standardbibliotheken von OpenFrameworks verwendet.

   //Aussen Rand (Rechteck dunkelblau)
   ofSetColor(0,0,255);
   ofRect(0, 0, BILDSCHIRMBREITE, BILDSCHIRMHOEHE );
   //Spielfeld in weiß
   ofSetColor(255,255,255);
   ofRect(BILDSCHIRMBREITE/100,BILDSCHIRMHOEHE/100,BILDSCHIRMBREITE-BILDSCHIRMBREITE/50, BILDSCHIRMHOEHE-BILDSCHIRMHOEHE/50);
   ofSetColor(0,0,0);
   //Tore
   ofRect(0, BILDSCHIRMHOEHE/3, BILDSCHIRMBREITE/50, BILDSCHIRMHOEHE/3);
   ofRect(BILDSCHIRMBREITE - (BILDSCHIRMBREITE/50) , BILDSCHIRMHOEHE/3, BILDSCHIRMBREITE/50, BILDSCHIRMHOEHE/3);
   ofSetColor(0,0,0);
   //Mittellinie
   ofSetLineWidth(5);
   ofLine(BILDSCHIRMBREITE/2, 0, BILDSCHIRMBREITE/2, BILDSCHIRMHOEHE);
   //Formatierung der Kreise setzen
   ofNoFill();
   ofSetLineWidth(5);
   //Mittelkreis
   ofSetCircleResolution(100);
   ofCircle(BILDSCHIRMBREITE/2, BILDSCHIRMHOEHE/2, BILDSCHIRMBREITE/10);
   //Torkreise
   ofEllipse(0, BILDSCHIRMHOEHE/2, BILDSCHIRMBREITE/3, 2*BILDSCHIRMHOEHE/3);
   ofEllipse(BILDSCHIRMBREITE, BILDSCHIRMHOEHE/2, BILDSCHIRMBREITE/3, 2*BILDSCHIRMHOEHE/3);

Physik

Um die physikalischen Eigenschaften der Banden, Puck und Schläger umzusetzen, wird das Addon Box2D eingebunden. Die Funktion erstelleBanden() legt diese Eigenschaften fest und wird in der setup() – Funktion aufgerufen.

 void ofApp::erstelleBanden()
 {
   //Erstelle Banden dynamisch
   Line1.addVertex(0,0);
   Line1.addVertex(BILDSCHIRMBREITE,0);
   //Objekt in die Welt werfen
   Line1.create(box2d.getWorld());
   //Bottom
   Line2.addVertex(0, BILDSCHIRMHOEHE);
   Line2.addVertex(BILDSCHIRMBREITE,BILDSCHIRMHOEHE);
   Line2.create(box2d.getWorld());
   //Links bis Tor
   Line3.addVertex(0,0);
   Line3.addVertex(0,BILDSCHIRMHOEHE/3);
   Line3.create(box2d.getWorld());
   //Links von Tor bis Bottom
   Line4.addVertex(0, BILDSCHIRMHOEHE*2/3);
   Line4.addVertex(0, BILDSCHIRMHOEHE);
   Line4.create(box2d.getWorld());
   //Rechts bis Tor
   Line5.addVertex(BILDSCHIRMBREITE,0);
   Line5.addVertex(BILDSCHIRMBREITE,BILDSCHIRMHOEHE/3);
   Line5.create(box2d.getWorld());
   //Rechts von Tor bis Bottom
   Line6.addVertex(BILDSCHIRMBREITE,BILDSCHIRMHOEHE*2/3);
   Line6.addVertex(BILDSCHIRMBREITE, BILDSCHIRMHOEHE);
   Line6.create(box2d.getWorld());
 }

Die Schläger und der Puck werden mit den folgenden Funktionen erstellt welche in setup() aufgerufen werden.

   //Erstelle Puck
   puck.get()-> setPhysics(3.0, 0.53, 0.2);    //density, bounce, friction
   puck.get()->setup(box2d.getWorld(), BILDSCHIRMBREITE/2, BILDSCHIRMHOEHE/2, RADIUS_PUCK);
   //Erstelle Schläger bat 1 Spieler 1
   bat_1.get()-> setPhysics(3.0, 0.53, 0.2);
   bat_1.get()-> setup(box2d.getWorld(), BILDSCHIRMBREITE/8, BILDSCHIRMHOEHE/2, RADIUS_BAT);
   //Erstelle Schläger bat 2 Spieler 2
   bat_2.get()-> setPhysics(3.0, 0.53, 0.2);
   bat_2.get()-> setup(box2d.getWorld(), BILDSCHIRMBREITE*7/8, BILDSCHIRMHOEHE/2, RADIUS_BAT);

Farbdetektion

Die Farbwerte werden aus der Konfiguration des Spieles entnommen, für jeden Spieler jeweils einen Wert für die Sättigung und Farbe. Das eingelesene Bild wird nun für jeden Spieler auf diese Farbe untersucht und binarisiert. Aus dem Binärbild, kann nun die Kontur und dadurch der Schwerpunkt berechnet werden. Für die Farberkennung ist es empfehlenswert, dass das Addon OpenCV in das Projekt eingebunden wird.

 if (movie.isFrameNew())
   {
       //copy webcam pixels to rgb image
       rgb.setFromPixels(movie.getPixels(), w, h);
       //mirror horizontal
       rgb.mirror(false, true);
       //duplicate rgb
       hsb = rgb;
       //convert to hsv
       hsb.convertRgbToHsv();
       hsb.convertToGrayscalePlanarImages(hue, sat, bri);
     
       //Berechnung der Toleranzen von Sättigung und Farbe
       int Hue1_plus = findHue1+10;
       int Hue1_minus = findHue1-10;
       int Sat1_plus = findSat1+10;
       int Sat1_minus = findSat1-10;
 
       int Hue2_plus = findHue2+10;
       int Hue2_minus = findHue2-10;
       int Sat2_plus = findSat2+10;
       int Sat2_minus = findSat2-10;
       //Bestimmung Position Player 1
       for (int i = 0; i < w*h; i++)
       {
           filterhue1 = ofInRange(hue.getPixels()[i],Hue1_minus, Hue1_plus) ? 255 : 0; 
           filtersat1 = ofInRange(sat.getPixels()[i],Sat1_minus, Sat1_plus) ? 255 : 0;
           filtered1.getPixels()[i] = ((filterhue1 )&& (filtersat1)) ? 255 : 0;
       }
       // Kontur und Schwerpunkt von Player 1
       filtered1.flagImageChanged();
       contours1.findContours(filtered1, 50, w*h/2, 1, false );
       if(contours1.blobs.size() > 0)
       {
           cent_x1    = contours1.blobs[0].centroid.x;
           cent_y1    = contours1.blobs[0].centroid.y;
       }
       // Bestimmung Position Player2
       for (int i = 0; i < w*h; i++)
       {
           filterhue2 = ofInRange(hue.getPixels()[i],Hue2_minus, Hue2_plus) ? 255 : 0;
           filtersat2 = ofInRange(sat.getPixels()[i],Sat2_minus, Sat2_plus) ? 255 : 0;
           filtered2.getPixels()[i] = ((filterhue2 ==255 )&& (filtersat2 == 255)) ? 255 : 0;
       }
       filtered2.flagImageChanged();
       contours2.findContours(filtered2, 50, w*h/2, 1, false );
       //KOntur und Schwerpunkt Player 2
       if(contours2.blobs.size() > 0)
       {
           cent_x2    = contours2.blobs[0].centroid.x;
           cent_y2    = contours2.blobs[0].centroid.y;
       }
   }

Merge

Mit den gewonnen Koordinaten der Farbdetektion, können nun die Positionen der Schläger upgedatet werden. Die Positionierung der Schläger muss nacheinander erfolgen. Der Schläger 1 wird gegriffen und auf die neue Position geschoben. Nun wird die Box2D-Welt geupdated und der Schläger 1 wieder losgelassen um daraufhin dasselbe für Schläger 2 zu machen.

   box2d.registerGrabbing();
   //Greife Objekt Schläger1 an seiner Position
   box2d.grabShapeDown(bat_1->getPosition()[0],bat_1->getPosition()[1]);
   //Bewege Schläger1 an die Position von erster Hand
   box2d.grabShapeDragged(cent_x1*FAKTOR_X,cent_y1*FAKTOR_Y);
   box2d.update();
   //Schläger1 loslassen
   box2d.grabShapeUp(bat_1->getPosition()[0],bat_1->getPosition()[1]);
   //Greife Objekt Schläger1 an seiner Position
   box2d.grabShapeDown(bat_2->getPosition()[0],bat_2->getPosition()[1]);
   //Bewege Schläger1 an die Position von erster Hand
   box2d.grabShapeDragged(cent_x2*FAKTOR_X,cent_y2*FAKTOR_Y);
   box2d.update();
   //Schläger2 loslassen
   box2d.grabShapeUp(bat_2->getPosition()[0],bat_2->getPosition()[1]);

Team

Fabian Beyerlein, Torben Groß, Annekatrin Heder, Julian Wölk

Anhang

Hier sind alle Projektdaten hinterlegt.