Unity Hoe u mobiele aanraakbedieningen maakt

De besturing is een van de belangrijkste onderdelen van een videogame, en het is geen verrassing dat spelers hiermee interactie kunnen hebben met de gamewereld.

Spelbesturingen zijn signalen die worden verzonden via hardware-interactie (muis/toetsenbord, controller, touchscreen, enz.) en vervolgens worden verwerkt door de spelcode, waarbij bepaalde acties worden toegepast.

Pc's en Gamingconsoles hebben fysieke knoppen die kunnen worden ingedrukt, maar moderne mobiele apparaten hebben slechts een paar fysieke knoppen, de rest van de interactie gebeurt via aanraakgebaren, wat betekent dat spelknoppen op het scherm moeten worden weergegeven. Daarom is het bij het maken van een mobiele game belangrijk om een ​​balans te vinden tussen alle knoppen op het scherm en tegelijkertijd gebruiksvriendelijk en overzichtelijk te houden.

Unity mobiele bediening

In deze tutorial laat ik zien hoe je volledig uitgeruste mobiele bedieningselementen (joysticks en knoppen) kunt maken in Unity met behulp van UI Canvas.

Stap 1: Maak alle benodigde scripts

Deze tutorial bevat twee scripts: SC_ClickTracker.cs en SC_MobileControls.cs. Het eerste script luistert naar de klikgebeurtenissen en het tweede script leest de waarden die uit de gebeurtenissen zijn gegenereerd.

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

Stap 2: Mobiele bediening instellen

  • Maak een nieuw canvas (GameObject -> UI -> Canvas)
  • Wijzig 'UI Scale Mode' in Canvas Scaler naar 'Scale With Screen Size' en wijzig de referentieresolutie naar degene waarmee u werkt (in mijn geval is dit 1000 x 600)
  • Voeg het SC_MobileControls-script toe aan Canvas-object
  • Klik met de rechtermuisknop op Canvasobject -> UI -> Afbeelding
  • Hernoem de nieuw gemaakte afbeelding naar "JoystickLeft"
  • Verander "JoystickLeft" Sprite in een lege cirkel (vergeet niet om Texture Type te veranderen naar 'Sprite (2D and UI)' na het importeren naar Unity)

  • Stel "JoystickLeft" Rect Transform-waarden hetzelfde in als in de screenshot hieronder:

  • Stel in de component Afbeelding de Kleur alfa in op 0,5 om de sprite enigszins transparant te maken:

  • Dupliceer het "JoystickLeft"-object en hernoem het naar "JoystickLeftButton"
  • Verplaats "JoystickLeftButton" binnen het "JoystickLeft"-object
  • Verander de "JoystickLeftButton" Sprite in een gevulde cirkel:

  • Stel "JoystickLeftButton" Rect Transform-waarden hetzelfde in als in de onderstaande schermafbeelding:

  • Knopcomponent toevoegen aan "JoystickLeftButton"
  • Wijzig in het onderdeel Knop Overgang naar 'None'
  • Voeg het SC_ClickTracker-script toe aan "JoystickLeftButton"
  • Stel in SC_ClickTracker de knopnaam in op een unieke naam (in mijn geval stel ik deze in op 'JoystickLeft') en schakel het selectievakje 'Is Joystick' in.

De joystickknop is gereed. Je kunt een willekeurig aantal joysticks hebben (in mijn geval heb ik er twee, één aan de linkerkant om de beweging te besturen en één aan de rechterkant om de rotatie te regelen).

  • Dupliceer "JoystickLeft" en hernoem het naar "JoystickRight"
  • Vouw "JoystickRight" uit en hernoem "JoystickLeftButton" naar "JoystickRightButton"
  • Stel "JoystickRight" Rect Transform-waarden hetzelfde in als in de onderstaande schermafbeelding:

  • Selecteer het object "JoystickRightButton" en wijzig in SC_ClickTracker de knopnaam in 'JoystickRight'

De tweede joystick is gereed.

Laten we nu een gewone knop maken:

  • Klik met de rechtermuisknop op Canvasobject -> UI -> Knop
  • Naam van knopobject wijzigen in "SprintButton"
  • Verander de "SprintButton" Sprite in een cirkel met een schuine kanteffect:

  • Stel "SprintButton" Rect Transform-waarden hetzelfde in als in de onderstaande schermafbeelding:

  • Wijzig de "SprintButton" Alfa van de afbeeldingskleur in 0,5
  • Voeg het SC_ClickTracker-script toe aan het "SprintButton"-object
  • Wijzig in SC_ClickTracker de knopnaam in 'Sprinting'
  • Selecteer Tekstobject binnen "SprintButton" en verander de tekst in 'Sprint', wijzig ook de Lettergrootte in 'Bold'

Unity mobiele knop

De knop is klaar.

We gaan nog een knop maken met de naam "Jump":

  • Dupliceer het "SprintButton"-object en hernoem het naar "JumpButton"
  • Wijzig de waarde "JumpButton" Pos Y in 250
  • Wijzig in SC_ClickTracker de knopnaam in 'Jumping'
  • Wijzig de tekst binnen "JumpButton" in 'Jump'

En de laatste knop is "Action":

  • Dupliceer het "JumpButton"-object en hernoem het naar "ActionButton"
  • Wijzig de waarde "ActionButton" Pos X in -185
  • Wijzig in SC_ClickTracker de knopnaam in 'Action'
  • Wijzig de tekst binnen "ActionButton" in 'Action'

Stap 3: Implementeer mobiele bedieningselementen

Als u de bovenstaande stappen heeft gevolgd, kunt u nu deze functies gebruiken om de mobiele bedieningselementen in uw script te implementeren:

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

Ik zal bijvoorbeeld mobiele besturingen implementeren met een FPS-controller uit deze tutorial. Volg eerst die tutorial, het is vrij eenvoudig.

Als je die tutorial zou volgen, zou je nu het "FPSPlayer"-object hebben samen met Canvas met mobiele bedieningselementen.

We behouden de desktopbediening en implementeren tegelijkertijd de mobiele bediening, waardoor deze platformonafhankelijk wordt:

  • Open het SC_FPSController-script, scroll tot regel 28 en verwijder dit onderdeel (als je dat onderdeel verwijdert, wordt voorkomen dat de cursor wordt vergrendeld en kun je op mobiele bedieningselementen in de Editor klikken):
        // Lock cursor
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
  • Scroll naar regel 39 en vervang:
        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;
  • Met:
        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;
  • Scroll naar beneden tot regel 45 en vervang:
        if (Input.GetButton("Jump") && canMove && characterController.isGrounded)
  • Met:
        if ((Input.GetButton("Jump") || SC_MobileControls.instance.GetMobileButtonDown("Jumping")) && canMove && characterController.isGrounded)
  • Scroll naar beneden tot regel 68 en vervang:
            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);
  • Met:
#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

Omdat de look-beweging het testen van de joystick in Editor zal verstoren, gebruiken we #if voor platformspecifieke compilatie om mobiele logica te scheiden van de rest van de platforms.

De Mobile FPS Controller is nu klaar, laten we hem testen:

Sharp Coder Video speler

Zoals u kunt zien, zijn alle joysticks en knoppen functioneel (behalve de "Action"-knop, die niet is geïmplementeerd omdat er geen geschikte functie voor is).

Voorgestelde artikelen
Mobiele aanraakinvoerjoystick in Unity
Spelersbeweging in eenheid creëren
Hoe u kraanbediening in eenheid kunt maken
Ruimteschipcontroller in eenheid
RTS- en MOBA-spelercontroller voor Unity
Helikoptercontroller voor Unity
Autocontroller voor Unity