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.

Controlli mobili Unity

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);
        }
    }
}
#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;
    }
}

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'

Pulsante Unità mobile

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);
#endif

Poiché 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:

Sharp Coder Lettore video

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).

Fonte
📁MobileControls.unitypackage272.33 KB
Articoli suggeriti
Joystick di input tattile mobile in Unity
Creazione del movimento del giocatore in Unity
Come eseguire il controllo della gru in Unity
Controller aereo per Unity
Tutorial sulla torcia elettrica per Unity
Controller FPS Unity
Controller per lettori RTS e MOBA per Unity