Unity Hvordan lage mobile berøringskontroller
Kontroller er en av de viktigste delene av et videospill, og ingen overraskelse, det er det som lar spillere samhandle med spillverdenen.
Spillkontroller er signaler som sendes gjennom maskinvareinteraksjon (mus/tastatur, kontroller, berøringsskjerm, etc.) som deretter behandles av spillkoden, ved å bruke visse handlinger.
PC-er og Spillkonsoller har fysiske knapper som kan trykkes på, men moderne mobile enheter har bare noen få fysiske knapper, resten av interaksjonen gjøres gjennom berøringsbevegelser, som betyr at spillknapper må vises på skjermen. Det er derfor når du lager et mobilspill, er det viktig å finne en balanse mellom å ha alle knappene på skjermen samtidig som det holder det brukervennlig og rotfritt.
I denne opplæringen vil jeg vise hvordan du lager fullfunksjons (joysticks og knapper) mobilkontroller i Unity ved hjelp av UI Canvas.
Trinn 1: Lag alle nødvendige skript
Denne opplæringen inneholder 2 skript, SC_ClickTracker.cs og SC_MobileControls.cs. Det første skriptet vil lytte til klikkhendelsene, og det andre skriptet vil lese verdiene generert fra hendelsene.
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;
}
}
Trinn 2: Konfigurer mobilkontroller
- Opprett et nytt lerret (GameObject -> UI -> Canvas)
- Endre 'UI Scale Mode' i Canvas Scaler til 'Scale With Screen Size' og endre Reference Resolution til den du jobber med (i mitt tilfelle er det 1000 x 600)
- Fest SC_MobileControls-skriptet til Canvas Object
- Høyreklikk på Canvas Object -> UI -> Image
- Gi nytt navn til det nyopprettede bildet til "JoystickLeft"
- Endre "JoystickLeft" Sprite til en tom sirkel (ikke glem å endre teksturtype til 'Sprite (2D and UI)' etter å ha importert den til Unity)
- Sett "JoystickLeft" Rect Transform-verdier på samme måte som i skjermbildet nedenfor:
- I Bilde-komponenten setter du Color alpha til 0,5 for å gjøre spriten litt gjennomsiktig:
- Dupliser "JoystickLeft"-objektet og gi det nytt navn til "JoystickLeftButton"
- Flytt "JoystickLeftButton" inne i "JoystickLeft"-objektet
- Endre "JoystickLeftButton" Sprite til en fylt sirkel:
- Sett "JoystickLeftButton" Rect Transform-verdier på samme måte som i skjermbildet nedenfor:
- Legg til knappekomponent til "JoystickLeftButton"
- I Button-komponenten endres Overgang til 'None'
- Fest SC_ClickTracker-skriptet til "JoystickLeftButton"
- I SC_ClickTracker setter jeg Button Name til et hvilket som helst unikt navn (i mitt tilfelle setter jeg det til 'JoystickLeft') og aktiver 'Is Joystick' avkrysningsboksen.
Joystick-knappen er klar. Du kan ha et hvilket som helst antall styrespaker (i mitt tilfelle vil jeg ha 2, en til venstre for å kontrollere bevegelsen og en til høyre for å kontrollere rotasjonen).
- Dupliser "JoystickLeft" og gi det nytt navn til "JoystickRight"
- Utvid "JoystickRight" og gi nytt navn til "JoystickLeftButton" til "JoystickRightButton"
- Sett "JoystickRight" Rect Transform-verdier på samme måte som i skjermbildet nedenfor:
- Velg "JoystickRightButton"-objektet og endre knappennavn til i SC_ClickTracker 'JoystickRight'
Den andre joysticken er klar.
La oss nå lage en vanlig knapp:
- Høyreklikk på Canvas Object -> UI -> Button
- Gi nytt navn til knappobjekt til "SprintButton"
- Endre "SprintButton" Sprite til en sirkel med skråeffekt:
- Sett "SprintButton" Rect Transform-verdier på samme måte som i skjermbildet nedenfor:
- Endre "SprintButton" bildefarge alfa til 0,5
- Fest SC_ClickTracker-skriptet til "SprintButton"-objektet
- I SC_ClickTracker endrer knappene navn til 'Sprinting'
- Velg tekstobjekt inne i "SprintButton" og endre teksten til 'Sprint', endre også skriftstørrelse til 'Bold'
Knappen er klar.
Vi skal lage en annen knapp kalt "Jump":
- Dupliser "SprintButton"-objektet og gi det nytt navn til "JumpButton"
- Endre "JumpButton" Pos Y-verdien til 250
- I SC_ClickTracker endrer knappene navn til 'Jumping'
- Endre tekst i "JumpButton" til 'Jump'
Og den siste knappen er "Action":
- Dupliser "JumpButton"-objektet og gi det nytt navn til "ActionButton"
- Endre "ActionButton" Pos X verdi til -185
- I SC_ClickTracker, endre Button Name til 'Action'
- Endre tekst i "ActionButton" til 'Action'
Trinn 3: Implementer mobile kontroller
Hvis du fulgte trinnene ovenfor, kan du nå bruke disse funksjonene til å implementere mobilkontrollene i skriptet ditt:
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-kontroller fra denne opplæringen. Følg den opplæringen først, det er ganske enkelt.
Hvis du fulgte den opplæringen, ville du nå ha "FPSPlayer"-objektet sammen med Canvas med mobilkontroller.
Vi vil bevare skrivebordskontrollene samtidig som vi implementerer mobilkontrollene, noe som gjør det på tvers av plattformer:
- Åpne SC_FPSController-skript, rull til linje 28, og fjern denne delen (hvis du fjerner den delen, forhindrer du at markøren låses, og du kan klikke på mobilkontroller i Editor.):
// Lock cursor
Cursor.lockState = CursorLockMode.Locked;
Cursor.visible = false;
- Bla til linje 39 og erstatt:
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;
- Rull ned til linje 45 og bytt ut:
if (Input.GetButton("Jump") && canMove && characterController.isGrounded)
- Med:
if ((Input.GetButton("Jump") || SC_MobileControls.instance.GetMobileButtonDown("Jumping")) && canMove && characterController.isGrounded)
- Rull ned til linje 68 og erstatt:
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
Siden utseendebevegelsen vil forstyrre joystick-testingen i Editor, bruker vi #if for plattformspesifikk kompilering for å skille mobillogikk fra resten av plattformene.
Den mobile FPS-kontrolleren er nå klar, la oss teste den:
Som du kan se, er alle styrespaker og knapper funksjonelle (bortsett fra "Action"-knappen, som ikke ble implementert på grunn av at den ikke hadde en passende funksjon for den).