Unity Cara Membuat Kontrol Sentuh Seluler

Kontrol adalah salah satu bagian terpenting dalam sebuah video game, dan tidak mengherankan, kontrol itulah yang memungkinkan pemain berinteraksi dengan dunia game.

Kontrol permainan adalah sinyal yang dikirim melalui interaksi perangkat keras (mouse/keyboard, pengontrol, layar sentuh, dll.) yang kemudian diproses oleh kode permainan, menerapkan tindakan tertentu.

PC dan Konsol Game memiliki tombol fisik yang dapat ditekan, namun, perangkat seluler modern hanya memiliki beberapa tombol fisik, interaksi selanjutnya dilakukan melalui gerakan sentuh, yang berarti tombol permainan perlu ditampilkan di layar. Itu sebabnya saat membuat game seluler, penting untuk menemukan keseimbangan antara menampilkan semua tombol di layar sekaligus menjaganya tetap ramah pengguna dan bebas dari kekacauan.

Kontrol Seluler Unity

Dalam tutorial ini, saya akan menunjukkan cara membuat kontrol seluler berfitur lengkap (Joystick dan Tombol) di Unity menggunakan UI Canvas.

Langkah 1: Buat Semua Skrip yang Diperlukan

Tutorial ini menampilkan 2 skrip, SC_ClickTracker.cs, dan SC_MobileControls.cs. Skrip pertama akan mendengarkan peristiwa klik dan skrip kedua akan membaca nilai yang dihasilkan dari peristiwa tersebut.

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

Langkah 2: Siapkan Kontrol Seluler

  • Buat Kanvas baru (GameObject -> UI -> Canvas)
  • Ubah 'UI Scale Mode' di Canvas Scaler menjadi 'Scale With Screen Size' dan ubah Resolusi Referensi ke resolusi yang sedang Anda kerjakan (dalam kasus saya 1000 x 600)
  • Lampirkan skrip SC_MobileControls ke Objek Canvas
  • Klik kanan pada Objek Kanvas -> UI -> Gambar
  • Ganti nama Gambar yang baru dibuat menjadi "JoystickLeft"
  • Ubah "JoystickLeft" Sprite menjadi lingkaran kosong (jangan lupa ubah Texture Type menjadi 'Sprite (2D and UI)' setelah import ke Unity)

  • Atur nilai "JoystickLeft" Rect Transform sama seperti pada screenshot di bawah ini:

  • Pada komponen Image, atur Color alpha menjadi 0.5 untuk membuat sprite sedikit transparan:

  • Gandakan Objek "JoystickLeft" dan ganti namanya menjadi "JoystickLeftButton"
  • Pindahkan "JoystickLeftButton" ke dalam Objek "JoystickLeft"
  • Ubah Sprite "JoystickLeftButton" menjadi lingkaran penuh:

  • Atur "JoystickLeftButton" nilai Rect Transform sama seperti pada gambar di bawah:

  • Tambahkan komponen Tombol ke "JoystickLeftButton"
  • Pada komponen Tombol, ubah Transisi menjadi 'None'
  • Lampirkan skrip SC_ClickTracker ke "JoystickLeftButton"
  • Di SC_ClickTracker atur Nama Tombol ke nama unik apa pun (dalam kasus saya, saya mengaturnya ke 'JoystickLeft') dan aktifkan kotak centang 'Is Joystick'.

Tombol Joystick sudah siap. Anda dapat memiliki sejumlah Joystick (Dalam kasus saya, saya akan memiliki 2, satu di kiri untuk mengontrol gerakan dan satu lagi di kanan untuk mengontrol rotasi).

  • Gandakan "JoystickLeft" dan ganti namanya menjadi "JoystickRight"
  • Perluas "JoystickRight" dan ganti nama "JoystickLeftButton" menjadi "JoystickRightButton"
  • Atur "JoystickRight" nilai Rect Transform sama seperti pada gambar di bawah:

  • Pilih Objek "JoystickRightButton" dan di SC_ClickTracker ubah Nama Tombol menjadi 'JoystickRight'

Joystick kedua sudah siap.

Sekarang mari kita buat tombol biasa:

  • Klik kanan pada Objek Kanvas -> UI -> Tombol
  • Ganti nama Objek Tombol menjadi "SprintButton"
  • Ubah Sprite "SprintButton" menjadi Lingkaran dengan efek miring:

  • Atur "SprintButton" nilai Rect Transform sama seperti pada gambar di bawah:

  • Ubah "SprintButton" Warna gambar alpha menjadi 0,5
  • Lampirkan skrip SC_ClickTracker ke Objek "SprintButton"
  • Di SC_ClickTracker ubah Nama Tombol menjadi 'Sprinting'
  • Pilih Objek Teks di dalam "SprintButton" dan ubah teksnya menjadi 'Sprint', ubah juga Ukuran Font menjadi 'Bold'

Tombol Seluler Persatuan

Tombolnya sudah siap.

Kita akan membuat tombol lain yang disebut "Jump":

  • Gandakan Objek "SprintButton" dan ganti namanya menjadi "JumpButton"
  • Ubah nilai "JumpButton" Pos Y menjadi 250
  • Di SC_ClickTracker ubah Nama Tombol menjadi 'Jumping'
  • Ubah Teks di dalam "JumpButton" menjadi 'Jump'

Dan tombol terakhir adalah "Action":

  • Gandakan Objek "JumpButton" dan ganti namanya menjadi "ActionButton"
  • Ubah nilai "ActionButton" Pos X menjadi -185
  • Di SC_ClickTracker ubah Nama Tombol menjadi 'Action'
  • Ubah Teks di dalam "ActionButton" menjadi 'Action'

Langkah 3: Terapkan Kontrol Seluler

Jika Anda mengikuti langkah-langkah di atas, kini Anda dapat menggunakan fungsi berikut untuk menerapkan kontrol seluler di skrip Anda:

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

Sebagai contoh, saya akan mengimplementasikan kontrol seluler dengan Pengontrol FPS dari tutorial ini. Ikuti dulu tutorialnya, cukup sederhana.

Jika Anda mengikuti tutorial itu, Anda sekarang akan memiliki Objek "FPSPlayer" bersama dengan Canvas dengan kontrol seluler.

Kami akan mempertahankan kontrol Desktop sambil juga menerapkan kontrol seluler, menjadikannya lintas platform:

  • Buka skrip SC_FPSController, gulir hingga baris 28, dan hapus bagian ini (menghapus bagian itu akan mencegah kursor terkunci dan memungkinkan klik pada kontrol seluler di Editor.):
        // Lock cursor
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
  • Gulir hingga baris 39 dan ganti:
        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;
  • Dengan:
        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;
  • Gulir ke bawah hingga baris 45 dan ganti:
        if (Input.GetButton("Jump") && canMove && characterController.isGrounded)
  • Dengan:
        if ((Input.GetButton("Jump") || SC_MobileControls.instance.GetMobileButtonDown("Jumping")) && canMove && characterController.isGrounded)
  • Gulir ke bawah hingga baris 68 dan ganti:
            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);
  • Dengan:
#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

Karena gerakan tampilan akan mengganggu pengujian joystick di Editor, kami menggunakan #if untuk kompilasi khusus platform untuk memisahkan logika seluler dari platform lainnya.

Pengontrol FPS Seluler sekarang sudah siap, mari kita uji:

Sharp Coder Pemutar video

Seperti yang Anda lihat, semua Joystick dan Tombol berfungsi (kecuali tombol "Action", yang tidak diterapkan karena tidak memiliki fitur yang sesuai).

Sumber
📁MobileControls.unitypackage272.33 KB
Artikel yang Disarankan
Joystick Input Sentuh Seluler di Unity
Menciptakan Pergerakan Pemain dalam Kesatuan
Cara Membuat Kontrol Derek di Unity
Tutorial Senter untuk Unity
Pengendali Helikopter untuk Persatuan
Pengendali Pesawat untuk Persatuan
Tutorial Pengontrol Worm 3D untuk Unity