Unity Cum să faci comenzi tactile mobile

Comenzile sunt una dintre cele mai importante părți ale unui joc video și nu este surprinzător, este ceea ce permite jucătorilor să interacționeze cu lumea jocului.

Controalele jocului sunt semnale care sunt trimise prin interacțiunea hardware (mouse/tastatură, controler, ecran tactil etc.) care sunt apoi procesate de codul jocului, aplicând anumite acțiuni.

PC-urile și Console de jocuri au butoane fizice care pot fi apăsate, totuși, dispozitivele mobile moderne au doar câteva butoane fizice, restul interacțiunii se face prin gesturi tactile, ceea ce înseamnă că butoanele de joc trebuie să fie afișate pe ecran. De aceea, atunci când creați un joc pentru mobil, este important să găsiți un echilibru între a avea toate butoanele de pe ecran, păstrându-l ușor de utilizat și fără dezordine.

Comenzi mobile Unity

În acest tutorial, voi arăta cum să creați controale mobile complete (joystick-uri și butoane) în Unity folosind UI Canvas.

Pasul 1: Creați toate scripturile necesare

Acest tutorial conține 2 scripturi, SC_ClickTracker.cs și SC_MobileControls.cs. Primul script va asculta evenimentele de clic, iar al doilea script va citi valorile generate de evenimente.

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

Pasul 2: Configurați comenzile mobile

  • Creați o pânză nouă (GameObject -> UI -> Canvas)
  • Schimbați 'UI Scale Mode' în Canvas Scaler la 'Scale With Screen Size' și schimbați Rezoluția de referință la cea cu care lucrați (în cazul meu, este 1000 x 600)
  • Atașați scriptul SC_MobileControls la obiectul Canvas
  • Faceți clic dreapta pe Canvas Object -> UI -> Image
  • Redenumiți imaginea nou creată în "JoystickLeft"
  • Schimbați "JoystickLeft" Sprite într-un cerc gol (nu uitați să schimbați Tipul texturii la 'Sprite (2D and UI)' după ce îl importați în Unity)

  • Setați valorile "JoystickLeft" Rect Transform la fel ca în screenshot de mai jos:

  • În componenta Imagine, setați Color alpha la 0,5 pentru a face sprite-ul ușor transparent:

  • Duplicați obiectul "JoystickLeft" și redenumiți-l în "JoystickLeftButton"
  • Mutați "JoystickLeftButton" în interiorul obiectului "JoystickLeft"
  • Schimbați sprite-ul "JoystickLeftButton" într-un cerc plin:

  • Setați valorile "JoystickLeftButton" Rect Transform la fel ca în captura de ecran de mai jos:

  • Adăugați componenta Button la "JoystickLeftButton"
  • În componenta Buton, schimbați Tranziția la 'None'
  • Atașați scriptul SC_ClickTracker la "JoystickLeftButton"
  • În SC_ClickTracker setați Button Name la orice nume unic (în cazul meu l-am setat la 'JoystickLeft') și activați caseta de selectare 'Is Joystick'.

Butonul Joystick este gata. Puteți avea orice număr de Joystick-uri (În cazul meu voi avea 2, unul în stânga pentru a controla mișcarea și unul în dreapta pentru a controla rotația).

  • Duplicați "JoystickLeft" și redenumiți-l în "JoystickRight"
  • Extindeți "JoystickRight" și redenumiți "JoystickLeftButton" în "JoystickRightButton"
  • Setați valorile "JoystickRight" Rect Transform la fel ca în captura de ecran de mai jos:

  • Selectați obiectul "JoystickRightButton" și în SC_ClickTracker schimbați Numele butonului în 'JoystickRight'

Al doilea Joystick este gata.

Acum să creăm un buton obișnuit:

  • Faceți clic dreapta pe Canvas Object -> UI -> Button
  • Redenumiți obiectul buton în "SprintButton"
  • Schimbați sprite-ul "SprintButton" într-un cerc cu efect de teșire:

  • Setați valorile "SprintButton" Rect Transform la fel ca în captura de ecran de mai jos:

  • Schimbați culoarea alfa a imaginii "SprintButton" la 0,5
  • Atașați scriptul SC_ClickTracker la obiectul "SprintButton"
  • În SC_ClickTracker schimbați numele butonului în 'Sprinting'
  • Selectați obiectul text în interiorul "SprintButton" și modificați textul acestuia în 'Sprint', de asemenea, schimbați dimensiunea fontului în 'Bold'

Butonul Unity Mobile

Butonul este gata.

Vom crea un alt buton numit "Jump":

  • Duplicați obiectul "SprintButton" și redenumiți-l în "JumpButton"
  • Schimbați valoarea "JumpButton" Pos Y la 250
  • În SC_ClickTracker schimbați numele butonului în 'Jumping'
  • Schimbați textul din "JumpButton" în 'Jump'

Iar ultimul buton este "Action":

  • Duplicați obiectul "JumpButton" și redenumiți-l în "ActionButton"
  • Schimbați valoarea "ActionButton" Poz X la -185
  • În SC_ClickTracker, schimbați numele butonului în 'Action'
  • Schimbați textul din "ActionButton" în 'Action'

Pasul 3: implementați controalele mobile

Dacă ați urmat pașii de mai sus, acum puteți utiliza aceste funcții pentru a implementa controalele mobile din scriptul dvs.:

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");

Ca exemplu, voi implementa controale mobile cu un controler FPS din acest tutorial. Urmează mai întâi acel tutorial, este destul de simplu.

Dacă ai urma acel tutorial, ai avea acum obiectul "FPSPlayer" împreună cu Canvas cu comenzi mobile.

Vom păstra controalele desktop în timp ce implementăm controalele mobile, făcându-l multiplatform:

  • Deschideți scriptul SC_FPSController, derulați până la linia 28 și eliminați această parte (eliminarea acelei părți va împiedica blocarea cursorului și va permite să faceți clic pe controalele mobile din Editor.):
        // Lock cursor
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
  • Derulați până la linia 39 și înlocuiți:
        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;
  • Cu:
        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;
  • Derulați în jos până la linia 45 și înlocuiți:
        if (Input.GetButton("Jump") && canMove && characterController.isGrounded)
  • Cu:
        if ((Input.GetButton("Jump") || SC_MobileControls.instance.GetMobileButtonDown("Jumping")) && canMove && characterController.isGrounded)
  • Derulați în jos până la linia 68 și înlocuiți:
            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);
  • Cu:
#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

Deoarece mișcarea aspectului va interfera cu testarea joystick-ului din Editor, folosim #if pentru compilare specifică platformei pentru a separa logica mobilă de restul platformelor.

Controlerul mobil FPS este acum gata, haideți să-l testăm:

Sharp Coder Video player

După cum puteți vedea, toate Joystick-urile și butoanele sunt funcționale (cu excepția butonului "Action", care nu a fost implementat din cauza lipsei unei funcții adecvate pentru el).