Unity Hur man gör mobila pekkontroller
Kontroller är en av de viktigaste delarna av ett videospel, och ingen överraskning, det är det som gör att spelare kan interagera med spelvärlden.
Spelkontroller är signaler som skickas genom hårdvaruinteraktion (mus/tangentbord, kontroller, pekskärm, etc.) som sedan bearbetas av spelkoden, med tillämpning av vissa åtgärder.
PC och Gaming Consoles har fysiska knappar som går att trycka på, dock har moderna mobila enheter bara ett fåtal fysiska knappar, resten av interaktionen sker genom touchgester, vilket innebär att spelknappar måste visas på skärmen. Det är därför när man skapar ett mobilspel är det viktigt att hitta en balans mellan att ha alla knappar på skärmen samtidigt som det är användarvänligt och stökigt.
I den här handledningen kommer jag att visa hur man skapar fullfjädrade (joysticks och knappar) mobilkontroller i Unity med UI Canvas.
Steg 1: Skapa alla nödvändiga skript
Denna handledning innehåller 2 skript, SC_ClickTracker.cs och SC_MobileControls.cs. Det första skriptet kommer att lyssna på klickhändelserna och det andra skriptet läser de värden som genereras från händelserna.
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;
}
}
Steg 2: Konfigurera mobilkontroller
- Skapa en ny Canvas (GameObject -> UI -> Canvas)
- Ändra 'UI Scale Mode' i Canvas Scaler till 'Scale With Screen Size' och ändra referensupplösningen till den du arbetar med (i mitt fall är det 1000 x 600)
- Bifoga SC_MobileControls-skriptet till Canvas Object
- Högerklicka på Canvas Object -> UI -> Image
- Byt namn på den nyskapade bilden till "JoystickLeft"
- Ändra "JoystickLeft" Sprite till en tom cirkel (glöm inte att ändra texturtyp till 'Sprite (2D and UI)' efter att ha importerat den till Unity)
- Ställ in "JoystickLeft" Rect Transform-värden på samma sätt som i skärmdumpen nedan:
- I bildkomponenten ställer du in Color alpha till 0,5 för att göra spriten något transparent:
- Duplicera "JoystickLeft"-objektet och byt namn på det till "JoystickLeftButton"
- Flytta "JoystickLeftButton" inuti "JoystickLeft"-objektet
- Ändra "JoystickLeftButton" Sprite till en fylld cirkel:
- Ställ in "JoystickLeftButton" Rect Transform-värden på samma sätt som i skärmdumpen nedan:
- Lägg till knappkomponent till "JoystickLeftButton"
- Ändra Övergång till i Button-komponenten 'None'
- Bifoga SC_ClickTracker-skriptet till "JoystickLeftButton"
- I SC_ClickTracker ställer jag in Button Name till vilket unikt namn som helst (i mitt fall ställer jag in det till 'JoystickLeft') och aktivera kryssrutan 'Is Joystick'.
Joystick-knappen är klar. Du kan ha valfritt antal joysticks (i mitt fall kommer jag att ha 2, en till vänster för att styra rörelsen och en till höger för att styra rotationen).
- Duplicera "JoystickLeft" och byt namn på den till "JoystickRight"
- Expandera "JoystickRight" och byt namn på "JoystickLeftButton" till "JoystickRightButton"
- Ställ in "JoystickRight" Rect Transform-värden på samma sätt som i skärmdumpen nedan:
- Välj "JoystickRightButton"-objektet och i SC_ClickTracker ändra knappnamn till 'JoystickRight'
Den andra joysticken är klar.
Låt oss nu skapa en vanlig knapp:
- Högerklicka på Canvas Object -> UI -> Button
- Byt namn på knappobjekt till "SprintButton"
- Ändra "SprintButton" Sprite till en cirkel med en avfasningseffekt:
- Ställ in "SprintButton" Rect Transform-värden på samma sätt som i skärmdumpen nedan:
- Ändra "SprintButton" bildfärg alfa till 0,5
- Bifoga SC_ClickTracker-skriptet till "SprintButton"-objektet
- Ändra knappens namn till SC_ClickTracker 'Sprinting'
- Välj textobjekt inuti "SprintButton" och ändra dess text till 'Sprint', ändra även teckenstorlek till 'Bold'
Knappen är klar.
Vi kommer att skapa en annan knapp som heter "Jump":
- Duplicera "SprintButton"-objektet och byt namn på det till "JumpButton"
- Ändra "JumpButton" Pos Y värde till 250
- Ändra knappens namn till SC_ClickTracker 'Jumping'
- Ändra text inuti "JumpButton" till 'Jump'
Och den sista knappen är "Action":
- Duplicera "JumpButton"-objektet och byt namn på det till "ActionButton"
- Ändra "ActionButton" Pos X värde till -185
- I SC_ClickTracker ändra knappnamn till 'Action'
- Ändra text inuti "ActionButton" till 'Action'
Steg 3: Implementera mobila kontroller
Om du följde stegen ovan kan du nu använda dessa funktioner för att implementera mobilkontrollerna i ditt skript:
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 ett exempel kommer jag att implementera mobila kontroller med en FPS Controller från den här handledningen. Följ den handledningen först, det är ganska enkelt.
Om du följde den handledningen skulle du nu ha "FPSPlayer"-objektet tillsammans med Canvas med mobilkontroller.
Vi kommer att bevara skrivbordskontrollerna samtidigt som vi implementerar de mobila kontrollerna, vilket gör det plattformsoberoende:
- Öppna SC_FPSController-skriptet, scrolla till rad 28 och ta bort den här delen (om du tar bort den delen förhindrar du att markören låses och gör det möjligt att klicka på mobilkontroller i Editor.):
// Lock cursor
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
- Scrolla till rad 39 och ersätt:
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;
- Scrolla ner till rad 45 och byt ut:
if (Input.GetButton("Jump") && canMove && characterController.isGrounded)
- Med:
if ((Input.GetButton("Jump") || SC_MobileControls.instance.GetMobileButtonDown("Jumping")) && canMove && characterController.isGrounded)
- Scrolla ner till rad 68 och byt ut:
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
Eftersom utseenderörelsen kommer att störa joystick-testningen i Editor, använder vi #if för plattformsspecifik kompilering för att separera mobil logik från resten av plattformarna.
Den mobila FPS-kontrollen är nu klar, låt oss testa den:
Som du kan se är alla joysticks och knappar funktionella (förutom "Action"-knappen, som inte implementerades på grund av att den inte hade en lämplig funktion för den).