DWX13-Nachtrag: Sprünge mit Kinect und Reactive Extensions erkennen

Bei der Developer Week 2013 habe ich einen Vortrag über Reactive Extensions gehalten. Die Folien sind auf Slideshare zu finden. Im Laufe des Vortrags habe ich einen Teil eines konsolenbasierten Jump-and-Run-Spiels live kodiert, das eine Kinect als Eingabemedium verwendet. Die erste Aufgabe war es, zu erkennen, wenn ein Spieler (der Freiwillige auf der Bühne) in die Luft springt. Hier ist der Code aus der Demo:

Dieser Code beinhaltet viele Vereinfachungen zu Demonstrationszwecken, kann aber trotzdem gut verwendet werden, um einige Konzepte, die hinter Reactive Extensions stecken, zu erklären. Ich werde die einzelnen Teile erklären und die var durch die spezifischen Typen ersetzen:

Diese Zeile greift auf die eine Kinect zu, die an den Rechner angeschlossen ist (und wirft eine Exception, wenn nicht genau eine Kinect angeschlossen ist).

Diese Anweisung erzeugt aus einem klassischen .NET-Event SkeletonFrameReady ein IObservable, indem es Rx beschreibt, wie man sich von dem Event an- und abmeldet.

Interessanterweise beinhaltet die Klasse SkeletonFrameReadyEventArgs überhaupt keine Eigenschaften sondern nur eine Methode public SkeletonFrame OpenSkeletonFrame();, über die man an die Skelettdaten gelangt. Die Instanz von SkeletonFrame muss daraufhin innerhalb von 1/30 Sekunde wieder über Dispose() zerstört werden.

Jetzt haben wir ein IObservable, das eine Liste an Skeletten von sich gibt, sobald sich Personen vor dem Kinect-Sensor befinden.

Dieser Code extrahiert die Gelenke aus dem einen Skelett, wenn es den Zustand SkeletonTrackingState.Tracked hat.

Hierdurch wird die durschnittliche vertikale Position des linken und rechten Fußes ermittelt. Dies ist eine Vereinfachung, da es auch möglich wäre, den Algorithmus zu überlisten, indem man einen Fuß doppelt so hoch hebt als man eigentlich mit zwei Füßen hätte springen sollen. Als Alternative könnte man im Select() beide Füße extrahieren.

Hier wird der eigentliche Sprung detektiert. Das Idee ist die folgende: Um gesprungen zu sein, müsste ein Spieler beide Füße in einer kurzen Zeitspanne erst tief, dann hoch und dann wieder tief haben. Um das zu ermitteln, analysieren wir eine Zeitspanne von einer Sekunde. Nach dieser Analyse bewegen wir und im Zeitstrahl 200 Millisekunden weiter und analysieren wieder. Diese Magie liefert und die Methode Buffer(), die eine Extension Method von IObservable ist. Innerhalb dieser Sekunde ermitteln wir den Maximalwert und bilden die Differenz zum ersten und letzten Wert des Zeitfensters. Zur Vereinfachung wird die Differenz mit einem hartkodierten Wert verglichen. Wenn der Algorithmus zuschlägt, enthält das resultierende IObservable einen Strom an entweder “jumped” oder “didn’t jump”.

Wenn man an dieser Stelle ein jumped.Subscribe(Console.WriteLine); einfügt, sieht man eine lange Folge von “didn’t jump” unterbrochen durch ein paar Vorkommnisse von “didn’t jump” an den Stellen, an denen der Spieler in die Luft gesprungen ist.

Durch den Aufruf von DistinctUntilChanged() gibt das IObservable nur einen Wert aus, wenn er sich vom Vorgängerwert unterscheidet. Die nächste Zeile filter dieses noch auf den Wert “jumped”, d.h. es wird immer einmal “jumped” ausgegeben, wenn der Spieler in die Luft springt.

Diese Zeile gibt das resultierende IObservable auf der Konsole aus.

Hierdurch wird der Kinect-Sensor gestartet.

Die Präsentation hat viel Spaß gemacht. Vielen Dank an den Freiwilligen! Dieses Jahr werde ich wieder auf der Developer Week sprechen, mit einem Vortrag über Cross-Plattform Mobile mit C#.