Unity Jak tworzyć mobilne elementy sterujące dotykiem

Sterowanie to jedna z najważniejszych części gier wideo i nic dziwnego, że to ona pozwala graczom na interakcję ze światem gry.

Sterowanie grą to sygnały wysyłane poprzez interakcję sprzętową (mysz/klawiatura, kontroler, ekran dotykowy itp.), które są następnie przetwarzane przez kod gry, wykonując określone działania.

Komputery PC i Konsole do gier mają fizyczne przyciski, które można nacisnąć, jednak nowoczesne urządzenia mobilne mają tylko kilka fizycznych przycisków, reszta interakcji odbywa się za pomocą gestów dotykowych, co oznacza, że ​​przyciski gry muszą być wyświetlane na ekranie. Dlatego tworząc grę mobilną, ważne jest, aby znaleźć równowagę pomiędzy umieszczeniem wszystkich przycisków na ekranie, a jednocześnie dbaniem o to, aby był on przyjazny dla użytkownika i uporządkowany.

Sterowanie mobilne Unity

W tym samouczku pokażę, jak utworzyć w pełni funkcjonalne mobilne elementy sterujące (joysticki i przyciski) w Unity przy użyciu UI Canvas.

Krok 1: Utwórz wszystkie niezbędne skrypty

Ten samouczek zawiera 2 skrypty, SC_ClickTracker.cs i SC_MobileControls.cs. Pierwszy skrypt będzie nasłuchiwał zdarzeń kliknięcia, a drugi odczytał wartości wygenerowane na podstawie tych zdarzeń.

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

Krok 2: Skonfiguruj sterowanie mobilne

  • Utwórz nowe płótno (GameObject -> UI -> Canvas)
  • Zmień 'UI Scale Mode' w Canvas Scaler na 'Scale With Screen Size' i zmień rozdzielczość odniesienia na tę, z którą pracujesz (w moim przypadku jest to 1000 x 600)
  • Dołącz skrypt SC_MobileControls do obiektu Canvas
  • Kliknij prawym przyciskiem myszy obiekt Canvas -> Interfejs użytkownika -> Obraz
  • Zmień nazwę nowo utworzonego obrazu na "JoystickLeft"
  • Zmień "JoystickLeft" Sprite'a na pusty okrąg (nie zapomnij zmienić typu tekstury na 'Sprite (2D and UI)' po zaimportowaniu go do Unity)

  • Ustaw wartości "JoystickLeft" Rect Transform tak samo, jak na zrzucie ekranu poniżej:

  • W komponencie Obraz ustaw wartość Kolor alfa na 0,5, aby duszek był lekko przezroczysty:

  • Zduplikuj obiekt "JoystickLeft" i zmień jego nazwę na "JoystickLeftButton"
  • Przesuń "JoystickLeftButton" wewnątrz obiektu "JoystickLeft"
  • Zmień duszka "JoystickLeftButton" na wypełnione koło:

  • Ustaw wartości "JoystickLeftButton" Rect Transform tak samo, jak na zrzucie ekranu poniżej:

  • Dodaj komponent Button do "JoystickLeftButton"
  • W komponencie Przycisk zmień Przejście na 'None'
  • Dołącz skrypt SC_ClickTracker do "JoystickLeftButton"
  • W SC_ClickTracker ustaw nazwę przycisku na dowolną unikalną nazwę (w moim przypadku ustawiłem ją na 'JoystickLeft') i zaznacz pole wyboru 'Is Joystick'.

Przycisk joysticka jest gotowy. Możesz mieć dowolną liczbę joysticków (w moim przypadku będę miał 2, jeden po lewej stronie do sterowania ruchem i jeden po prawej stronie do sterowania obrotem).

  • Zduplikuj "JoystickLeft" i zmień jego nazwę na "JoystickRight"
  • Rozwiń "JoystickRight" i zmień nazwę "JoystickLeftButton" na "JoystickRightButton"
  • Ustaw wartości "JoystickRight" Rect Transform tak samo, jak na zrzucie ekranu poniżej:

  • Wybierz obiekt "JoystickRightButton" i w SC_ClickTracker zmień nazwę przycisku na 'JoystickRight'

Drugi joystick jest gotowy.

Teraz utwórzmy zwykły przycisk:

  • Kliknij prawym przyciskiem myszy obiekt Canvas -> Interfejs użytkownika -> Przycisk
  • Zmień nazwę obiektu przycisku na "SprintButton"
  • Zmień duszka "SprintButton" na okrąg z efektem skosu:

  • Ustaw wartości "SprintButton" Rect Transform tak samo, jak na zrzucie ekranu poniżej:

  • Zmień "SprintButton" Kolor obrazu alfa na 0,5
  • Dołącz skrypt SC_ClickTracker do obiektu "SprintButton"
  • W SC_ClickTracker zmień nazwę przycisku na 'Sprinting'
  • Wybierz obiekt tekstowy wewnątrz "SprintButton" i zmień jego tekst na 'Sprint', a także zmień Rozmiar czcionki na 'Bold'

Przycisk mobilny Unity

Przycisk jest gotowy.

Zamierzamy utworzyć kolejny przycisk o nazwie "Jump":

  • Zduplikuj obiekt "SprintButton" i zmień jego nazwę na "JumpButton"
  • Zmień wartość "JumpButton" Pos Y na 250
  • W SC_ClickTracker zmień nazwę przycisku na 'Jumping'
  • Zmień tekst wewnątrz "JumpButton" na 'Jump'

A ostatni przycisk to "Action":

  • Zduplikuj obiekt "JumpButton" i zmień jego nazwę na "ActionButton"
  • Zmień wartość "ActionButton" Poz X na -185
  • W SC_ClickTracker zmień nazwę przycisku na 'Action'
  • Zmień tekst wewnątrz "ActionButton" na 'Action'

Krok 3: Wdróż kontrolę mobilną

Jeśli wykonałeś powyższe kroki, możesz teraz użyć tych funkcji, aby zaimplementować kontrolki mobilne w swoim skrypcie:

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

Jako przykład zaimplementuję sterowanie mobilne za pomocą kontrolera FPS z tego samouczka. Najpierw postępuj zgodnie z tym samouczkiem, to dość proste.

Jeśli postępowałeś zgodnie z tym samouczkiem, będziesz mieć teraz obiekt "FPSPlayer" wraz z kanwą z mobilnymi elementami sterującymi.

Zachowamy elementy sterujące przeznaczone dla komputerów stacjonarnych, jednocześnie wdrażając elementy sterujące mobilne, dzięki czemu będzie to rozwiązanie wieloplatformowe:

  • Otwórz skrypt SC_FPSController, przewiń do linii 28 i usuń tę część (usunięcie tej części zapobiegnie zablokowaniu kursora i umożliwi klikanie elementów sterujących urządzeń mobilnych w Edytorze.):
        // Lock cursor
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
  • Przewiń do linii 39 i zamień:
        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;
  • Z:
        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;
  • Przewiń w dół do linii 45 i zamień:
        if (Input.GetButton("Jump") && canMove && characterController.isGrounded)
  • Z:
        if ((Input.GetButton("Jump") || SC_MobileControls.instance.GetMobileButtonDown("Jumping")) && canMove && characterController.isGrounded)
  • Przewiń w dół do linii 68 i zamień:
            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);
  • Z:
#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

Ponieważ ruch wyglądu będzie kolidował z testowaniem joysticka w Edytorze, używamy #if do kompilacji specyficznej dla platformy, aby oddzielić logikę mobilną od reszty platform.

Mobilny kontroler FPS jest już gotowy, przetestujmy go:

Sharp Coder Odtwarzacz wideo

Jak widać, wszystkie joysticki i przyciski działają (z wyjątkiem przycisku "Action", który nie został zaimplementowany ze względu na brak odpowiedniej funkcji).

Źródło
📁MobileControls.unitypackage272.33 KB
Sugerowane artykuły
Tworzenie ruchu gracza w jedności
Mobilny joystick wejściowy dotykowy w Unity
Kontroler samolotu dla Unity
Kontroler Unity FPS
Jak sterować dźwigiem w Unity
Jak dodać obsługę platformy ruchomej do kontrolera postaci w Unity
Kontroler helikoptera dla Unity