Ihno Lübbers

Voraussichtliche Lesedauer: 11 Minuten

Techblog

Sprachsteuerung für Unity mit Android

Unity ist derzeit eine der am meisten genutzten Game-Engines am Markt. Vermutlich ist das auch auf die hervorragende Verfügbarkeit von Plugins, sogenannte Unity-Packages, für diverse Technologien und Einsatzzwecke zurückzuführen. Insbesondere für die Entwicklung von VR- und AR-Anwendungen für den Konsumentenmarkt wird neben der Unreal-Engine fast ausschließlich Unity genutzt. Da es aber in VR (Virtual Reality)…

Techblog

Unity ist derzeit eine der am meisten genutzten Game-Engines am Markt. Vermutlich ist das auch auf die hervorragende Verfügbarkeit von Plugins, sogenannte Unity-Packages, für diverse Technologien und Einsatzzwecke zurückzuführen. Insbesondere für die Entwicklung von VR- und AR-Anwendungen für den Konsumentenmarkt wird neben der Unreal-Engine fast ausschließlich Unity genutzt. Da es aber in VR (Virtual Reality) und AR (Augmented Reality) schwer fällt, konventionelle Eingabemedien wie Maus und Tastatur zu nutzen, müssen andere Möglichkeiten herhalten.

Alternative Interaktionsmöglichkeiten sind typischerweise Controller- und Gestensteuerung beispielsweise in Kombination mit einer Spracherkennung. Unity verfügt über eine eingebaute Spracherkennung, die auf die Windows Spracherkennung zugreift. Wie zu erwarten ist, kann diese auf anderen Plattformen, beispielsweise Android, nicht eingesetzt werden. Für Android kann stattdessen ein natives Plugin implementiert werden, das die vorhandene Spracherkennung der Plattform aufruft.

How-to: Android Plugin für Unity zur Spracherkennung

In diesem Tutorial beschreibe ich, wie ein Android-Plugin für Unity erstellt wird, das native Android-Funktionen aufruft. Der erste Teil beschreibt die grundsätzliche Android-Plugin-Erstellung für Unity. Der zweite Teil zeigt, wie Unity-Funktionen aus Android aufgerufen und aus Unity heraus native Android-Schnittstellen angesprochen werden. Im Unterschied zu vielen der existierenden Android-Plugins für Unity wird in diesem Tutorial nicht von der UnityPlayerActivity abgeleitet. Statt dessen wird mit einem Fragment gearbeitet. Dadurch verhindert man, dass unterschiedliche Android Plugins sich in Unity gegenseitig überschreiben – Stichwort: Manifest.

Das Tutorial kann als Merge von diesem Tutorial und dem Git-Repository betrachtet werden: 

Das Git Repository mit gesamten Projekten, dem fertigen Plugin sowie Source-Code gibt es hier


Teil 1

HelloFromAndroid Plugin

Im ersten Teil wird ein minimales Android-Plugin für Unity erstellt, das nichts weiter tut, als eine Begrüßung auszugeben. Wer das kennt, kann direkt zum zweiten Teil springen.

Schritt 1- Android Studio Project Setup:

  • Neues Android-Studio-Projekt ohne Activity anlegen
    File -> New -> New Project -> „Dein Projekt“
  • Projekt- statt Android-Ansicht aktivieren, um alle Dateien zu sehen
  • Neues Android-Library-Modul anlegen
    File -> New -> New Module -> “Dein Modul”
  • Das zu Beginn automatisch generierte „app“ Modul kann gelöscht werden
    Rechtsklick „app“ Modul -> Open Module Settings -> Minus>
    Der „app“ Ordner kann jetzt gelöscht werden

Schritt 2 – Erstellen eines Hello-Plugins

  • Zum src Ordner deines Moduls navigieren und die Klasse „HelloFromAndroid.java“ mit folgendem Inhalt anlegen:
package de.maibornwolff.speechrec;

public class HelloFromAndroid {
    public static String sayHi(String caller){
        return "Hi "+caller+", I'm Android!";
    }
}
  • Das Hello-Plugin ist fertig und kann in Android Studio über Gradle gebaut werden
    Gradle Tab -> „dein Modul“ -> Tasks -> build -> assemble
  • Die generierten .aar Dateien befinden sich build -> outputs Ordner des Moduls

Schritt 3 – Verwendung im Unity Projekt

  • Ein neues Unity Projekt anlegen und den Ordner Plugins/“dein Plugin“ darin anlegen
  • Eine der .aar Dateien aus Schritt 2 (debug/release) per Drag&Drop in den Ordner kopieren
  • Eine „GetGreeting.cs“ Klasse mit folgendem Inhalt in den Assets anlegen:

using UnityEngine;
using UnityEngine.UI;

public class GetGreeting : MonoBehaviour
{
    public Text uiText;

    void Start()
    {       
        AndroidJavaClass pluginClass = new AndroidJavaClass(
            "de.maibornwolff.speechrec.HelloFromAndroid");
        uiText.text = pluginClass.CallStatic<string>("sayHi", "Unity");
    }
}

  • Nicht vergessen, die Variable uiText im Unity-Editor zu verbinden und das Skript einem aktiven Objekt (z.B. der Main Camera) hinzuzufügen!
  • Die HelloFromAndroid-App ist fertig und kann für Android gebaut werden!
  • Zum Testen ein Device anschließen oder Android Emulator starten.
  • Über Assets -> Export Package, kann auch ein Unity Package erstellt werden.

Teil 2

Natives Plugin für die Spracherkennung

Im zweiten Teil erstellen wir ein Plugin, das aus Unity heraus die integrierte Spracherkennung von Android aufruft und das Ergebnis an Unity zurückliefert. Es empfiehlt sich,  entweder ein neues Android-Projekt zu erstellen (wie in Schritt 1 beschrieben), oder die Datei „HelloFromAndroid.java“ aus Teil 1 zu löschen, da sie nicht länger benötigt wird. Das .aar-Plugin und das Script im Unity-Projekt sollten ebenfalls gelöscht werden.

Schritt 4 – Nutzen von Unity-Funktionen in Android Studio

  • Um in Android Studio auf Unity zugreifen zu können, wird die classes.jar der Unity Installation in den libs-Ordner des Moduls in Android Studio kopiert
    Fundort bei Windows: C:Program FilesUnityEditorDataPlaybackEnginesAndroidPlayerVariationsmonoReleaseClassesclasses.jar
    Optional: Beim Kopieren umbenennen in „UnityPlayer.jar“
  • Um mögliche „unable to convert classes into dex format“-Fehler beim Unity-Build zu vermeiden, sollten alle Android- und Unity-Dependencies des Moduls auf „Provided“ gesetzt werden, da diese von Unity ohnehin bereitgestellt werden. 
  • Im „src“ Ordner wird eine Android-Fragment-Klasse mit folgendem Inhalt angelegt:
package de.maibornwolff.speechrecognition.plugin;

import android.app.Fragment;
import android.content.Intent;
import android.os.Bundle;
import android.speech.RecognizerIntent;
import android.util.Log;
import com.unity3d.player.UnityPlayer;
import java.util.Locale;

import static android.app.Activity.RESULT_OK;

public class SpeechRecFragment extends Fragment {

    static final String TAG = "SpeechRecFragment";
    static final int REQ_CODE_SPEECH_INPUT = 1;
    static SpeechRecFragment instance;
    static String gameObjectName;

    public static void start(String gameObjectName) {
        instance = new SpeechRecFragment();
        instance.gameObjectName = gameObjectName;
        UnityPlayer.currentActivity.getFragmentManager().
                beginTransaction().add(instance, SpeechRecFragment.TAG).commit();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Retain between configuration changes (like device rotation)
        setRetainInstance(true);
    }

    public static void promptSpeechInput() {
        Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, Locale.getDefault());
        instance.startActivityForResult(intent, REQ_CODE_SPEECH_INPUT);
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch (requestCode) {
            case REQ_CODE_SPEECH_INPUT: {
                if (resultCode == RESULT_OK && null != data) {
                    UnityPlayer.UnitySendMessage(gameObjectName, "OnSpeechRecognitionResult",
                            data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS).get(0));
                    String match = data.getStringArrayListExtra(
                            RecognizerIntent.EXTRA_RESULTS).get(0);
                    Log.i("UnityTag", match);
                }
            }
        }
    }
}
  • Das Plugin kann jetzt über Gradle gebaut und die .aar in das Unity-Projekt eingefügt werden.

Schritt 5 – Aufruf der nativen Android Funktion aus Unity

  • Im Unity wird ein Script „AndroidSpeechRecognizer.cs“ mit folgendem Inhalt angelegt:
using UnityEngine;
using UnityEngine.UI;

public class AndroidSpeechReconizer : MonoBehaviour {

    AndroidJavaClass pluginClass;   
    public Text uiText;

    void Start()
    {       
        AndroidJavaClass pluginClass = new AndroidJavaClass(
            "de.maibornwolff.speechrecognition.plugin.SpeechRecFragment");       
        pluginClass.CallStatic("start", gameObject.name);    
        pluginClass.CallStatic("promptSpeechInput");        
    }

    public void OnSpeechRecognitionResult(string result)
    {
        uiText.text = result;
    }
}
  • dann das Script einem aktiven Objekt in der Szene hinzufügen und den UI-Text verlinken
  • Das Unity Test-Projekt ist fertig und kann für Android gebaut werden.
  • Über Assets → Export Package kann zudem ein Unitypackage erstellt werden:

Das mit diesem Tutorial erstellte Plugin ruft, wie in der Abbildung zu sehen ist, die in Android eingebaute Spracherkennung von Google auf. Die Resultate werden an die im Quellcode definierte Funktion in Unity zurückgeliefert. Nach dem beschriebenen Prinzip können auch andere native Android-Funktionen, beispielsweise Kamera, aufgerufen und als Unity Plugin exportiert werden. Da im Kern des Plugins ein Android-Fragment – statt der UnityPlayerActivity – genutzt wird, können die so erstellten Plugins auch problemlos im selben Unity-Projekt genutzt werden, denn jedes Fragment besitzt eine eigene „onActivityResult()“ Funktion.


Über den Autor

Ihno Lübbers

Full-Stack Software Engineer 

Ihno arbeitet seit 2017 bei MaibornWolff. Seitdem war er in technologisch sehr verschiedenen Projekten aktiv. Beispiele für die Technologien sind Android, Unity, Unreal, Cloud-Microservices und die Prototypen-Entwicklung. Als Full-Stack Developer liebt es Ihno leidenschaftlich den Blick über den technischen Tellerrand zu werfen und sich in neue Gebiete vorzuwagen. Sein Focus liegt dabei aber immer auf sauberem und gut getestetem Code. Privat ist Ihno oft auf einem rollendem oder rutschendem Board anzutreffen.