UI in Unity

Aus hyperdramatik
Zur Navigation springen Zur Suche springen

UI

UI steht generell als Kurzform von User Interface. Im Kontext von Unity bezeichnet der Begriff UI allerdings eine von Unity spezifisch vorgegebene Interaktionsstruktur, die mit Buttons, Textfeldern und ähnlichem arbeitet, und sich im Code von den meisten anderen Eingabemöglichkeiten unterscheidet. Der Hauptunterschied besteht im von Unity vorgegebenen Interaktionsmodus durch UI Elemente und die Unterschiede in der Art der Darstellung auf dem Bildschirm.

UI Elemente hinzufügen

Unity UI Elemente sind GameObjects die man in der Hierarchy über den Unterpunkt UI erstellen kann.
Create-ui-menu-unity.png
Es stehen eine Reihe von Elementen zur Auswahl. Wir werden uns auf dieser Seite hauptsächlich mit den Elementen Button (TextMeshPro) und Text (TextMeshpro) beschäftigen.

Kurze Erklärung zu TextMeshPro

Unity ist in den letzten zehn Jahren enorm schnell gewachsen und hat oft Code-Elemente, die von Menschen ausserhalb der Firma erstellt wurden über Zeit in den eigenen Code übernommen. TextMeshPro ist so ein Fall, in dem besserer Code zur Darstellung von texten in unity eingeflossen ist. Unity muss allerdings nun den von ihnen selbst erstellten sowie den zugekauften Code weiter anbieten, was dazu führt, dass man als Nutzer*in die Auswahl aht, für welche dier beiden zur Verfügung gestellten Varianten man sich entscheiden möchte.

Wir wollen uns im Zweifel immer für TextMeshPro entscheiden, da der Text schöner dargestellt wird.
Wenn ihr das erste Mal aus der Hierarchy ein GameObject mit dem Zusatz (TextMeshPro) erstellt, ploppt folgender Dialog auf, bei dem ihr auf beide Knöpfe klicken könnt:
Tmp-import.png

Das Canvas Konzept

Alle in Unity genutzten UI Elemente werden einem sogenannten Canvas Objekt zugeordnet, einer Art Leinwand, die über der Szenen auf den Bildschirm gespannt ist. Wenn wir in der Hierarchy einen neuen Button erstellen, erstellt Unity auch gleich ein Canvas Object sowie ein EventSystem (über letzteres werde wir uns im Rahmen des Studiengangs nur peripher unterhalten).
Unity-canvas-eventsystem-ui.png
UI Elemente werden relativ zu diesem Canvas positioniert und dargestellt (gerendert) und unterliegen dem Koordinatensystem, das durch den Canvas vorgegeben ist. Für gewöhnlich sind sie daher unabhängig von der Position und Rotation der Kamera in der Szene immer am selben Ort auf dem Bildschirm. Deswegen müssen UI Elemente in der Hierarchy in Unity auch immer unter einem Canvas Objekt auftauchen.
Das Koordinatensystem des Canvas Objekts wiederum orientiert sich an der Auflösung des Bildschirms, wie in der Game View eingestellt.
Unity-gameview-resolution.png
Da man sich der Auflösung der Endgeräte zu Beginn eines Projektes nicht immer sicher sein kann, und Unity von sich aus vorsieht, dass die Nutzer*innen ihre Auflösung auch selbst einstellen können (kommt bei uns seltener vor), bietet uns Unity durch die Nutzung des Canvas Konzeptes an, dass wir verschiedene UI Elemente relativ zu den Ecken des Bildschirms positionieren können. Hierfür ändert Unity den uns bekannten Transform Component in ein RectTransform Component (der allerdings von Transform abgeleitet ist, so dass wir per Script weiterhin wie gewohnt auf transform.position zugreifen können):
Recttransform.png

Unterschiede in Koordinaten

Wie ihr gut erkennen könnt, sind die Zahlen, die in diesem Component die Position des GameObjects beschreiben deutlich anders, als die von uns bisher genutzten. Während wir bei unseren bisherigen Übungen für gewöhnlich Koordinaten zwischen 1 und 10 oder -1 und -10 für die x, y und z Werte genutzt haben, stehen hier plötzlich zahlen in der Grösse von 100 und 1000. Das hat damit zu tun, dass die Koordinaten bei Rect Transform als Bildschirmkoordinaten interpretiert werden, wobei eine Koordinate von 1 genau einen Pixel beschreibt. Zum Vergleich: in unserer Unity Szene beschreibt eine Koordinate von 1 für gewöhnlich die Distanz von einem Meter.

Darstellung in Scene View und Game View

Wenn wir zwischen der Unity Scene View und der Game View hin und her schalten, stellen wir fest, dass unser Button in der Game View zwar an einem sinnvollen Punkt dargestellt wird, in unserer Scene View Allerdings nichts von ihm zu sehen ist. Stattdessen sehen wir eine seltsame Linie mit Ecke:
Ui-scene-vs-gameview.png
Der Grund dafür ist, dass die Unity Scene View immer alle Dinge in Unity Koordinaten darstellt (1 Unity Koordinate interpretieren wir für gewöhnlich als 1 Meter), wohingegen in der Game View die Besonderheiten des Canvas-Konzeptes bereits in Betracht gezogen werden, und die UI Elemente unabhängig von der Kamera auf dem Bildschirm platziert werden.
UI Elemente sind also am Einfachsten in der Game View sichtbar und es empfiehlt sich, ihre positionierung auch dort zu überprüfen.

Trotzdem können UI Elemente auch in der Scene View angeschaut und manipuliert werden. Die oben angesprochene weisse Linie beschreibt den Umfang des Canvas Objektes in Unity 3D-Koordinaten. Im Canvas Objekt selbst könnt ihr unter width und height nachschauen, wie gross dieser Umfang ist. Wenn ihr gaaaaaaaanz weit herauszoomt, könnt ihr dann auch euren Button sehen. Eine einfachere Version, um eine übersicht über die UI Elemente in der Scene View zu bekommen ist, das Canvas GameObjekt doppelklicken.
Unity-doubleclick-canvas.png

Ihr könnt selbstverständlich wie gewohnt, auch UI Elemente in der Scene View anklicken und verschieben. Wichtig hierbei ist nur zu wissen, dass der Canvas diese Elemente nicht perspektivisch darstellt und sich an der Darstellung der UI Elemente somit auch bei Verschiebung in der Z-Achse nichts ändert.

Relatives Ausrichten der UI Elemente

Durch klicken auf das Piktogramm links bei RectTransform können wir bestimmen, wie Unity die relative Position unseres UI Elements bestimmen soll:
Unity-ui-anchors.png
Gerade bewegt Unity unseren Button von der Mitte des Screens aus gesehen 687 Pixel nach links und 369.5 Pixel nach unten. Mehr informationen hierzu findet ihr in der Unity Gebrauchsanweisung für UI Elemente.

Nachdem wir einen Button (TextMeshPro) in Unity hinzugefügt haben, sollte unsere Szene ungefähr so aussehen:

Unity-scene-with-button.png

Textfelder

Nicht immer werden UI Elemente dafür genutzt, dass sie manipuliert werden können. Oft sollen sie auch einfach nur etwas darstellen oder anzeigen. Ein Beispiel dafür ist Text (TextMeshPro):
Unity-ui-create-text-tmp.png

Ein Textfeld generiert genauso einen Canvas (falls noch keiner da ist) oder ordnet sich automatisch einem bestehenden Canvas unter (falls schon ein anderes UI Element in der Szene ist).

Text in einem Textfeld per Script definieren

Der Unity Editor erlaubt euch, den Text den ihr im Textfeld seht direkt im Editor einzugeben. Für Nutzer*innen eures Programms ist der Text allerdings nicht editierbar. Jedoch könnt ihr den Text selbstverständlich per Script jederzeit ändern.
Dafür erstellen wir hier als Beispiel ein neues Script, dass den Text in unserem neu generierten Textfeld verändert. Wir wollen es TextChange.cs nennen:
Unity-new-script-textChange.png

Um von unserem Script aus auf das Text (TextMeshPro) GameObject zugreifen zu können und dort den angezeigten Text zu verändern, müssen wir eine Verbindung zu unserem Script in Form einer Variable schaffen. Dazu brauchen wir, wie immer, den Typ des Scripts auf das wir zugreifen wollen. Im Falle eines Text (TextMeshPro) stellt sich der Typ allerdings als etwas komplizierter dar, weil es Text in Unity in unterschiedlichen Formen gibt: einmal für UI und einmal für die 3D Repräsentation in unserer Szene! Indiesem Fall wollen wir auf die UI-Version des Typs Text (TextMeshPro) zugreifen. Der Typ unseres Textfeldes ist in diesem Fall TextMeshProUGUI (UGUI steht kurz für Unity Graphical User Interface). Damit wir direkt Zugriff auf diesen Typ bekommen, sollten wir die Library TMPro in unserem Script mit laden. Das sieht dann so aus:
Visual-studio-using-tmpro.png

Wir können nun im Unity Editor unser Script in ein neues, leeres GameObject ziehen, das wir TextManipulation nennen können:
Unity-ui-textmanipulation-gameobject.png
Und dort dann die Verbindung mit unserem Text(TMP) GameObject und dem zugehörigen TextMeshProUGUI Component herstellen:
Unity-ui-connect Text-tmp-to-textmanipulation.png

Nun können wir in unserem TextChange.cs Script auf die Variable textin der verbundenen TextMeshProUGUI-Komponente zugreifen und sie verändern, in dem wir beispielsweise unsere Start Funktion folgendermassen umschreiben:

void Start()
{

  meinTextFeld.text="Hallo Welt!";

}

oder in unserer Update Funktion folgendes Schreiben:

void Update()
{

  meinTextFeld.text+="mi";

}

Einen Button abfragen

Prinzipiell erstellen wir in unseren Szenen Buttons als UI-Elemente, weil wir im Code darauf reagieren wollen, dass jemensch den Button geklickt hat. Hier hilft uns Unity durch bereits vorprogrammierte Strukturen, die es uns sehr einfach ermöglichen, eine Funktion aufzurufen, wenn ein bestimmter Button geklickt wurde.

Beispielscript

Wir können also ein Beispielscript erstellen, nennen wir es KnopfGeklickt.cs (Grossschreibung bei neuen Scripten beachten!). Und diesem Script können wir eine einfache Funktion hinzufügen - in diesem Beispiel die Funktion ButtonWurdeGeklickt(), die uns in der Konsole etwas ausgibt:

public void ButtonWurdeGeklickt(){

  Debug.Log("Yay!");
 
}

Das public bei public void ist in diesem Fall sehr wichtig, da wir diese Funktion ausserhalb des eigenen GameObjects aufrufen wollen!

Button mit Beispielscript verbinden

Nachdem wir unser Script als Component auf ein leeres Gameobject hinzugefügt haben, können wir daraufhin im Inspector den Button mit unserer Funktion direkt verbinden. Dazu fügen wir beim Button im Feld OnClick eine neue Verbindung zu unserer Funktion hinzu:
Ausgangspunkt ist unsere Hierarchy mit unserem GameObject (ich habe es KlickKonsequenz genannt) und unserem neuen Script als Component:
Unity-klickkonsequenz-setup.png
In Folgenden Schritten können wir die oben genannte Funktion in unserem Script ausführen lassen, wenn der Button gedrückt wurde:

  • Neue Verbindung bei Button herstellen:
    Unity-btn-add-connection.png
  • Unser GameObject zuweisen:
    Unity-btn-drag-gameobject-to-connect.png
  • Unsere neu geschriebene Funktion auswählen:
    Unity-btn-select-function.png

Wenn alles funktioniert hat ist das Resultat in der Console ablesbar:
Unity-btn-yay.png

Mehrere Buttons mit einem Script verbinden

Im Folgenden Beispiel wollen wir mit zwei Buttons arbeiten, und unser KnopfGeklickt.cs Script so erweitern, dass wir im Script unterscheiden können, welcher der beiden Knöpfe gedrückt wurde. Hierzu benutzen wir ein Kozpet, dass in Unity in diesem Kontext mit static paramaters bezeichnet wird.

Hinzufügen eines Parameters in unsere Scriptfunktion

Wir ändern die Funktion in unserem Script, so dass wir einen Parameter unserer Wahl hinzufügen, und dadurch eine Entscheidung über unsere Ausgabe treffen können. Dieser Parameter wird später bei der Verbindung zu den Buttons mit einem Wert gefüllt, den wir im Inspector bei den jeweiligen Buttons einstellen können. Als Beispiel wähle ich hier einen int Parameter und nenne ihn "welcherButton":

public void ButtonWurdeGeklickt(int welcherButton)
{
 
  if (welcherButton==1){
    Debug.Log("Button 1 gedrückt!");
  }

  if (welcherButton==2){
    Debug.Log("Button 2 gedrückt!");
  }

}

Damit ich zwischen zwei Buttons unterscheiden kann, brauche ich ja auch zwei Buttons. Ich habe dafür meinen ersten Button kopiert und leicht verschoben, dann beiden einen Namen gegeben, damit ich sie für mich besser unterscheiden kann. Meine Hierarchy sieht dementsprechend so aus:
Unity-btn-two-btns.png
Ich habe nun zwei Buttons, beide in der Hierarchy unter demselben Canvas GameObject, allerdings der Übersicht halber unterschiedlich benannt. Das sich mein Script geändert hat, muss ich die Funktion bei OnCLick wieder neu Verbinden.Beim Verbinden mit meiner abgeänderten Funktion entsteht nun ein neues Feld, in den ich manuell den Wert des Parameters eingeben kann, der an meine Funktion übergeben wird, wenn der Button gedrückt wird:
Unity-btn-static-parameter.png

Diese statischen Parameter müssen nun für jeden Button einzeln auf eine Zahl gesetzt werden, die ich dann im oben genannten Script abfragen kann. Für Button Eins setze ich die Zahl auf 1, für Button Zwei setze ich sie auf zwei. So kann ich mit einem einzelnen Script unterscheiden, welcher Knopf im User Interface gedrückt wurde.

Textfelder

Nachdem wir die Verbindung von UI Elementen mit unseren Scripten und den Umgang mit statischen parametern angeschaut haben, stellt sich nun die Frage: Was tun, wenn wir die Eingabe von Nutzer*innen in textfelder direkt in Code weiterverarbeiten wollen?
Hierzu erstellen wir zunächst ein neues InputField (TextMeshPro) in unserer Hierarchy. Wir ihr sehen könnt wird es direkt dem bereits bestehenden Canvas GameObject in der Hierarchy untergeordnet:
Unity-new-inputfield.png
Dieses Inputfield hat nun mehrere Möglichkeiten, die Texteingabe von Nutzer*innen an den von uns geschriebenen Code weiterzuleiten:
Unity-textinput-functionlist.png
Die Funktionszuschreibungen funktionieren hier genauso wie bei den Buttons weiter oben, jedoch können wir zu verschiedenen Zuständen, die es beim InputField geben kann auch verschiedene Funktionen zuordnen.

Durch Textfelder Funktionen rufen

Um das auszuprobieren, können wir ein neues Script erstellen, dass ich beispielhalber TextEingabeLesen.cs nennen möchte. In diesem Script können wir eine Funktion hinzufügen, von der wir wollen, dass sie aufgerufen wird, wenn jemand etwas in unser InputField geschrieben und die Eingabe bestätigt hat. Die Funktion könnte ungefähr so aussehen:

public void TextEingegeben(){

  Debug.Log("Yay!");

}

Da ich mich zunächst vergewissern möchte, dass ich alles richtig verbunden habe, macht diese Funktion zunächst nicht besonders viel. Sie ist aber ein erster Schritt, mit dem ich überprüfen kann, ob die Funktion tatsächlich im richtigen Moment gerufen wird.
Folgende Schritte stehen also nun an:

  • Ein neues, leeres GameObject muss der Hierarchy hinzugefügt werden, vielleicht nennen wir es "TextVerarbeitung"
  • Unser neu geschriebenes Script mit dem Namen TextEingabeLesen.cs muss als Component zum neuen GameObject TextVerarbeitung hinzugefügt werden.
  • In unserem InputField GameObject, muss ich eine neue Verbindung bei OnEndEdit zu TextVerarbeitung hinzufügen:

Unity-connect-script-to-inputfield.png

  • Als Funktion soll die Funktion TextEingeben ausgeführt werden.

Wenn alles verbunden ist, könnt ihr die Eingabe testen: In der Game View und im Play Modus könnt ihr auf das Textfeld klicken, einen Text eingeben und dann die Enter-Taste drücken. In der Konsole sollte dann "Yay!"erscheinen.
Unity-btn-yay.png

Übungen

  • Könnt ihr die Szene so verändern, dass "Yay!" jedesmal erscheint wenn ein Buchstabe ins Textfeld eingegeben wird?
  • Könnt ihr euer Script so umschreiben, dass in der Console "..." ausgegeben wird, wenn jemand etwas tippt, und "Yay!" wenn die Eingabe beendet wurde?

Dynamische Parameter

Ein einfaches Beispiel könnte den eingegebenen Text in der Console ausgeben. Wie aber stellt mir das InputField diesen eingegebenen text zur Verfügung? Die Antwort ist: über einen dynamic parameter. Wir können für unsere Funktion, ähnlich wie bei static parameter bei den Buttons, nun einen neuen Parameter vom Typ string definieren:

public void TextEingegeben(string derText){

  Debug.Log(derText);

}

Damit das InputFIeld GameObject weiss, dass es den eingegebenen Text an diese Funktion übergeben soll, müssen wir die Funktion folgendermassen verbinden:
Unity-inputfield-dynamic-string.png
Wie ihr sehen könnt, gibt es unsere Funktion TextEingegeben in der Liste gleich zweimal: einmal unter der Kategorie Dynamic string und einmal unter der Kategorie Static Parameters. In diesem Fall wollen wir unsere Funktion aus der oberen Liste auswählen, damit Unity den eingegebenen Text direkt an den Parameter unserer Funktion weitergeben kann!

Übungen

  • Kannst du eine Unity Szene machen, in der sich ein Würfel immer einen Meter vorwärts bewegt, wenn jemand das Wort "liebe" in ein Textfeld eingibt?
  • Kannst du eine Unity Szene machen, in der derselbe Würfel einen Namen bekommt, der durch ein zweites Textfeld festgelegt wird?
  • Kannst du eine Eingabe aus einem textfeld in einem 3D-Text (TextMeshPro) erscheinen lassen?

Übungen zu UI und Unity

  • Kannst du ein Quiz-Programm schreiben, dass die 10 Fragen hintereinander stellt, und am Ende eine Auswertung auf dem Screen ausgibt?
  • Kannst du einige der Fragen über ein UI Element Slider von 1-10 beantworten lassen?
  • Kannst du über OSC in Unity die Antworten dieser Fragen auf einem anderen Computer visualisieren?