Bogenschießen

Aus toolbox_interaktion
Wechseln zu: Navigation, Suche
Interaktion hg final 2.jpg



Interactive Archery ist ein interaktives Spiel, wobei der Spieler mit Hilfe seiner Hände zielen und schießen kann und welches auf eine Leinwand übertragen wird. Dieses Projekt wurde innerhalb des Fachs Interaktion im Wintersemester 2012/13 realisiert. Neben Hardwarekomponenten(Kinect, Beamer), welche für die Steuerung und Projektion des Spiels veranwortlich sind, wurden auch verschiedene Softwarekomponenten(Processing, oscP5, Synapse) verwendet. Diese dienen zur Programmierung des Spiels und zur Verwertung der einkommenden Daten.


Systemübersicht

Folgende Komponenten wurden verwendet:


Hardwarekomponenten

Beamer

Damit das Spiel realitätsnäher wiedergespiegelt wird, projezieren wir es mittels eines Beamers auf eine größere Leinwand.

Kinect

Die Kinect ist eine von Microsoft entwickelte Hardware, welche zur Steuerung interaktiver Spiele gedacht ist. In unserem Fall ist sie das Herzstück des Programms. Über die Tiefendaten, welche die Kinect über einen Tiefensensor erfassen kann, lassen sich die Position der linken und rechten Hand ermitteln. Mit ihnen kann man sowohl das Zielen, die Stärke des Bogenschusses als auch den Abschusszeitpunkt bestimmen

Softwarekomponenten

Synapse

Synapse ist ein von Ryan Challinor entwickeltes Tool, welches uns die Möglichkeit bietet, die Positionsdaten der rechten und linken Hand zu erfassen, und sie mittels OSC an Processing zu übertragen.

oscP5

oscP5 ist eine Library für Processing. Hiermit ist es möglich, die von Synapse gesendeten OSC-Signale in unsere Entwicklungsumgebung einzubinden und diese folglich zu verarbeiten.

Processing

Processing ist eine auf Java aufbauende, objektorientierte Entwicklungsumgebung. Sie ermöglicht visuelle Elemente und Interaktionen zu kombinieren und zu programmieren. In unserem Fall ist Processing für die Darstellung des Spielhintergrundes, für die Steuerung des Spieles und für die Soundausgabe verantwortlich.

Vorgehensweise

Grundidee

Die Grundidee zur Realisierung unseres Projektes ist folgende: Kinect trackt den Spieler, Synapse greift die Daten der Joints(Fixpunkte) ab und diese werden durch das oscP5 Protokoll an Processing übertragen. Hierbei werden 3 Aufgaben realisiert:

- Das eigentliche Zielen: Kinect kann die Position eines vorher festgelegten Joints (in unserem Fall die linke Hand) berechnen. Dieser Punkt wird anschließend auf der Spieloberfläche als Fadenkreuz dargestellt.

- Stärke des Bogenschusses: Kinect bietet die Möglichkeit den Abstand zwischen festgelegten Joints(in unserem Fall rechte und linke Hand) zu berechnen. Je größer der Abstand, desto stärker wird geschossen.

- Auslösen des Bogenschusses: Verlässt die hintere Hand einen vordefinierten Bereich, wird der Schuss ausgelöst.

Um etwas mehr Leben in das Spiel zu bringen verwenden wir zwei unterschiedliche Audiospuren. Die eine wird als Loop abgespielt und dient als Hintergrundgeräusch. Die andere wird beim Abfeuern des Bogens abgespielt.

Die Zielscheibe bewegt sich zufällig über die Spieloberfläche. Trifft der Spieler die Scheibe erhält er Punkte. Nach 10 Schüssen ist das Spiel vorbei und die Endsumme wird ausgegeben.


Spielhintergrund

Der Hintergrund wurde mittels Illustrator erstellt und soll aufgrund der Farbgebung und Gestaltung Kinder, aber auch Erwachsene ansprechen


Interaktion background.jpg


Umsetzung

Um das Spiel wie gewollt umzusetzen, musste zunächst geklärt werden, wie die eigentliche dreidimensionale Handlung auf eine 2D-Spieloberfläche umgesetzt werden kann. Das Spiel wurde von uns iterativ entwickelt. Nachdem eine Verbindung der Kinect zu Processing über Synapse hergestellt werden konnte, stießen wir auf eine Demo, die ein Skellet des menschlichen Körpers abbildet. Der Link dazu: http://www.openprocessing.org/sketch/33885. Mithilfe dieser Demo konnte nun weiterverfahren werden. Die Demo spiegelt insgesamt 11 Punkte des Körpers wieder, die von der Kinect getrackt werden und über OSC (OpenSoundControl) über den sog. localhost an unser in Processing erstelltes Programm weitergegeben werden.

Funktionsweise der Weitergabe der Punkte über OSC

Die Funktionsweise der Weitergabe der getrackten Punkte über OSC wird hier auszugsweise zum besseren Verständnis für einen Punkt vorgestellt. Vereinfacht kann man es so erklären, dass zunächst die die beiden Bibliotheken OSCP5 und NetP5 importiert werden und dann Koordinaten festgelegt werden. Hier die die x und y-Position für die linke Hand (Die anderen Punkte wurden außen vor gelassen um die Übersicht behalten zu können). Es wird dann ein Objekt der OSCP5-Klasse und der Netzadresse angelegt. Für die Netzadresse wird 127.0.0.1 also der localhost und der Port 12346 festgelegt. Die OSCP5 Objekte erhalten dann über diesen localhost die OSC-Postionsdaten eines Punktes von der Kinect und speichern diese in den Koordinaten des Punktes in der IDE. Das ständige Aktualisieren der Punkte wird über die Funktion sendData() realisiert. Diese wird so oft ausgeführt, wie das Bild in der Sekunde gemalt wird. Hier in diesem Fall 25 mal (Framerate 25).


Quellcode in Processing 



import oscP5.*;
import netP5.*;

OscP5 oscP5;
NetAddress myRemoteLocation;

float lhx, lhy; Koordinaten

void setup() {

size(640, 480);
frameRate(25);
oscP5 = new OscP5(this, 12345);
myRemoteLocation = new NetAddress("127.0.0.1", 12346);

}

void draw() {
background(0);
sendData(); //refresh

ellipse(lhx, lhy, 30, 30);
fill(50, 50, 255);

}

void sendData() {

OscMessage myMessage = new OscMessage("/lefthand_trackjointpos");
myMessage.add(3);
oscP5.send(myMessage, myRemoteLocation);

}

/* incoming osc message. */
void oscEvent(OscMessage theOscMessage) {
// print("### received an osc message."+theOscMessage);
//print(" addrpattern: "+theOscMessage.addrPattern());
// println(" typetag: "+theOscMessage.typetag());

String v=theOscMessage.addrPattern();

if (v.equals("/lefthand_pos_screen")) {
float x = theOscMessage.get(0).floatValue();
float y = theOscMessage.get(1).floatValue();
lhx=x;
lhy=y;
}



Mithilfe dieser Informationen konnten wir überlegen, wie wir das für uns nutzen können. Wir haben dann die Anzahl der Punkte von 11 auf zwei für uns wichtige Punkte geschrumpft. Die linke und rechte Hand waren für unsere Zwecke völlig ausreichend.  Der eine Arm hält den Bogen und repräsentiert so auch den Zielpunkt und der andere Arm "zieht" den Bogen auf. 


Klasse für das Fadenkreuz


Fadenkreuz.JPG
















Hier die Erzeugung der Instanz des Fadenkreuzes und der Übergabe der Koordinaten. Das Fadenkreuz bewegt sich nun simultan mit den Koordinaten der linken Hand, bzw. wird immer an der Stelle des Punktes der linken Hand gezeichnet.


Fadenkreuzinstanz.JPG


Die rechte Hand wird ebenso gezeichnet. Dies wurde gemacht um beim Testen immer zu sehen, wo sich die rechte Hand befindet. Als nächstes musste ein Ziel her.


Klasse für die Zielscheibe


Zielscheibe.JPG



Die Zielscheibe bewegt sich nach einem zufälligen Muster auf dem Bildschirm auf und ab.

Ziel des Spieles ist es ja mit einem Bogen das Ziel zu treffen, also musste noch eine Klasse für den Pfeil implementiert werden.

Klasse für den Arrow


Arrow.JPG


Die Klasse Arrow wird bei einem Abschuss instanziert und erhält als Koordinaten die Koordinaten der Position, wo sich die linke Hand gerade befindet. Der Pfeil erhält also die Koordinaten, auf die gerade gezielt wird. Die übergegeben Koordinaten werden in temporäre Variablen gespeichert, damit sie nicht immer wieder überschrieben werden. Sie würden nämlich sonst immer die Koordinaten der linken Hand erhalten und so würde der Pfeil ständig seine Richtung ändern.


3D-Raum

Der nächste Meilenstein war die Lösung eines grundsäztlichen Problems. Das Spiel wird zweidimensional dargestellt, wir wollten aber eine Handlung in 3D umsetzen. Also ein Ziel in der Tiefe treffen. Die Lösung war hier, dass man in Processing einen 3D-Raum definieren kann, welcher in der Setup-Methode festgelegt wird.


Draw.JPG


Man hat nun 3 Richtungen in die man Objekte anordnen kann. X, Y und Z-Richtung. Mithilfe der Funktion "translate()" kann man nun die Objekte entsprechend platzieren. Translate(0, 0, 40) bedeutet, dass das Objekt 0 Pixel in x und y-Postion, aber 40 Pixel in Z-Richtung, also in die "Tiefe", verschoben wird. Mithilfe dieser Tatsache, konnten wir nun das Spiel realisieren.

Zunächst wurde das Hintergrundbild ganz nach hinten gelegt, da dies auf der untersten Ebene liegen muss.


Das ist die dazugehörige Funktion: 


public void hintergrund_malen() {

//Fügt das Hintergrundbild als background ein. Vorsicht.
//Größe des Processing-Ausgabefensters muss Größe des Bildes entsprechen.
pushMatrix();
translate(0, 0, -50);
image(img, -60, -60, 1380, 850);
popMatrix();
}


Auf gleicher Ebene kann nun die Zielscheibe gelegt werden. Dies wird gleich in der Klasse, bzw. dessen Kontruktor festgelegt. Vorsicht: Die Tiefe der Zielscheibe darf nicht kleiner sein als die des Hintergrunds, da sie sonst logischerweise nicht sichtbar wäre, weil sie ja hinter dem Hintergrund in der Tiefe liegen würde. Die Zielscheibe bewegt sich nun also über dem Hintergrund hin und her. Die linke und rechte Hand liegen ganz normal vorne, also haben keinen Tiefenwert. Erst der abgeschossene Pfeil bekommt dann einen Tiefenwert, der sich bei jedem Durchlauf von Draw verringert.


Bogenschuss

Stärke

Als nächstes wurde überelegt, wie es umgesetzt werden kann, dass die "Stärke" des Schusses beeinflusst werden kann. Die Lösung war, den Abstand zwischen den beiden Händen im Zeitpunkt des Abschusses zu messen und so einen Parameter festzusetzen, der die Stärke des Schusses steuert. Die Lösung war hier den Abstand der beiden Punkte in allen 3 Richtungen in Abhängigkeit voneinander zu messen. Dies wurde mit folgender Funktion umgesetzt. Es wird ein Wert errechnet und dieser dann als horizontaler Balken dargestellt. Der Wert wird ständig angepasst, sodass der Balken sich auch mit dem Abstand der rechten zur linken Hand verändert. Man kann so sehen, wie stark man "aufzieht".


Gain.JPG


Im Spiel wird hier entschieden, wie schnell der Pfeil in die Tiefe geschossen wird. Dies ist abhängig von dem Wert von distanceHands. Umso größer dieser Wert umso schneller geht der Pfeil in die Tiefe.


Gain2.JPG


Abschuss

Nun kommen wir zum Herzstück des Spiels: Der Abschuss des Pfeiles. Der Abschuss des Pfeiles wurde an zwei Bedingungen geknüpft. Zunächst muss der boolsche Wert canShoot true sein und die rechte Hand muss unter einen bestimmten y-Wert (hier 500) sein. Dies wurde dem echten Schießen nachempfunden. Es muss zunächst ein Pfeil vorhanden sein und die rechte Hand verändert ihre Positon ja auch nach dem Loslassen des Pfeils nach unten. Nachdem diese Kriterien also beide erfüllt sind, werden die Koordinaten der linken Hand an zwei temporäre Variablen übergeben. Mit diesen wird dann eine Instanz von einem Arrow erzeugt. Durch die Funktion arrow.movearrow(tiefe) wird der Pfeil nun mit jedem Durchlauf der Draw()-Funktion in die Tiefe bewegt. Zur Erinnerung: Der Wert der Tiefe richtet sich nach der "Stärke" des Abschusses über die Variable distanceHands. Gleichzeitig wird der Pfeil gemalt und der Button für die CanShoot-Anzeige auf false gesetzt, d.h. solange der Pfeil fliegt, kann kein neuer Pfeil abgeschossen werden und es wird geprüft, ob er die Zielscheibe getroffen hat. Dies wird über untenstehende If-Abfrage erledigt. Sollte der Pfeil getroffen haben, dann wird der Punktezähler erhöht und der canShoot-Wert auf false gesetzt. Der boolsche Wert für die Anzeige der Zielscheibe wird ebenfalls auf false gesetzt. Diese wird dann für eine gewisse Anzahl von Durchläufen von Draw nicht angezeigt um zu zeigen, dass die Zielscheibe getroffen wurde. Der Pfeilzähler wird logischerweise runtergezählt. Die Anzeige hierzu wird in einer anderen Funktion implementiert. Sollte der Pfeil die Zeilscheibe nicht getroffen und eine bestimmte Tiefe erreicht haben, wird der Pfeilzähler auch dekrementiert und der boolsche Wert für canShoot auf false gesetzt. 


Abschuss.JPG


Nachladen

Das "Nachladen" eines Pfeils wird mit folgender Funktion umgesetzt. Der boolsche Wert canShoot wird nach einem Abschuss auf false gesetzt. Erst nachdem man dann mit der rechten Hand einen Button überfährt, wird dieser Wert wieder auf true gesetzt und man kann einen neuen Pfeil abschießen. Der Button ändert dann seine Farbe von Rot auf Grün.


Nachladen.JPG

Fazit

Mit der Umsetzung unseres Spiels sind wir sehr zufrieden und die Arbeit hat allen beteiligten Spaß gemacht. Ob das Projekt in Zukunft erweitert wird ist noch nicht sicher.

Abschließend möchten wir die gute technische Ausstattung der Hochschule loben und bedanken uns bei der hilfreichen Unterstützung duch die Betreuer.


Links

SOURCE CODE - iA

processing.org/

www.xbox.com/de-DE/Kinect

synapsekinect.tumblr.com/

www.sojamo.de/libraries/oscP5/