Unity Come creare controlli touch mobili
I controlli sono una delle parti più importanti di un videogioco e, nessuna sorpresa, sono ciò che consente ai giocatori di interagire con il mondo di gioco.
I controlli di gioco sono segnali inviati attraverso l'interazione hardware (mouse/tastiera, controller, touchscreen, ecc.) che vengono poi elaborati dal codice del gioco, applicando determinate azioni.
I PC e Console di gioco hanno pulsanti fisici che possono essere premuti, tuttavia, i moderni dispositivi mobili hanno solo pochi pulsanti fisici, il resto dell'interazione avviene tramite gesti tattili, il che significa che i pulsanti di gioco devono essere visualizzati sullo schermo. Ecco perché quando crei un gioco per cellulare, è importante trovare un equilibrio tra avere tutti i pulsanti sullo schermo mantenendolo facile da usare e privo di ingombri.

In questo tutorial mostrerò come creare controlli mobili completi di funzionalità (joystick e pulsanti) in Unity utilizzando UI Canvas.
Passaggio 1: crea tutti gli script necessari
Questo tutorial presenta 2 script, SC_ClickTracker.cs e SC_MobileControls.cs. Il primo script ascolterà gli eventi clic e il secondo script leggerà i valori generati dagli eventi.
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);
        }
    }
}
#endifSC_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;
    }
}Passaggio 2: imposta i controlli mobili
- Crea una nuova tela (GameObject -> UI -> Canvas)
- Cambia 'UI Scale Mode' in Canvas Scaler in 'Scale With Screen Size' e cambia la Risoluzione di riferimento in quella con cui stai lavorando (nel mio caso è 1000 x 600)
- Allega lo script SC_MobileControls all'oggetto Canvas
- Fare clic con il tasto destro su Oggetto Canvas -> UI -> Immagine
- Rinominare l'immagine appena creata in "JoystickLeft"
- Cambia "JoystickLeft" Sprite in un cerchio vuoto (non dimenticare di cambiare il tipo di texture in 'Sprite (2D and UI)' dopo averlo importato in Unity)

- Imposta i valori "JoystickLeft" Rect Transform come nello screenshot di seguito:

- Nel componente Immagine, imposta Colore alfa su 0,5 per rendere lo sprite leggermente trasparente:

- Duplica l'oggetto "JoystickLeft" e rinominalo in "JoystickLeftButton"
- Muovi "JoystickLeftButton" all'interno dell'oggetto "JoystickLeft"
- Cambia lo Sprite "JoystickLeftButton" in un cerchio pieno:

- Imposta i valori "JoystickLeftButton" Rect Transform come nello screenshot seguente:


- Aggiungi il componente Pulsante a "JoystickLeftButton"
- Nel componente Pulsante cambia Transizione in 'None'
- Allega lo script SC_ClickTracker a "JoystickLeftButton"
- In SC_ClickTracker imposta il Nome del pulsante su qualsiasi nome univoco (nel mio caso l'ho impostato su 'JoystickLeft') e abilita la casella di controllo 'Is Joystick'.

Il pulsante del joystick è pronto. Puoi avere un numero qualsiasi di Joystick (nel mio caso ne avrò 2, uno a sinistra per controllare il movimento e uno a destra per controllare la rotazione).
- Duplica "JoystickLeft" e rinominalo in "JoystickRight"
- Espandi "JoystickRight" e rinomina "JoystickLeftButton" in "JoystickRightButton"
- Imposta i valori "JoystickRight" Rect Transform come nello screenshot seguente:

- Seleziona l'oggetto "JoystickRightButton" e in SC_ClickTracker modifica il nome del pulsante in 'JoystickRight'
Il secondo Joystick è pronto.
Ora creiamo un pulsante normale:
- Fare clic con il tasto destro su Oggetto Canvas -> UI -> Pulsante
- Rinomina oggetto pulsante in "SprintButton"
- Cambia lo Sprite "SprintButton" in un Cerchio con un effetto smussato:

- Imposta i valori "SprintButton" Rect Transform come nello screenshot seguente:

- Cambia "SprintButton" il colore alfa dell'immagine su 0,5
- Allega lo script SC_ClickTracker all'oggetto "SprintButton"
- In SC_ClickTracker cambia il nome del pulsante in 'Sprinting'
- Seleziona Oggetto testo all'interno di "SprintButton" e cambia il suo testo in 'Sprint', cambia anche la dimensione del carattere in 'Bold'

Il pulsante è pronto.
Creeremo un altro pulsante chiamato "Jump":
- Duplica l'oggetto "SprintButton" e rinominalo in "JumpButton"
- Cambia il valore "JumpButton" Pos Y su 250
- In SC_ClickTracker cambia il nome del pulsante in 'Jumping'
- Cambia il testo all'interno di "JumpButton" in 'Jump'
E l'ultimo pulsante è "Action":
- Duplica l'oggetto "JumpButton" e rinominalo in "ActionButton"
- Cambia il valore "ActionButton" Pos X in -185
- In SC_ClickTracker cambia il nome del pulsante in 'Action'
- Cambia il testo all'interno di "ActionButton" in 'Action'
Passaggio 3: implementa i controlli mobili
Se hai seguito i passaggi precedenti, ora puoi utilizzare queste funzioni per implementare i controlli mobili nel tuo script:
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");Ad esempio, implementerò i controlli mobili con un controller FPS da questo tutorial. Vai prima a seguire quel tutorial, è abbastanza semplice.
Se seguissi quel tutorial ora avresti l'oggetto "FPSPlayer" insieme a Canvas con controlli mobili.
Preserveremo i controlli desktop implementando anche i controlli mobili, rendendolo multipiattaforma:
- Apri lo script SC_FPSController, scorri fino alla riga 28 e rimuovi questa parte (rimuovere quella parte impedirà il blocco del cursore e consentirà di fare clic sui controlli mobili nell'editor.):
        // Lock cursor
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;- Scorri fino alla riga 39 e sostituisci:
        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;- Con:
        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;- Scorri verso il basso fino alla riga 45 e sostituisci:
        if (Input.GetButton("Jump") && canMove && characterController.isGrounded)- Con:
        if ((Input.GetButton("Jump") || SC_MobileControls.instance.GetMobileButtonDown("Jumping")) && canMove && characterController.isGrounded)- Scorri verso il basso fino alla riga 68 e sostituisci:
            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);- Con:
#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);
#endifPoiché il movimento dello sguardo interferirà con il test del joystick nell'editor, utilizziamo #if per compilazione specifica della piattaforma per separare la logica mobile dal resto delle piattaforme.
Il Mobile FPS Controller è ora pronto, testiamolo:

Come puoi vedere, tutti i joystick e i pulsanti sono funzionanti (tranne il pulsante "Action", che non è stato implementato perché non disponeva di una funzionalità adatta per questo).