Unity Cum să faci comenzi tactile mobile
Comenzile sunt una dintre cele mai importante părți ale unui joc video și nu este surprinzător, este ceea ce permite jucătorilor să interacționeze cu lumea jocului.
Controalele jocului sunt semnale care sunt trimise prin interacțiunea hardware (mouse/tastatură, controler, ecran tactil etc.) care sunt apoi procesate de codul jocului, aplicând anumite acțiuni.
PC-urile și Console de jocuri au butoane fizice care pot fi apăsate, totuși, dispozitivele mobile moderne au doar câteva butoane fizice, restul interacțiunii se face prin gesturi tactile, ceea ce înseamnă că butoanele de joc trebuie să fie afișate pe ecran. De aceea, atunci când creați un joc pentru mobil, este important să găsiți un echilibru între a avea toate butoanele de pe ecran, păstrându-l ușor de utilizat și fără dezordine.
În acest tutorial, voi arăta cum să creați controale mobile complete (joystick-uri și butoane) în Unity folosind UI Canvas.
Pasul 1: Creați toate scripturile necesare
Acest tutorial conține 2 scripturi, SC_ClickTracker.cs și SC_MobileControls.cs. Primul script va asculta evenimentele de clic, iar al doilea script va citi valorile generate de evenimente.
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;
}
}
Pasul 2: Configurați comenzile mobile
- Creați o pânză nouă (GameObject -> UI -> Canvas)
- Schimbați 'UI Scale Mode' în Canvas Scaler la 'Scale With Screen Size' și schimbați Rezoluția de referință la cea cu care lucrați (în cazul meu, este 1000 x 600)
- Atașați scriptul SC_MobileControls la obiectul Canvas
- Faceți clic dreapta pe Canvas Object -> UI -> Image
- Redenumiți imaginea nou creată în "JoystickLeft"
- Schimbați "JoystickLeft" Sprite într-un cerc gol (nu uitați să schimbați Tipul texturii la 'Sprite (2D and UI)' după ce îl importați în Unity)
- Setați valorile "JoystickLeft" Rect Transform la fel ca în screenshot de mai jos:
- În componenta Imagine, setați Color alpha la 0,5 pentru a face sprite-ul ușor transparent:
- Duplicați obiectul "JoystickLeft" și redenumiți-l în "JoystickLeftButton"
- Mutați "JoystickLeftButton" în interiorul obiectului "JoystickLeft"
- Schimbați sprite-ul "JoystickLeftButton" într-un cerc plin:
- Setați valorile "JoystickLeftButton" Rect Transform la fel ca în captura de ecran de mai jos:
- Adăugați componenta Button la "JoystickLeftButton"
- În componenta Buton, schimbați Tranziția la 'None'
- Atașați scriptul SC_ClickTracker la "JoystickLeftButton"
- În SC_ClickTracker setați Button Name la orice nume unic (în cazul meu l-am setat la 'JoystickLeft') și activați caseta de selectare 'Is Joystick'.
Butonul Joystick este gata. Puteți avea orice număr de Joystick-uri (În cazul meu voi avea 2, unul în stânga pentru a controla mișcarea și unul în dreapta pentru a controla rotația).
- Duplicați "JoystickLeft" și redenumiți-l în "JoystickRight"
- Extindeți "JoystickRight" și redenumiți "JoystickLeftButton" în "JoystickRightButton"
- Setați valorile "JoystickRight" Rect Transform la fel ca în captura de ecran de mai jos:
- Selectați obiectul "JoystickRightButton" și în SC_ClickTracker schimbați Numele butonului în 'JoystickRight'
Al doilea Joystick este gata.
Acum să creăm un buton obișnuit:
- Faceți clic dreapta pe Canvas Object -> UI -> Button
- Redenumiți obiectul buton în "SprintButton"
- Schimbați sprite-ul "SprintButton" într-un cerc cu efect de teșire:
- Setați valorile "SprintButton" Rect Transform la fel ca în captura de ecran de mai jos:
- Schimbați culoarea alfa a imaginii "SprintButton" la 0,5
- Atașați scriptul SC_ClickTracker la obiectul "SprintButton"
- În SC_ClickTracker schimbați numele butonului în 'Sprinting'
- Selectați obiectul text în interiorul "SprintButton" și modificați textul acestuia în 'Sprint', de asemenea, schimbați dimensiunea fontului în 'Bold'
Butonul este gata.
Vom crea un alt buton numit "Jump":
- Duplicați obiectul "SprintButton" și redenumiți-l în "JumpButton"
- Schimbați valoarea "JumpButton" Pos Y la 250
- În SC_ClickTracker schimbați numele butonului în 'Jumping'
- Schimbați textul din "JumpButton" în 'Jump'
Iar ultimul buton este "Action":
- Duplicați obiectul "JumpButton" și redenumiți-l în "ActionButton"
- Schimbați valoarea "ActionButton" Poz X la -185
- În SC_ClickTracker, schimbați numele butonului în 'Action'
- Schimbați textul din "ActionButton" în 'Action'
Pasul 3: implementați controalele mobile
Dacă ați urmat pașii de mai sus, acum puteți utiliza aceste funcții pentru a implementa controalele mobile din scriptul dvs.:
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");
Ca exemplu, voi implementa controale mobile cu un controler FPS din acest tutorial. Urmează mai întâi acel tutorial, este destul de simplu.
Dacă ai urma acel tutorial, ai avea acum obiectul "FPSPlayer" împreună cu Canvas cu comenzi mobile.
Vom păstra controalele desktop în timp ce implementăm controalele mobile, făcându-l multiplatform:
- Deschideți scriptul SC_FPSController, derulați până la linia 28 și eliminați această parte (eliminarea acelei părți va împiedica blocarea cursorului și va permite să faceți clic pe controalele mobile din Editor.):
// Lock cursor
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
- Derulați până la linia 39 și înlocuiți:
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;
- Cu:
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;
- Derulați în jos până la linia 45 și înlocuiți:
if (Input.GetButton("Jump") && canMove && characterController.isGrounded)
- Cu:
if ((Input.GetButton("Jump") || SC_MobileControls.instance.GetMobileButtonDown("Jumping")) && canMove && characterController.isGrounded)
- Derulați în jos până la linia 68 și înlocuiți:
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);
- Cu:
#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
Deoarece mișcarea aspectului va interfera cu testarea joystick-ului din Editor, folosim #if pentru compilare specifică platformei pentru a separa logica mobilă de restul platformelor.
Controlerul mobil FPS este acum gata, haideți să-l testăm:
După cum puteți vedea, toate Joystick-urile și butoanele sunt funcționale (cu excepția butonului "Action", care nu a fost implementat din cauza lipsei unei funcții adecvate pentru el).