// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using TMPro; using UnityEngine; using UnityEngine.Serialization; namespace Microsoft.MixedReality.Toolkit.Experimental.Joystick { /// /// Example script to demonstrate joystick control in sample scene /// public class JoystickController : MonoBehaviour { [Experimental] [SerializeField, FormerlySerializedAs("objectToManipulate")] [Tooltip("The large or small game object that receives manipulation by the joystick.")] private GameObject targetObject = null; public GameObject TargetObject { get => targetObject; set => targetObject = value; } [SerializeField] [Tooltip("A TextMeshPro object that displays joystick values.")] private TextMeshPro debugText = null; [SerializeField] [Tooltip("The joystick mesh that gets rotated when this control is interacted with.")] private GameObject joystickVisual = null; [SerializeField] [Tooltip("The mesh + collider object that gets dragged and controls the joystick visual rotation.")] private GameObject grabberVisual = null; [SerializeField] [Tooltip("Toggles on / off the GrabberVisual's mesh renderer because it can be dragged away from the joystick visual, it kind of breaks the illusion of pushing / pulling a lever.")] private bool showGrabberVisual = true; [Tooltip("The speed at which the JoystickVisual and GrabberVisual move / rotate back to a neutral position.")] [Range(1, 20)] public float ReboundSpeed = 5; [Tooltip("How sensitive the joystick reacts to dragging left / right. Customize this value to get the right feel for your scenario.")] [Range(0.01f, 10)] public float SensitivityLeftRight = 3; [Tooltip("How sensitive the joystick reacts to pushing / pulling. Customize this value to get the right feel for your scenario.")] [Range(0.01f, 10)] public float SensitivityForwardBack = 6; [SerializeField] [Tooltip("The property that the joystick manipulates.")] private JoystickMode mode = JoystickMode.Move; public JoystickMode Mode { get => mode; set => mode = value; } [Tooltip("The distance multiplier for joystick input. Customize this value to get the right feel for your scenario.")] [Range(0.0003f, 0.03f)] public float MoveSpeed = 0.01f; [Tooltip("The rotation multiplier for joystick input. Customize this value to get the right feel for your scenario.")] [Range(0.01f, 1f)] public float RotationSpeed = 0.05f; [Tooltip("The scale multiplier for joystick input. Customize this value to get the right feel for your scenario.")] [Range(0.00003f, 0.003f)] public float ScaleSpeed = 0.001f; private Vector3 startPosition; private Vector3 joystickGrabberPosition; private Vector3 joystickVisualRotation; private const int joystickVisualMaxRotation = 80; private bool isDragging = false; private void Start() { startPosition = grabberVisual.transform.localPosition; if (grabberVisual != null) { grabberVisual.GetComponent().enabled = showGrabberVisual; } } private void Update() { if (!isDragging) { // when dragging stops, move joystick back to idle if (grabberVisual != null) { grabberVisual.transform.localPosition = Vector3.Lerp(grabberVisual.transform.localPosition, startPosition, Time.deltaTime * ReboundSpeed); } } CalculateJoystickRotation(); ApplyJoystickValues(); } private void CalculateJoystickRotation() { joystickGrabberPosition = grabberVisual.transform.localPosition - startPosition; // Left Right = Horizontal joystickVisualRotation.z = Mathf.Clamp(-joystickGrabberPosition.x * SensitivityLeftRight, -joystickVisualMaxRotation, joystickVisualMaxRotation); // Forward Back = Vertical joystickVisualRotation.x = Mathf.Clamp(joystickGrabberPosition.z * SensitivityForwardBack, -joystickVisualMaxRotation, joystickVisualMaxRotation); // TODO: calculate joystickVisualRotation.y to always face the proper direction (for when the joystick container gets moved around the scene) if (joystickVisual != null) { joystickVisual.transform.localRotation = Quaternion.Euler(joystickVisualRotation); } } private void ApplyJoystickValues() { if (TargetObject != null) { if (Mode == JoystickMode.Move) { TargetObject.transform.position += (joystickGrabberPosition * MoveSpeed); if (debugText != null) { debugText.text = TargetObject.transform.position.ToString(); } } else if (Mode == JoystickMode.Rotate) { Vector3 newRotation = TargetObject.transform.rotation.eulerAngles; // only take the horizontal axis from the joystick newRotation.y += (joystickGrabberPosition.x * RotationSpeed); newRotation.x = 0; newRotation.z = 0; TargetObject.transform.localRotation = Quaternion.Euler(newRotation); if (debugText != null) { debugText.text = TargetObject.transform.localRotation.eulerAngles.ToString(); } } else if (Mode == JoystickMode.Scale) { // TODO: Clamp above zero Vector3 newScale = new Vector3(joystickGrabberPosition.x, joystickGrabberPosition.x, joystickGrabberPosition.x) * ScaleSpeed; TargetObject.transform.localScale += newScale; if (debugText != null) { debugText.text = TargetObject.transform.localScale.ToString(); } } } } /// /// The ObjectManipulator script uses this to determine when the joystick is grabbed. /// public void StartDrag() { isDragging = true; } /// /// The ObjectManipulator script uses this to determine when the joystick is released. /// public void StopDrag() { isDragging = false; } /// /// Set the joystick mode from a UI button. /// public void JoystickModeMove() { Mode = JoystickMode.Move; } /// /// Set the joystick mode from a UI button. /// public void JoystickModeRotate() { Mode = JoystickMode.Rotate; } /// /// Set the joystick mode from a UI button. /// public void JoystickModeScale() { Mode = JoystickMode.Scale; } } }