Unity Sådan laver du mobile berøringskontroller

Kontrolelementer er en af ​​de vigtigste dele af et videospil, og ingen overraskelse, det er det, der tillader spillere at interagere med spilverdenen.

Spilkontroller er signaler, der sendes gennem hardwareinteraktion (mus/tastatur, controller, berøringsskærm osv.), som derefter behandles af spilkoden og anvender bestemte handlinger.

PC'er og Gaming Consoles har fysiske knapper, der kan trykkes på, dog har moderne mobile enheder kun få fysiske knapper, resten af ​​interaktionen foregår gennem touch-bevægelser, hvilket betyder, at spilknapper skal vises på skærmen. Derfor er det vigtigt, når man laver et mobilspil, at finde en balance mellem at have alle knapperne på skærmen og samtidig holde det brugervenligt og rodfrit.

Unity Mobile Controls

I denne tutorial vil jeg vise, hvordan man opretter fuldt udstyrede (joysticks og knapper) mobile kontroller i Unity ved hjælp af UI Canvas.

Trin 1: Opret alle de nødvendige scripts

Denne vejledning indeholder 2 scripts, SC_ClickTracker.cs og SC_MobileControls.cs. Det første script vil lytte til klikhændelserne, og det andet script vil læse værdierne genereret fra hændelserne.

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

Trin 2: Konfigurer Mobile Controls

  • Opret et nyt lærred (GameObject -> UI -> Canvas)
  • Skift 'UI Scale Mode' i Canvas Scaler til 'Scale With Screen Size' og skift referenceopløsning til den du arbejder med (i mit tilfælde er det 1000 x 600)
  • Vedhæft SC_MobileControls-scriptet til Canvas Object
  • Højreklik på Canvas Object -> UI -> Billede
  • Omdøb det nyoprettede billede til "JoystickLeft"
  • Skift "JoystickLeft" Sprite til en tom cirkel (glem ikke at ændre teksturtype til 'Sprite (2D and UI)' efter import til Unity)

  • Indstil "JoystickLeft" Rect Transform-værdier på samme måde som i skærmbilledet nedenfor:

  • Indstil Color alpha til 0,5 i billedkomponenten for at gøre spriten let gennemsigtig:

  • Dupliker "JoystickLeft"-objektet og omdøb det til "JoystickLeftButton"
  • Flyt "JoystickLeftButton" ind i "JoystickLeft"-objektet
  • Skift "JoystickLeftButton" Sprite til en udfyldt cirkel:

  • Indstil "JoystickLeftButton" Rect Transform værdier som på skærmbilledet nedenfor:

  • Tilføj knapkomponent til "JoystickLeftButton"
  • I Knap-komponenten ændres Overgang til 'None'
  • Vedhæft SC_ClickTracker-scriptet til "JoystickLeftButton"
  • I SC_ClickTracker indstiller jeg Button Name til ethvert unikt navn (i mit tilfælde indstiller jeg det til 'JoystickLeft') og aktiverer 'Is Joystick' afkrydsningsfeltet.

Joystick-knappen er klar. Du kan have et hvilket som helst antal joysticks (i mit tilfælde vil jeg have 2, et til venstre til at styre bevægelsen og et til højre til at styre rotationen).

  • Dupliker "JoystickLeft" og omdøb den til "JoystickRight"
  • Udvid "JoystickRight" og omdøb "JoystickLeftButton" til "JoystickRightButton"
  • Indstil "JoystickRight" Rect Transform værdier som på skærmbilledet nedenfor:

  • Vælg "JoystickRightButton"-objektet og i SC_ClickTracker skift knapnavn til 'JoystickRight'

Det andet joystick er klar.

Lad os nu oprette en almindelig knap:

  • Højreklik på Canvas Object -> UI -> Button
  • Omdøb knapobjekt til "SprintButton"
  • Skift "SprintButton" Sprite til en cirkel med en skråeffekt:

  • Indstil "SprintButton" Rect Transform værdier som på skærmbilledet nedenfor:

  • Skift "SprintButton" billedfarve alfa til 0,5
  • Vedhæft SC_ClickTracker-scriptet til "SprintButton"-objektet
  • I SC_ClickTracker ændres knappens navn til 'Sprinting'
  • Vælg tekstobjekt inde i "SprintButton" og skift dets tekst til 'Sprint', skift også skriftstørrelse til 'Bold'

Unity mobil knap

Knappen er klar.

Vi vil oprette en anden knap kaldet "Jump":

  • Dupliker "SprintButton"-objektet og omdøb det til "JumpButton"
  • Skift "JumpButton" Pos Y værdi til 250
  • I SC_ClickTracker ændres knappens navn til 'Jumping'
  • Skift tekst inde i "JumpButton" til 'Jump'

Og den sidste knap er "Action":

  • Dupliker "JumpButton"-objektet og omdøb det til "ActionButton"
  • Skift "ActionButton" Pos X værdi til -185
  • I SC_ClickTracker ændres knapnavn til 'Action'
  • Skift tekst inde i "ActionButton" til 'Action'

Trin 3: Implementer Mobile Controls

Hvis du fulgte ovenstående trin, kan du nu bruge disse funktioner til at implementere de mobile kontroller i dit 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");

Som et eksempel vil jeg implementere mobile kontroller med en FPS-controller fra denne tutorial. Følg den tutorial først, det er ret simpelt.

Hvis du fulgte den tutorial, ville du nu have "FPSPlayer"-objektet sammen med Canvas med mobile kontroller.

Vi vil bevare Desktop-kontrollerne, mens vi også implementerer de mobile kontroller, hvilket gør det på tværs af platforme:

  • Åbn SC_FPSController-scriptet, rul indtil linje 28, og fjern denne del (hvis du fjerner den del, forhindrer du, at markøren låses, og du kan klikke på mobilkontroller i Editor).
        // Lock cursor
        Cursor.lockState = CursorLockMode.Locked;
        Cursor.visible = false;
  • Rul til linje 39 og erstat:
        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;
  • Med:
        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;
  • Rul ned til linje 45 og udskift:
        if (Input.GetButton("Jump") && canMove && characterController.isGrounded)
  • Med:
        if ((Input.GetButton("Jump") || SC_MobileControls.instance.GetMobileButtonDown("Jumping")) && canMove && characterController.isGrounded)
  • Rul ned til linje 68 og udskift:
            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);
  • Med:
#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

Da udseendebevægelsen vil forstyrre joystick-testen i Editor, bruger vi #if til platformspecifik kompilering for at adskille mobillogik fra resten af ​​platformene.

Den mobile FPS-controller er nu klar, lad os teste den:

Sharp Coder Videoafspiller

Som du kan se, er alle joysticks og knapper funktionelle (undtagen "Action"-knappen, som ikke blev implementeret på grund af ikke at have en passende funktion til den).