Touch Input in der Unity Engine
Sebastian Pohl - 30. März 2014Da man mit der kostenlosen Version der Unity-Engine nun schon seit Mitte des letzten Jahres auch Software für die verschiedenen Mobilplattformen erzeugen kann steigt die Anzahl der Unity-Projekte die auch auf eben diesen Geräten veröffentlicht werden immer weiter an.
Gerade auf Handys und Tabletts die nur eine begrenzte Zahl an Eingabemöglichkeiten haben ist es enorm wichtig ein konsistentes, intuitives und vor allem zuverlässiges Eingabeschema zur Verfügung zu stellen.
Für die traditionellen Geräte Maus und Tastatur bietet Unity dabei von Haus aus schon eine Fülle von Möglichkeiten und fertigen Funktionen. Für Touch-Eingaben sieht es da noch nicht so gut aus. Zwar gibt es bereits eine gute Unterstützung durch Systemfunktionen aber die Umsetzung ist dabei dem Entwickler überlassen.
Ob man nun ein neues Projekt beginnt oder ein vorhandenes portieren möchte, der erste Schritt ist es, die vorhandenen GUI Elemente oder Steuerungsmöglichkeiten darauf anzupassen Touch-Eingaben zu erkennen.
Unity stellt dafür ein Array (Input.touches) zur Verfügung das die aktuell vorhandenen Touch Eingaben vorrätig hält. Enthält dieses Array Elemente, dann gibt es aktuell Eingaben. Um nun einem Gameobject beizubringen darauf zu reagieren, bekommt es ein neues Skript zugewiesen und der Code für diese Abfrage sieht so aus:
using UnityEngine; using System.Collections; public class TouchInput : MonoBehaviour { void Update () { if (Input.touches.Length <= 0) { //Keine Touch-Eingaben } else { //Touch-Eingaben verarbeiten } }
Damit weis das Programm zumindest schon mal ob es Eingaben gibt, viel mehr ist aber noch nicht bekannt. Da sehr häufig mit grafischen Elementen für die Eingabe gearbeitet wird wäre es sehr hilfreich wenn wir beispielsweise für ein GUITexture Element feststellen können ob eine der Eingaben dieses Element trifft. Das GUITexture Element stellt dafür eine Funktion HitTest bereit mit der wir einfach überprüfen können ob die Position der Touch-Eingabe das Element trifft. Wir gehen als alle Eingaben durch und prüfen ob eine davon das Element trifft:
for ( int i = 0; i < Input.touchCount; i++) { if ( this.guiTexture.HitTest (Input.GetTouch(i).position)) { //Code für den Fall, das dass Element berührt wurde. if ( Input.GetTouch(i).phase == TouchPhase.Began ) { //Code für neue Touch Eingaben } } }
Der obige Codeschnippsel zeigt direkt auch eine weitere Information die Unity uns zur Verfügung stellt, nämlich die Phase in der sich eine Touch-Eingabe befindet. Mögliche Zustände sind:
- TouchPhase.Began
Die Touch-Eingabe hat gerade begonnen. - TouchPhase.Moved
Eine Eingabe die vorher schon bestand wurde bewegt. - TouchPhase.Stationary
Eine vorhandene Eingabe wurde nicht bewegt. - TouchPhase.Ended
Eine vorher vorhandene Eingabe wurde beendet. - TouchPhase.Canceled
Vom Betriebssystem wurde die Verfolgung der Eingabe abgebrochen.
Um das gezeigte Beispiel nicht zu kompliziert zu gestalten werden wir uns auf zwei der Werte konzentrieren, TouchPhase.Began und TouchPhase.Ended.
Prinzipiell lassen sich damit zwei sehr wichtige Eingabearten umsetzen, zum einen ein GUI Element das einmal gedrückt wird und dabei auch nur einmal weitere Funktionalitäten auslöst und ein Element das gedrückt und „gehalten“ wird und dabei kontinuierlich weiteren Code ausführt.
Der „Einmal-Knopf“ lässt sich mit dieser Abfrage lösen:
if ( Input.GetTouch(i).phase == TouchPhase.Began ) { //Code der einmal am Anfang der Touch-Eingabe ausgeführt wird. }
und für kontinuierliche Eingabe sieht es so aus (Der Code wird ausgeführt solange die Touch-Eingabe nicht beendet wurde!):
if ( Input.GetTouch(i).phase != TouchPhase.Ended ) { //Code der Ausgeführt wird solange die Touch-Eingabe nicht beendet wurde. }
Damit lässt sich schon ein Großteil der häufigsten Eingabearten auf Geräten mit Touchscreen umsetzen. Als kleine Zugabe möchte ich aber noch erklären wie man eine Eingabe ähnlich eines virtuellen Joysticks erreichen kann.
Dazu ist es notwendig ein bisschen zur rechnen, da wir zum einen die Touch-Eingaben nur in Pixel-Koordinaten bekommen und zum anderen die Position und Größe der GUITexture Elemente nicht ganz intuitiv sind. Die Position ist als Zahl von 0 bis 1 in beiden Achsen ausgehend von der linken unteren Ecke angegeben und die Breite und Höhe sind in Pixeln angegeben. Um nun einen virtuellen Joystick zu erstellen müssen wir wissen wie der Abstand vom Mittelpunkt zum tatsächlichen Berührungspunkt ist und diesen Wert am besten noch so umrechnen das wir Bereiche von -1 bis 1 für die horizontale und vertikale Richtung haben. Diese Werte können wir dann komfortabel an die restliche Spiellogik weitergeben um zum Beispiel die Bewegung einer Spielfigur steuern zu können.
Um diesen Artikel nicht unnötig in die Länge zu ziehen ist der Code hier nun komplett inklusive Kommentaren:
public class VirtualJoystick : MonoBehaviour { Vector2 touchPosition, iconPosition, relativePosition; void Update () { if (Input.touches.Length <= 0) { //Keine Touch-Eingaben. } else { for ( int i = 0; i < Input.touchCount; i++) { if ( this.guiTexture.HitTest (Input.GetTouch(i).position)) { if ( Input.GetTouch(i).phase != TouchPhase.Ended ) { // Die Position der Touch-Eingabe wird als Vector gespeichert. touchPosition = Input.GetTouch (i).position; // Die Position des GUITexture Elements wird ebenfalls gespeichert. iconPosition = transform.position; /* * Da die Position des GUITexture Elements relativ zur Bildschirmauflösung ist * muss diese umgerechnet werden um die Pixelposition zu bekommen. */ iconPosition.x *= (float)Screen.width; iconPosition.y *= (float)Screen.height; /* * Um den Mittelpunkt des GUITexture Elements zu bekommen wird * jeweils die halbe Breite und Höhe in Pixeln zur Position addiert. */ iconPosition.x += this.guiTexture.pixelInset.width / 2.0f; iconPosition.y += this.guiTexture.pixelInset.height / 2.0f; //Der Vektor von der GUITexture Position zur Eingabe Position wird gespeichert. relativePosition = touchPosition - iconPosition; /* * Um nun den Vektor in den Wertebereich von -1.0 bis +1.0 für beide * Richtungen zu bringen wird er durch die halbe Breite und Höhe * des GUITexture Elements geteilt. */ relativePosition.x /= this.guiTexture.pixelInset.width / 2.0f; relativePosition.y /= this.guiTexture.pixelInset.height / 2.0f; //Hier kann nun Code stehen der die Eingabewerte verarbeitet. } } } } } }
Der Code wird einfach einem GUITexture Element zugewiesen und an die eigenen Bedürfnisse angepasst!