OSC in Unity

Aus hyperdramatik
Wechseln zu: Navigation, Suche

OSC in Unity

Um von anderen Programmen (z.B. Processing) Daten an Unity zu senden, verwenden wir das IP Protokoll und eine OSC Library. Das heisst, das Device auf dem das Unity Programm laufen soll (Computer oder Tablet) muss mit einem Netzwerk verbunden sein und eine IP-Adresse besitzen. OSC basiert wiederum auf dem UDP Protokoll, das vor allem in lokalen Netzwerken gut funktioniert. Mehr Infos zu Protokollen und Netzwerken gibt es auf der Netzwerk Seite.

externen OSC Code in unser Projekt einfügen

Herunterladen des externen OSC Codes

Code für die Implementierung von OSC in Unity gibt es auf der github Seite von Thomas Fredericks unter Releases, eine kurze Erklärung und ein Beispiel auf seiner Webseite. Für uns ist der Release For Unity 2018 relevant (funktioniert auch bis Unity 2020.3).

Nachdem wir den SourceCode als .zip heruntergeladen haben, erstellen wir am Besten ein neues Unity Projekt mit dem Standard Template (3D). Wenn ihr bereits ein Unity Projekt habt, in dem ihr OSC verwenden möchtet, dann könnt ihr diesen Schritt gerne überspringen. :-)

Die von uns hier verwendete OSC-Implementierung funktioniert nachgewiesen auf Unity 2019.4 und Unity 2020.3. Gerne könnt ihr 2020.3 nutzen.
New-osc-test-project.jpg

Einfügen des externen OSC Codes in unser Projekt

Nachdem wir das Projekt erstellt haben, und der Unity Editor das Projekt geöffnet hat, kopieren wir den Inhalt der heruntergeladenen .zip Datei in den Projektordner unseres neu erstellten Projekts, wie hier angezeigt:
Copy osc to new unity project.jpg

Windows fragt, ob wir einige der zu kopierenden Dateien überschreiben oder überspringen wollen. In diesem Fall würden wir sie überspringen. Beides ist aber okay.

Wenn wir zurück zu Unity wechseln, werden nach einem kurzen Moment des Importierens im Project Tab einige neue Dateien angezeigt:
Imported-Osc-unity.jpg

Aufräumen

Für die Anleitung hier im Wiki werden wir nicht vom mitgelieferten beispiel ausgehen, deswegen würde ich die 5 Dateien die von OSC direkt in den Assets Folder kopiert worden sind direkt in den Extras-Ordner verschieben, um die Übersicht im Projekt zu behalten:
Move-unnecessary-example-osc-unity.jpg
Ebenso lohnt es sich, direkt im Assets Folder einen Ordner zu erstellen, den wir Scripts nennen, und in dem wir unsere eigenen Scripte speichern werden:
Unity-scripts-folder-osc-example.jpeg

OSC in unserer Szene benutzen

Nachdem wir den OSC Code erfolgreich ins Projekt importiert haben, können wir nur beispielhaft damit arbeiten. Hierfür müssen wir das externe OSC.cs Script auf ein GameObject in unserer Szene legen. Wir wollen im weiteren Verlauf Scripte schreiben, die auf die Funktionen des OSC.cs Scripts zugreifen können. Dafür müssen wir das OSC.cs Script in ein GameObject in der Szene einfügen.

Einfügen des OSC Scripts in die Szene

Dafür erstellen wir am Besten ein neues, leeres Gameobject in unserer Hierarchy und nennen es "OSC"(wir könnten es auch anders nennen) :
Create-osc-gameobject.jpg
Dann wählen wir das eben erstellte Gameobject in unserer Hierarchy an und ziehen das OSC.cs Script aus dem Ordner OSC in unserem Project Tab in den Inspector:
Place-osc-script-on-gameobject.jpg

Einstellungen im OSC Script

Wenn wir das Script eingefügt haben, können wir einige Variablen ändern, die mit unserer Nutzung von OSC im weiteren Verlauf zu tun haben:
Osc-script-explained.jpg

  • inPort - steht für den Port, auf dem das OSC Script nach ankommenden OSC-Messages sucht. Im Processing OSC Beispiel ist der Port auf 12000 gesetzt, hier ist er Standardmässig auf 6969 gesetzt. Es lohnt sich, das direkt zu ändern, damit Processing und Unity direkt miteinander kommunizieren können.
  • outIP - steht für die IP Adresse, an die unser OSC-Script im weiteren Verlauf OSC-Messages schicken kann.
  • outPort - steht für den Port, an dem geschickte OSC-Messages ankommen werden.

Wichtig: der inPort wird von Unity für das Betriebssystem reserviert, wenn wir auf Play drücken. Das heisst, kein anderes Programm kann dann auf diesen Port zugreifen, bzw. es gibt eine Fehlermeldung, wenn ein Anderes Programm schon diesen Port reserviert hat. Wenn ihr das Processing Beispiel gleichzeitig laufen habt können nicht beide Programme den Port 12000 reservieren.

OSC Nachrichten empfangen

Nun möchten wir über ein eigenes Script auf die OSC-Funktionalität des OSC Scripts zugreifen. Es gibt sehr viele verschiedene Wege das zu tun. Der hier angeführte Weg macht viele Dinge, die im weiteren Verlauf eleganter gelöst werden können. Es wird Kekse für elegantere Lösungen geben.

  • Hierzu erstellen wir ein neues, leeres GameObject in der Hierarchy und geben ihm den Namen "OSCempfangen":
  • Daraufhin erstellen wir in unserem Scripts-Ordner im Project Tab ein neues C# Script und nennen es OscReceive.cs (Gross-Kleinschreibung beachten! Scripte immer mit Grossbuchstaben beginnen!). Der Unterschied in der Namensgebung zwischen dem Gameobject und dem Script soll andeuten, dass diese Namen frei gewählt sind und nicht etwa eine versteckte Unity Funktionalität andeuten. Es lohnt sich, Scripte und Gameobjects nach dem zu benennen, was sie tun.
  • Schliesslich legen wir unser neu erstelltes Script auf das neu erstellte Gameobject, so dass es sich in unsere Szene befindet und ausgeführt wird. Daraufhin können wir mit der eigentlichen Programmierung beginnen.

Osc-oscreceive-setup.jpg

In Code auf das externe OSC Script zugreifen

Wir haben nun in unserer Szene zwei Scripte als Components auf jeweils einem GameObject:

  • Das OSC-Script, dass wir von extern haben.

und:

  • Unser OscReceive Script, das wir jetzt schreiben wollen.

Wir möchten von unserem eigenen OscReceive-Script aus auf die Funktionen und Variablen des OSC Scripts zugreifen. Hierzu müssen wir in unserem Code eine Verbindung über eine Variable schaffen, durch die wir auf die Funktionen und Variablen des anderen Scripts zugreifen können:
Osc-connect-to-osc-cSharp.jpg

Ihr erinnert euch sicherlich, dass Scripte in Unity immer auch als Typ genutzt werden können, um von "aussen" auf alle Variablen und Funktionen dieses Scripts zugreifen zu können. Wir wollen also durch eine Variable vom Typ OSC, die wir "myOSC" nennen (könnte auch anders heissen), auf die Funktionen und Variablen des OSC-Scripts zugreifen.
Wir definieren diese Variable als public, damit wir sie im Unity Editor mit dem bereits in der Szene befindlichen OSC-Script verbinden können:
Connect-osc-script-in-editor.jpg

Nachdem wir diese Verbindung im Unity Editor geschaffen haben, können wir nun mit Hilfe von "myOSC" auf die Funktionalität des OSC Scripts zugreifen.

OSC Messages im eigenen Script empfangen

Um im eigenen Script OSC Messages zu empfangen, müssen wir selbst eine Funktion festlegen, die aufgerufen wird, wenn eine neue OSC-Message an unserem inPort ankommt. Das tun wir, indem wir folgende Funktion im OSC Script über unsere myOSC Variable ausführen:

SetMessageHandler(funktionsname);

Dabei können wir uns den Funktionsnamen aussuchen. Er beschreibt eine Funktion, die in unserem Script stehen muss, und die ausgeführt wird, wenn messages ankommen. Ich schlage vor, wir nutzen den selben Namen wie bei Processing: OscEvent.

Da wir diese Funktion nur einmal ausführen müssen, um die Verbindung von "Message kommt an" zu "Funktion wird ausgeführt" einzustellen, macht es Sinn, diesen Funktionsaufruf in die "Start" Funktion unseres Scripts zu schreiben:

 public OSC myOSC;

   // Start is called before the first frame update
   void Start()
   {
       myOSC.SetAllMessageHandler(OscEvent);
   }

Als nächstes müssen wir die Funktion selbst schreiben und festlegen, was passieren soll, wenn eine OscMessage ankommt. Wir fügen also eine neue Funktion in unser Script ein, die OscEvent heisst (weil wir sie so genannt haben), und die als Parameter eine Variable vom Typ OscMessage hat, die wir z.B. receivedMessage nennen können (weil wir es wollen, nicht müssen). Diese Funktion muss einen Parameter vom Typ OscMessage haben, damit die verbindung von "Message kommt an" und "Funktion wird aufgerufen" funktioniert. Wie wir die Variable im Parameter nennen ist allerdings uns überlassen.
Die Funktion würde dann so aussehen:

   void OscEvent(OscMessage receivedMessage)
   {

   }

Durch den Parameter bekommen wir automatisch die neueste, angekommene OscMessage zugespielt und können in dieser Funktion anfangen, mit ihr umzugehen. Unser Script sieht also inzwischen so aus:
Basic-osc-receive-script.JPG

OSC Message "auspacken"

Da wir jetzt davon ausgehen können, dass wir eine Funktion haben, die ausgeführt wird, wenn eine OscMessage am inPort ankommt, und eine Variable durch die wir Zugang zu dieser OscMessage haben (die wir receivedMessage genannt haben), können wir uns mit dem Inhalt der Message befassen. Ähnlich wie in Processing stehen uns mehrere Funktionen zur Verfügung, um auf die Inhalte der Message zuzugreifen:

receivedMessage.address;            //gibt uns die osc-Adresse der OSC-Message
receivedMessage.GetInt(index);      //gibt uns eine ganze Zahl als Wert an der Stelle "index" zurück
receivedMessage.GetFloat(index);    //gibt uns eine Kommazahl als Wert an der Stelle "index" zurück

receivedMessage.values[index];      //lässt uns direkt auf den Wert in der Liste an der Stelle "index" zugreifen

receivedMessage.values[index].ToString();    //gibt uns einen String als Wert an der Stelle "index" zurück

Ausgehend von unserem Processing-Code, den wir hier als Test-Sendeprogramm nutzen können, können wir unsere Funktion also folgendermassen schreiben:
Debug-osc-receive-script.jpg

Wenn alles funktioniert

Hier ist ein Screenshot von Unity und Processing nebeneinander, mit einigen rot eingefärbten Bereichen. Könnt ihr euch erklären, wieso die rot eingefärbten Werte so gewählt wurden, wie sie gewählt wurden?
Osc-unity-processing-test-setup.JPG

Where To From Here

In diesem Beispiel haben wir ein eigenes Script geschrieben, das nur dafür da ist, den Inhalt der OscMessage in die Konsole zu schreiben. In euren projekten wollt ihr sicherlich kompliziertere Dinge machen, wenn ihr OscMessages empfangt, und werdet vielleicht schon ein Script haben, das etwas bestimmtes macht (Ton abspielen, Dinge bewegen, etc.) wenn eine OscMessage empfangen wird.
Wenn ihr aufmerksam mitgelesen habt, könnt ihr feststellen, dass ihr in jede eurer Scripte eine Funktion einbauen könnt, die aufgerufen wird, wenn eine OscMessage ankommt. Ihr müsst in eurem Script dafür dann folgendes tun:

  • eine Verbindung zum OSC Script über eine public OSC myOSC Variable anlegen (wobei ihr myOSC auch anders nennen könnt).
  • diese Verbindung im Editor verknüpfen, indem ihr das GameObject, auf dem das OSC Script liegt in diese Variable auf eurem Script zieht
  • in eurer Start() Funktion die Funktion myOSC.SetAllMessageHandler(OscEvent); aufrufen (wobei eure Funktion auch anders heissen darf
  • in eurem Script die Funktion void OscEvent(OscMessage myMessage){} schreiben. (wenn ihr eure Funktion anders genannt habt, dann hier den anderen Namen nutzen.)

Hier ein Beispiel eines Scripts, das jedes mal kleine Würfel aus einem Prefab generiert, wenn eine OscMessage ankommt:

InstantiatePrefabOnOscMessage

Ihr könnt euch auch den Beispielcode anschauen, der bei der OSC-Implementierung dabei ist, oder die Erklärungen auf der Webseite lesen. Vielleicht entdeckt ihr ja noch ein paar weitere Funktionen, die man im OSC-Script nutzen kann.

OSC Nachrichten verschicken

tbd.

Beispielprojekte

Ein Projekt des ersten Jahrgangs Spiel und Objekt, das sehr viel OSC in Processing und Unity implementiert hat, ist Be Bernd.