Unity So erstellen Sie mobile Touch-Steuerelemente

Die Steuerung ist einer der wichtigsten Teile eines Videospiels, und es überrascht nicht, dass sie es den Spielern ermöglicht, mit der Spielwelt zu interagieren.

Spielsteuerungen sind Signale, die durch Hardware-Interaktion (Maus/Tastatur, Controller, Touchscreen usw.) gesendet werden und dann vom Spielcode verarbeitet werden, wodurch bestimmte Aktionen ausgeführt werden.

PCs und Spielkonsolen verfügen über physische Tasten, die gedrückt werden können, moderne Mobilgeräte verfügen jedoch nur über wenige physische Tasten, der Rest der Interaktion erfolgt über Touch-Gesten, Das bedeutet, dass Spielschaltflächen auf dem Bildschirm angezeigt werden müssen. Aus diesem Grund ist es beim Erstellen eines Handyspiels wichtig, ein Gleichgewicht zwischen der Anzeige aller Tasten auf dem Bildschirm und gleichzeitig Benutzerfreundlichkeit und Übersichtlichkeit zu finden.

Unity Mobile-Steuerelemente

In diesem Tutorial zeige ich, wie man mit UI Canvas voll funktionsfähige mobile Steuerelemente (Joysticks und Schaltflächen) in Unity erstellt.

Schritt 1: Erstellen Sie alle erforderlichen Skripte

Dieses Tutorial enthält zwei Skripte: SC_ClickTracker.cs und SC_MobileControls.cs. Das erste Skript hört auf die Klickereignisse und das zweite Skript liest die aus den Ereignissen generierten Werte.

SC_ClickTracker.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;

#if UNITY_EDITOR
using UnityEditor;
#endif

public class SC_ClickTracker : MonoBehaviour, IPointerDownHandler, IDragHandler, IPointerUpHandler
{
    public string buttonName = ""; //This should be an unique name of the button
    public bool isJoystick = false;
    public float movementLimit = 1; //How far the joystick can be moved (n x Joystick Width)
    public float movementThreshold = 0.1f; //Minimum distance (n x Joystick Width) that the Joystick need to be moved to trigger inputAxis (Must be less than movementLimit)

    //Reference variables
    RectTransform rt;
    Vector3 startPos;
    Vector2 clickPos;

    //Input variables
    Vector2 inputAxis = Vector2.zero;
    bool holding = false;
    bool clicked = false;

    void Start()
    {
        //Add this button to the list
        SC_MobileControls.instance.AddButton(this);

        rt = GetComponent<RectTransform>();
        startPos = rt.anchoredPosition3D;
    }

    //Do this when the mouse is clicked over the selectable object this script is attached to.
    public void OnPointerDown(PointerEventData eventData)
    {
        //Debug.Log(this.gameObject.name + " Was Clicked.");

        holding = true;

        if (!isJoystick)
        {
            clicked = true;
            StartCoroutine(StopClickEvent());
        }
        else
        {
            //Initialize Joystick movement
            clickPos = eventData.pressPosition;
        }
    }

    WaitForEndOfFrame waitForEndOfFrame = new WaitForEndOfFrame();

    //Wait for next update then release the click event
    IEnumerator StopClickEvent()
    {
        yield return waitForEndOfFrame;

        clicked = false;
    }

    //Joystick movement
    public void OnDrag(PointerEventData eventData)
    {
        //Debug.Log(this.gameObject.name + " The element is being dragged");

        if (isJoystick)
        {
            Vector3 movementVector = Vector3.ClampMagnitude((eventData.position - clickPos) / SC_MobileControls.instance.canvas.scaleFactor, (rt.sizeDelta.x * movementLimit) + (rt.sizeDelta.x * movementThreshold));
            Vector3 movePos = startPos + movementVector;
            rt.anchoredPosition = movePos;

            //Update inputAxis
            float inputX = 0;
            float inputY = 0;
            if (Mathf.Abs(movementVector.x) > rt.sizeDelta.x * movementThreshold)
            {
                inputX = (movementVector.x - (rt.sizeDelta.x * movementThreshold * (movementVector.x > 0 ? 1 : -1))) / (rt.sizeDelta.x * movementLimit);
            }
            if (Mathf.Abs(movementVector.y) > rt.sizeDelta.x * movementThreshold)
            {
                inputY = (movementVector.y - (rt.sizeDelta.x * movementThreshold * (movementVector.y > 0 ? 1 : -1))) / (rt.sizeDelta.x * movementLimit);
            }
            inputAxis = new Vector2(inputX, inputY);
        }
    }

    //Do this when the mouse click on this selectable UI object is released.
    public void OnPointerUp(PointerEventData eventData)
    {
        //Debug.Log(this.gameObject.name + " The mouse click was released");

        holding = false;

        if (isJoystick)
        {
            //Reset Joystick position
            rt.anchoredPosition = startPos;
            inputAxis = Vector2.zero;
        }
    }

    public Vector2 GetInputAxis()
    {
        return inputAxis;
    }

    public bool GetClickedStatus()
    {
        return clicked;
    }

    public bool GetHoldStatus()
    {
        return holding;
    }
}

#if UNITY_EDITOR
//Custom Editor
[CustomEditor(typeof(SC_ClickTracker))]
public class SC_ClickTracker_Editor : Editor
{
    public override void OnInspectorGUI()
    {
        SC_ClickTracker script = (SC_ClickTracker)target;

        script.buttonName = EditorGUILayout.TextField("Button Name", script.buttonName);
        script.isJoystick = EditorGUILayout.Toggle("Is Joystick", script.isJoystick);
        if (script.isJoystick)
        {
            script.movementLimit = EditorGUILayout.FloatField("Movement Limit", script.movementLimit);
            script.movementThreshold = EditorGUILayout.FloatField("Movement Threshold", script.movementThreshold);
        }
    }
}
#endif

SC_MobileControls.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SC_MobileControls : MonoBehaviour
{
    [HideInInspector]
    public Canvas canvas;
    List<SC_ClickTracker> buttons = new List<SC_ClickTracker>();

    public static SC_MobileControls instance;

    void Awake()
    {
        //Assign this script to static variable, so it can be accessed from other scripts. Make sure there is only one SC_MobileControls in the Scene.
        instance = this;

        canvas = GetComponent<Canvas>();
    }

    public int AddButton(SC_ClickTracker button)
    {
        buttons.Add(button);

        return buttons.Count - 1;
    }

    public Vector2 GetJoystick(string joystickName)
    {
        for(int i = 0; i < buttons.Count; i++)
        {
            if(buttons[i].buttonName == joystickName)
            {
                return buttons[i].GetInputAxis();
            }
        }

        Debug.LogError("Joystick with a name '" + joystickName + "' not found. Make sure SC_ClickTracker is assigned to the button and the name is matching.");

        return Vector2.zero;
    }

    public bool GetMobileButton(string buttonName)
    {
        for (int i = 0; i < buttons.Count; i++)
        {
            if (buttons[i].buttonName == buttonName)
            {
                return buttons[i].GetHoldStatus();
            }
        }

        Debug.LogError("Button with a name '" + buttonName + "' not found. Make sure SC_ClickTracker is assigned to the button and the name is matching.");

        return false;
    }

    public bool GetMobileButtonDown(string buttonName)
    {
        for (int i = 0; i < buttons.Count; i++)
        {
            if (buttons[i].buttonName == buttonName)
            {
                return buttons[i].GetClickedStatus();
            }
        }

        Debug.LogError("Button with a name '" + buttonName + "' not found. Make sure SC_ClickTracker is assigned to the button and the name is matching.");

        return false;
    }
}

Schritt 2: Mobile Steuerelemente einrichten

  • Erstellen Sie eine neue Leinwand (GameObject -> UI -> Canvas)
  • Ändern Sie 'UI Scale Mode' im Canvas Scaler in 'Scale With Screen Size' und ändern Sie die Referenzauflösung auf die, mit der Sie arbeiten (in meinem Fall ist es 1000 x 600).
  • Hängen Sie das SC_MobileControls-Skript an das Canvas-Objekt an
  • Klicken Sie mit der rechten Maustaste auf Canvas-Objekt -> Benutzeroberfläche -> Bild
  • Benennen Sie das neu erstellte Bild um in "JoystickLeft"
  • Ändern Sie das Sprite "JoystickLeft" in einen leeren Kreis (vergessen Sie nicht, den Texturtyp nach dem Import in Unity in 'Sprite (2D and UI)' zu ändern)

  • Stellen Sie "JoystickLeft" Rect Transform-Werte wie im Screenshot unten ein:

  • Stellen Sie in der Bildkomponente den Farbalpha auf 0,5 ein, um das Sprite leicht transparent zu machen:

  • Duplizieren Sie das "JoystickLeft"-Objekt und benennen Sie es in um "JoystickLeftButton"
  • Bewegen Sie "JoystickLeftButton" innerhalb des "JoystickLeft"-Objekts
  • Ändern Sie das Sprite "JoystickLeftButton" in einen gefüllten Kreis:

  • Stellen Sie "JoystickLeftButton" Rect Transform-Werte wie im Screenshot unten ein:

  • Button-Komponente hinzufügen zu "JoystickLeftButton"
  • Ändern Sie in der Schaltflächenkomponente den Übergang in 'None'
  • Hängen Sie das SC_ClickTracker-Skript an "JoystickLeftButton"
  • Stellen Sie in SC_ClickTracker den Button-Namen auf einen beliebigen eindeutigen Namen ein (in meinem Fall habe ich ihn auf 'JoystickLeft' gesetzt) ​​und aktivieren Sie das Kontrollkästchen 'Is Joystick'.

Die Joystick-Taste ist bereit. Sie können eine beliebige Anzahl von Joysticks verwenden (in meinem Fall habe ich zwei, einen links zur Steuerung der Bewegung und einen rechts zur Steuerung der Drehung).

  • Duplizieren Sie "JoystickLeft" und benennen Sie es in um "JoystickRight"
  • Erweitern Sie "JoystickRight" und benennen Sie "JoystickLeftButton" in um "JoystickRightButton"
  • Stellen Sie "JoystickRight" Rect Transform-Werte wie im Screenshot unten ein:

  • Wählen Sie das "JoystickRightButton"-Objekt aus und ändern Sie in SC_ClickTracker den Button-Namen in 'JoystickRight'

Der zweite Joystick ist fertig.

Jetzt erstellen wir eine normale Schaltfläche:

  • Klicken Sie mit der rechten Maustaste auf Canvas-Objekt -> Benutzeroberfläche -> Schaltfläche
  • Schaltflächenobjekt umbenennen in "SprintButton"
  • Ändern Sie das Sprite "SprintButton" in einen Kreis mit Abschrägungseffekt:

  • Stellen Sie "SprintButton" Rect Transform-Werte wie im Screenshot unten ein:

  • Ändern Sie den Alpha-Wert für die Bildfarbe "SprintButton" auf 0,5
  • Hängen Sie das SC_ClickTracker-Skript an das "SprintButton"-Objekt an
  • Ändern Sie in SC_ClickTracker den Button-Namen in 'Sprinting'
  • Wählen Sie das Textobjekt in "SprintButton" aus und ändern Sie den Text in 'Sprint', ändern Sie außerdem die Schriftgröße in 'Bold'

Unity Mobile-Taste

Der Knopf ist fertig.

Wir werden eine weitere Schaltfläche namens "Jump" erstellen:

  • Duplizieren Sie das "SprintButton"-Objekt und benennen Sie es in um "JumpButton"
  • Ändern Sie den Wert "JumpButton" Pos Y auf 250
  • Ändern Sie in SC_ClickTracker den Button-Namen in 'Jumping'
  • Ändern Sie den Text in "JumpButton" in 'Jump'

Und der letzte Button ist "Action":

  • Duplizieren Sie das "JumpButton"-Objekt und benennen Sie es in um "ActionButton"
  • Ändern Sie den Wert "ActionButton" Pos X in -185
  • Ändern Sie in SC_ClickTracker den Button-Namen in 'Action'
  • Ändern Sie den Text in "ActionButton" in 'Action'

Schritt 3: Mobile Steuerelemente implementieren

Wenn Sie die oben genannten Schritte befolgt haben, können Sie nun diese Funktionen verwenden, um die mobilen Steuerelemente in Ihrem Skript zu implementieren:

if(SC_MobileControls.instance.GetMobileButtonDown("BUTTON_NAME")){
	//Mobile button has been pressed one time, equivalent to if(Input.GetKeyDown(KeyCode...))
}

if(SC_MobileControls.instance.GetMobileButton("BUTTON_NAME")){
	//Mobile button is being held pressed, equivalent to if(Input.GetKey(KeyCode...))
}

//Get normalized direction of a on-screen Joystick
//Could be compared to: new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")) or new Vector2(Input.GetAxis("Mouse X"), Input.GetAxis("Mouse Y"))
Vector2 inputAxis = SC_MobileControls.instance.GetJoystick("JOYSTICK_NAME");

Als Beispiel werde ich mobile Steuerungen mit einem FPS-Controller aus diesem Tutorial implementieren. Folgen Sie zuerst diesem Tutorial, es ist ziemlich einfach.

Wenn Sie diesem Tutorial gefolgt wären, hätten Sie jetzt das "FPSPlayer"-Objekt zusammen mit Canvas mit mobilen Steuerelementen.

Wir werden die Desktop-Steuerelemente beibehalten und gleichzeitig die mobilen Steuerelemente implementieren, um es plattformübergreifend zu machen:

  • Öffnen Sie das SC_FPSController-Skript, scrollen Sie bis zur Zeile 28 und entfernen Sie diesen Teil (durch das Entfernen dieses Teils wird verhindert, dass der Cursor gesperrt wird, und das Klicken auf mobile Steuerelemente im Editor wird ermöglicht.):
        // Lock cursor
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
  • Scrollen Sie bis Zeile 39 und ersetzen Sie:
        bool isRunning = Input.GetKey(KeyCode.LeftShift);
        float curSpeedX = canMove ? (isRunning ? runningSpeed : walkingSpeed) * Input.GetAxis("Vertical") : 0;
        float curSpeedY = canMove ? (isRunning ? runningSpeed : walkingSpeed) * Input.GetAxis("Horizontal") : 0;
  • Mit:
        bool isRunning = Input.GetKey(KeyCode.LeftShift) || SC_MobileControls.instance.GetMobileButton("Sprinting");
        float curSpeedX = canMove ? (isRunning ? runningSpeed : walkingSpeed) * (Input.GetAxis("Vertical") + SC_MobileControls.instance.GetJoystick("JoystickLeft").y) : 0;
        float curSpeedY = canMove ? (isRunning ? runningSpeed : walkingSpeed) * (Input.GetAxis("Horizontal") + SC_MobileControls.instance.GetJoystick("JoystickLeft").x) : 0;
  • Scrollen Sie nach unten bis Zeile 45 und ersetzen Sie:
        if (Input.GetButton("Jump") && canMove && characterController.isGrounded)
  • Mit:
        if ((Input.GetButton("Jump") || SC_MobileControls.instance.GetMobileButtonDown("Jumping")) && canMove && characterController.isGrounded)
  • Scrollen Sie nach unten bis Zeile 68 und ersetzen Sie:
            rotationX += -Input.GetAxis("Mouse Y") * lookSpeed;
            rotationX = Mathf.Clamp(rotationX, -lookXLimit, lookXLimit);
            playerCamera.transform.localRotation = Quaternion.Euler(rotationX, 0, 0);
            transform.rotation *= Quaternion.Euler(0, Input.GetAxis("Mouse X") * lookSpeed, 0);
  • Mit:
#if UNITY_IPHONE || UNITY_ANDROID || UNITY_EDITOR
            rotationX += -(SC_MobileControls.instance.GetJoystick("JoystickRight").y) * lookSpeed;
#else
            rotationX += -Input.GetAxis("Mouse Y") * lookSpeed;
#endif
            rotationX = Mathf.Clamp(rotationX, -lookXLimit, lookXLimit);
            playerCamera.transform.localRotation = Quaternion.Euler(rotationX, 0, 0);
#if UNITY_IPHONE || UNITY_ANDROID || UNITY_EDITOR
            transform.rotation *= Quaternion.Euler(0, SC_MobileControls.instance.GetJoystick("JoystickRight").x * lookSpeed, 0);
#else
            transform.rotation *= Quaternion.Euler(0, Input.GetAxis("Mouse X") * lookSpeed, 0);
#endif

Da die Look-Bewegung die Joystick-Tests im Editor beeinträchtigt, verwenden wir #if für die plattformspezifische Kompilierung, um die mobile Logik vom Rest der Plattformen zu trennen.

Der Mobile FPS Controller ist jetzt fertig, testen wir ihn:

Sharp Coder Videoplayer

Wie Sie sehen können, sind alle Joysticks und Tasten funktionsfähig (mit Ausnahme der "Action"-Taste, die nicht implementiert wurde, da es keine passende Funktion dafür gab).