Flash-Unity-Kommunikation mit uniTool

Eines meiner aktuellen Projekte, der OUTFLEXX-Loungeplaner, ist ein in Flash programmierter Online-Planer für Outdoor-Rattanmöbel. Er besteht hauptsächlich aus einer 2D-Grundrissplanung mit einer darauf folgenden Echtzeit-3D-Darstellung der Möbel. Die 3D-Darstellung hatte ich zuerst mittels Away3D in Flash selbst durchgeführt, wollte dann aber noch eine „Premium“-Variante des Planers erstellen, bei der die 3D-Darstellung mittels Unity erfolgt. Dabei stieß ich schnell auf ein grundsätzliches Problem: das Flash-Planungsprogramm muss mit dem Unity-Webplayer kommunizieren können und umgekehrt. Genau dafür haben schlauere Leute als ich schon eine fertige Lösung programmiert: uniTool.

uniTool von Duncan Hall ist ein Javascript/Actionscript-Framework. Damit kann ein in einer HTML-Seite eingebettetes SWF-Objekt

  • die Unity-Webplayer-Installation anstoßen, falls das Unity-Plugin noch nicht installiert ist
  • ein (oder mehrere) Unity-Objekt(e) dynamisch in die HTML-Seite einbinden (bzw. wieder entfernen)
  • mit diesen Unity-Objekten Nachrichten austauschen, sie (oder auch sich selbst) sichtbar bzw. unsichtbar machen

Aber Achtung: uniTool ermöglicht nur die Einbettung von Unity-Webplayer-Objekten durch Flash bzw. die Kommunikation zwischen Flash- und Unity-Objekten innerhalb einer HTML-Seite! uniTool integriert nicht die eigentlichen Unity-Inhalte in die Flashdatei – es müssen also trotzdem beide PlugIns im Browser vorhanden sein bzw. installiert werden.

Hier zuerst ein Diagramm mit einer Übersicht der am Einbettungs- und Kommunikationsprozess beteiligten Komponenten innerhalb der HTML-Seite:

Übersicht

Recht kompliziert und unübersichtlich auf den ersten Blick. Im Folgenden versuche ich deshalb, diese Abläufe nacheinander darzustellen und zu erklären.

Download und Installation

Das uniTool-Framework kann, zusammen mit einer Beispielanwendung, als ZIP-Datei von der Google Code Projektseite heruntergeladen werden:

Source-Code zum Download als ZIP-Datei DOWNLOAD FRAMEWORK-SOURCE

Nach dem Herunterladen und Entpacken müssen die Script-Dateien aus dem Ordner „src“ an die richtigen Orte auf der Festplatte verschoben/kopiert werden:

ActionScript-Datei
Alle ActionScript-Dateien aus dem Unterverzeichnissen von „src/as3“ müssen von der FLA-Datei des eigenen Projekts aus erreichbar sein. Am einfachsten gelingt das, in dem man den Ordner „net“ inklusive seiner Unterordner in das Verzeichnis kopiert, in dem auch die FLA-Datei liegt.

JavaScript-Datei
Die Datei „uniTool.js“ im Verzeichnis „src/js“ wird in die HTML-Seite des fertigen Projekts eingebunden. Die Datei muss also in ein Verzeichnis kopiert werden, das von der HTML-Seite aus erreichbar ist.

In der uniTool.js-Datei selbst habe ich noch 2 Änderungen vorgenommen:

1. Aktuelle Webplayer-Version (Zeilen 244 bis 247)
Zum Zeitpunkt dieses Tutorials ist die Version 3 des Webplayer-PlugIns die aktuelle, daher habe ich bei allen URLs das „-2.x“ in ein „-3.x“ geändert:

DEFAULT_INSTALL_URL: "http://www.unity3d.com/unity-web-player-3.x",
INSTALLER_MAC_INTEL: "http://webplayer.unity3d.com/download_webplayer-3.x/webplayer-i386.dmg",
INSTALLER_MAC_PPC: "http://webplayer.unity3d.com/download_webplayer-3.x/webplayer-ppc.dmg",
INSTALLER_WINDOWS: "http://webplayer.unity3d.com/download_webplayer-3.x/UnityWebPlayer.exe",

2. Fix für den Google Chrome-Browser (Zeile 650)
Die Zeile lautet im Original:

else if (navigator.appVersion.toLowerCase().indexOf("safari") != -1) return document.getElementById(this.objectID);

Hier die geänderte Version, damit die Kommunikation auch im Google Chrome-Browser funktioniert:

else if (navigator.appVersion.toLowerCase().indexOf("safari") != -1 <strong>&& navigator.appVersion.toLowerCase().indexOf("chrome") == -1)</strong> return document.getElementById(this.objectID);

Das Setup der HTML-Datei

JavaScript in HTML
Die HTML-Datei des fertigen Projekts muss natürlich zuerst einmal die JavaScript-Datei „uniTool.js“ verlinken, damit deren Funktionen verwendet werden können. Dies geschieht im <head>-Bereich mit der Zeile
<script src=”uniTool.js” type=”text/javascript”></script>
Mehr an JavaScript-Programmierung ist in der HTML-Datei gar nicht nötig, die uniTool-Aufrufe selbst werden alle vom Flash- bzw. Unity-Objekt aus initiiert.

SWF in HTML
Das Flash-Objekt wird mit den üblichen Verfahren (per SWFObject, aus Dreamweaver oder Flash generiertem HTML-Code, etc.) in die HTML-Seite eingebettet. Bei den Einbettungsparametern sind aber die folgenden beiden wichtig für eine korrekte uniTool-Funktion:

„allowScriptAccess“:
„sameDomain“ oder „always“, darf nicht auf „never“ stehen

„wmode“:
falls das Unity-Objekt den Flashfilm ganz oder teilweise überdeckt wie in meinem Fall, muss der wmode auf „transparent“ gesetzt werden, sonst gibt es Darstellungsfehler in manchen Browsern

UNITY in HTML
Das Unity-Objekt selbst wird erst zur Laufzeit durch das Flash-Objekt eingebunden. Damit die Kommunikation zwischen den beiden funktioniert, muss uniTool das Unity-Objekt selbst einbetten! Im HTML-Quelltext wird also nur ein leeres <div> mit einer eindeutigen ID versehen:

<div id=”unity_content” class=“unity_style“ ></div>

Die <div>-ID wird dann aus Flash heraus an das uniTool.js übergeben, das den entsprechenden HTML-Code generiert und dort einbettet. Über die CSS-Klasse „unity_style“ kann man zusätzliche Formatierungen vornehmen, um z.B. das <div> auf der HTML-Seite zu positionieren.

Der Einsatz des uniTool-Frameworks im Flash-Projekt

Die komplette Dokumentation der ActionScript-Klassen hat Duncan Hall hier online gestellt.

Die Programmierung und das Debuggen des Flash-Unity-Projekts ist nicht ganz einfach, da die uniTool-JavaScript-Funktionen aus Flash mittels ExternalInterface() aufgerufen werden. Nur wenn das Browser-DOM-Objekt und die Browser-JavaScript-Engine zur Verfügung stehen, werden diese Aufrufe korrekt ausgeführt. Ein reines Testen der SWF-Datei aus Flash heraus funktioniert also nicht, man muss dazu die HTML-Seite mit dem eingebetteten SWF in einem Browser öffnen – entweder von einem Testserver im Internet oder lokal mittels einer WAMP-/MAMP-Installation.

1. UniTool-Initialisierung

Die UniTool-Klasse übernimmt die gesamte Steuerung der Kommunikation. Sie kann auch unabhängig von einem konkreten Unity-Objekt benutzt werden, um z.B. vor dem Einbetten eines Unity-Objekts zu prüfen, ob das Unity-Webplayer-PlugIn im Browser verfügbar ist.

Bevor man eine der UniTool-Funktion benutzen kann, muss das UniTool einmalig initialisiert werden. Dies geschieht mit der UniTool.init()-Methode. Da die UniTool-Klasse statisch ist, kann (und muss) man ihre Methoden aufrufen, ohne vorher eine UniTool-Instanz erzeugt zu haben (so wie bei der Flash-eigenen „Math “-Klasse auch):

Falsch ist also:

myUniTool:UniTool = new UniTool();
myUniTool.init();
// -> Fehler!

Richtig dagegen ist:

UniTool.init();

2. Unity-Webplayer-PlugIn: Verfügbarkeit prüfen und ggf. installieren

Der UniTool.init()-Befehl setzt unter anderem auch die uniTool-Eigenschaft „pluginIsInstalled“ auf true oder false, so dass eine ActionScript-Zeile genügt, um zu prüfen, ob der Unity-Webplayer im Browser zur Verfügung steht:

if (UniTool.pluginIsInstalled) {...}

Falls das PlugIn nicht installiert ist, könnte sofort mit UniTool.installPlugin() der Download und die Installation angestoßen werden, der Benutzer wird dann direkt mit einem Datei-Ausführen/Speichern-Dialog (je nach Betriebssystem) konfrontiert. Eleganter ist es, zuerst einen Button „Install Unity-WebPlayer“ anzuzeigen und erst nach einem Klick auf denselben mit dem Download und der Installation zu beginnen.
Zusätzlich zu der bereits erwähnten installPlugin()-Methode wird dann der UniToolEvent interessant, der ausgelöst wird, sobald die Installation des Webplayer-Plugins beendet ist:

UniTool.addEventListener(UniToolEvent.PLUGIN_INSTALL_COMPLETE, onPluginInstallComplete);
UniTool.installPlugin();

In der „onPluginInstallComplete()“-Funktion kann dann nach der PlugIn-Installation gleich das Unity-Objekt initialisiert und eingebettet werden (Schritte 3 und 4).

Die Verwendung dieses PLUGIN_INSTALL_COMPLETE-EventListeners hat den großen Vorteil, dass der Unity-Inhalt direkt nach der Installation des PlugIns auch angezeigt werden kann. Es muss dazu weder der Browser geschlossen und neu geöffnet noch die aktuelle HTML-Seite neu geladen werden.

3. UObject initialisieren

Nachdem die Verfügbarkeit des Webplayer-Plugins sichergestellt ist, ist es nun an der Zeit, das Unity-Objekt zu initialisieren. Diese Initialisierung bewirkt noch nicht, dass das Unity-Objekt direkt geladen, eingebettet oder angezeigt wird, sie erzeugt nur ein ActionScript-Objekt mit allen dafür notwendigen Informationen.

var unityObject:IUObject = new UObject(src, containerID, width, height, options);

Hier werden eine ganze Reihe von Parametern mit übergeben:

src:String
// der Dateiname mit Pfad (relativ zum HTML-Dokument) des Unity3D-Objekts
// z.B. „unity/Loungeplaner.unity3d“

containerID:String,
// die ID des <div>s, das für die Unity-Einbettung verwendet werden soll
// z.B. „unity_content“ für den HTML-Code

width:int
// die Breite des Unity-Objekts in Pixeln
// z.B. 600

height:int,
// die Höhe des Unity-Objekts in Pixeln
// z.B. 400

options:UembedOptions
// Optionen, die den Ladevorgang des Unity-Webplayers steuern

Das UembedOptions-Objekt kann u.a. das Aussehen des Ladescreens und des Fortschrittbalkens bestimmen, wichtig für den Gesamtablauf des Ladevorgangs sind aber diese beiden Parameter:

UembedOptions.data.autoInstall:Boolean
// Wenn autoInstall=true wird der Download/Installationsprozess für das Unity-PlugIn automatisch gestartet, falls das Unity-Objekt angezeigt werden soll und das Unity-PlugIn nicht im Browser installiert ist. Ich selbst bevorzuge stattdessen den PlugIn-Check vorab und die Anzeige eines „Install Unity“-Buttons, um dem Benutzer die Wahl zu lassen

UembedOptions.data.queueEmbed:Boolean
// queueEmbed=true sorgt dafür, dass nach dem Abschluss der Unity-PlugIn-Installation (egal ob manuell durch einen Buttonklick des Benutzers oder automatisch durch autoInstall=true gestartet) das Unity-Objekt in die HTML-Seite eingebettet wird, ohne dass dies nochmals explizit per ActionScript geschehen muss.

4. Unity-Objekt einbetten

Nach all der Vorbereitung ist es nun soweit: Das Unity-Objekt kann von der SWF-Datei in die HTML-Seite eingebettet werden. Dazu dient diese ActionScript-Zeile:

// Richtig:
unityObject = UniTool.embedUnity(unityObject);
// Falsch!!!
UniTool.embedUnity(unityObject);

Die falsche Methode funktioniert im ersten Moment zwar auch (das Unity-Objekt wird korrekt eingebettet und angezeigt), die Kommunikation zwischen Flash und dem eingebetteten Unity-Objekt ist dann aber NICHT möglich! Es ist entscheidend, dass die von UniTool.embedUnity() zurückgegebene Objektreferenz wieder der unityObject -Variablen zugewiesen wird, da dies eine neue Objektreferenz ist, nicht die ursprüngliche.

Dieser Schritt macht das Unity-Objekt auf der HTML-Seite sichtbar, es lädt die Datei und zeigt sie an der durch die <div>-CSS-Parameter bestimmten Position an.

Nachdem das Unity-Object jetzt also geladen, sichtbar und richtig positioniert ist, kommt der entscheidende Teil: Die Kommunikation zwischen dem Flash-Objekt und dem Unity-Objekt. Dabei gibt es natürlich zwei Richtungen, zuerst zur

5. Kommunikation von Flash zu Unity

Kommunikation Flash-zu-Unity

Die Flash-Seite
Um eine Funktion/Methode in Unity aufrufen zu können, muss diese in einem Skript definiert sein, das als Komponente an ein aktives GameObject der Szene angehängt wurde.
In meinem Fall heißt das GameObject „LogicController“, daran ist die C#-Skriptdatei „MainScript“ angehängt, in der wiederum die Methode „initArtikel“ definiert ist. Diese Funktion soll der Flashfilm aufrufen und dabei noch mehrere Parameter mit übergeben.

Dazu müssen
- der Name des GameObjects, an dem das Script angehängt ist,
- der Name der aufzurufenden Funktion im Script
- die (optionalen) Parameter für den Funktionsaufruf
an die sendMessage()-Methode des unityObjects übergeben werden.

Die sendMessage()-Methode kann aber nur 1 Parameter verarbeiten (String, int oder Number), mehrere Parameter müssen also zuvor zuerst zu einem String kombiniert werden (hier im Beispiel mit Komma-getrennten Werten, andere Trennzeichen sind aber auch möglich):

var params:String = artikelNr + "," + drehung+ "," + posX + ....;
unityObject.sendMessage("LogicController", "initArtikel", params);

Die Unity-Seite
Die korrespondierende C#-Methode „initArtikel“, die diesen Aufruf entgegennimmt, sieht so aus:

public void initArtikel (string params) {
    // alle Parameter kommen (durch Komma getrennt) als 1 String an
    string[] parameterArray = params.Split(',');
    // Die Array-Elemente sind natürlich ebenfalls Strings und müssen zur weiteren
    // Verwendung ggf. in numerische Typen umgewandelt werden:
    int artikelNr = Convert.ToInt64(parameterArray[0]);
    int drehung = Convert.ToInt32(parameterArray[1]);
    float posX = (float)Convert.ToDouble(parameterArray[2]);
    ...
}

Das war schon alles, was die Kommunikation von Flash zu Unity betrifft. Zurück geht’s sogar noch einfacher:

6. Kommunikation von Unity zu Flash

Kommunikation Unity-zu-Flash

Die Unity-Seite
Im Unity-Script wird die Funktion „Application.ExternalCall“ aufgerufen, diese bekommt 2 Parameter mit: als erstes den String “uniTool.unity_messageHandler”. Das ist der Name der JavaScript-Funktion im uniTool.js und bleibt daher immer gleich. Der 2. Parameter besteht aus einem String mit 2 Teilen, die durch das „pipe-Symbol“ | getrennt werden (= Alt-7 auf der Apple-Tastatur, AltGr-< bei Windows). Im ersten Teil steht der ActionScript-Funktionsname, der aufgerufgen werden soll (im Beispiel „rotateArtikel“), im zweiten Teil stehen wiederum die übergebenen Parameter. Selbst wenn keine Parameter nötig sind, darf der zweite Teil nicht leer sein – man übergibt dann einfach einen Dummy-String, etwa „ok“.

Application.ExternalCall( "uniTool.unity_messageHandler", "rotateArtikel|12" );

Die Flash-Seite
Um diese Nachricht von Unity im Flash zu empfangen ist ein EventListener nötig:

UniTool.addEventListener(UniToolMessageEvent.MESSAGE_RECEIVED, messageFromUnity);

Die vom EventListener aufgerufene Funktion (im Beispiel „messageFromUnity“) kann die zwei Nachrichtenteile wieder über die beiden event-Eigenschaften event.msgHeader und event.msgBody extrahieren und entsprechend darauf reagieren:

private function messageFromUnity(event:UniToolMessageEvent):void {
    trace("Message-Header " + event.msgHeader);
    // der msgHeader ist der String vor dem |-Symbol
    // im Beispiel also „rotateArtikel“
    trace("Message-Body " + event.msgBody);
    // der msgBody ist der String nach dem |-Symbol
    // im Beispiel also „12“ (als String)
    ...
}

Das war’s – damit klappt die Flash-Unity-Kommunikation Betriebssystem- und Browserübergreifend. Unity hat Ende Feburar 2011 angekündigt, dass an einer Unity-Flash-Exportoption gearbeitet wird, so dass in Zukunft wahrscheinlich eine Unity-Szene direkt als SWF-Datei exportiert werden kann und die Kommunikation bzw. Steuerung dann auch direkt durch ActionScript erfolgt. Dann wird vielleicht weder die Unity-PlugIn-Installation noch die Kommunikation mittels uniTool noch nötig sein. Ich bin gespannt…

Veröffentlicht in Deutsch, Flash, Unity | 1 Kommentar »

1 Antwort

  1. Michael schreibt:

    Interessante Sache! Danke

Kommentar verfassen

Achtung: Die Kommentare werden moderiert und erscheinen deshalb nicht sofort im Blog!