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.
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'
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:
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).