|
|
- // Panel Menu|Prefabs|0130
- namespace VRTK
- {
- using System.Collections;
- using UnityEngine;
-
- /// <summary>
- /// Adds a top-level controller to handle the display of up to four child PanelMenuItemController items which are displayed as a canvas UI panel.
- /// </summary>
- /// <remarks>
- /// **Prefab Usage:**
- /// * Place the `VRTK/Prefabs/PanelMenu/PanelMenu` prefab as a child of the `VRTK_InteractableObject` the panel menu is for.
- /// * Optionally remove the panel control menu item child GameObjects if they are not required, e.g. `PanelTopControls`.
- /// * Set the panel menu item controllers on the `VRTK_PanelMenuController` script to determine which panel control menu items are available.
- /// * The available panel control menu items can be activated by pressing the corresponding direction on the touchpad.
- /// </remarks>
- /// <example>
- /// `040_Controls_Panel_Menu` contains three basic interactive object examples of the PanelMenu in use.
- /// </example>
- public class VRTK_PanelMenuController : MonoBehaviour
- {
- public enum TouchpadPressPosition
- {
- None,
- Top,
- Bottom,
- Left,
- Right
- }
-
- [Tooltip("The GameObject the panel should rotate towards, which is the Camera (eye) by default.")]
- public GameObject rotateTowards;
- [Tooltip("The scale multiplier, which relates to the scale of parent interactable object.")]
- public float zoomScaleMultiplier = 1f;
- [Tooltip("The top PanelMenuItemController, which is triggered by pressing up on the controller touchpad.")]
- public VRTK_PanelMenuItemController topPanelMenuItemController;
- [Tooltip("The bottom PanelMenuItemController, which is triggered by pressing down on the controller touchpad.")]
- public VRTK_PanelMenuItemController bottomPanelMenuItemController;
- [Tooltip("The left PanelMenuItemController, which is triggered by pressing left on the controller touchpad.")]
- public VRTK_PanelMenuItemController leftPanelMenuItemController;
- [Tooltip("The right PanelMenuItemController, which is triggered by pressing right on the controller touchpad.")]
- public VRTK_PanelMenuItemController rightPanelMenuItemController;
-
- // Relates to scale of canvas on panel items.
- protected const float CanvasScaleSize = 0.001f;
-
- // Swipe sensitivity / detection.
- protected const float AngleTolerance = 30f;
- protected const float SwipeMinDist = 0.2f;
- protected const float SwipeMinVelocity = 4.0f;
-
- protected VRTK_ControllerEvents controllerEvents;
- protected VRTK_PanelMenuItemController currentPanelMenuItemController;
- protected GameObject interactableObject;
- protected GameObject canvasObject;
- protected readonly Vector2 xAxis = new Vector2(1, 0);
- protected readonly Vector2 yAxis = new Vector2(0, 1);
- protected Vector2 touchStartPosition;
- protected Vector2 touchEndPosition;
- protected float touchStartTime;
- protected float currentAngle;
- protected bool isTrackingSwipe = false;
- protected bool isPendingSwipeCheck = false;
- protected bool isGrabbed = false;
- protected bool isShown = false;
- protected Coroutine tweenMenuScaleRoutine;
-
- /// <summary>
- /// The ToggleMenu method is used to show or hide the menu.
- /// </summary>
- public virtual void ToggleMenu()
- {
- if (isShown)
- {
- HideMenu(true);
- }
- else
- {
- ShowMenu();
- }
- }
-
- /// <summary>
- /// The ShowMenu method is used to show the menu.
- /// </summary>
- public virtual void ShowMenu()
- {
- if (!isShown)
- {
- isShown = true;
- InitTweenMenuScale(isShown);
- }
- }
-
- /// <summary>
- /// The HideMenu method is used to hide the menu.
- /// </summary>
- /// <param name="force">If true then the menu is always hidden.</param>
- public virtual void HideMenu(bool force)
- {
- if (isShown && force)
- {
- isShown = false;
- InitTweenMenuScale(isShown);
- }
- }
-
- /// <summary>
- /// The HideMenuImmediate method is used to immediately hide the menu.
- /// </summary>
- public virtual void HideMenuImmediate()
- {
- if (currentPanelMenuItemController != null && isShown)
- {
- HandlePanelMenuItemControllerVisibility(currentPanelMenuItemController);
- }
- transform.localScale = Vector3.zero;
- canvasObject.transform.localScale = Vector3.zero;
- isShown = false;
- }
-
- protected virtual void Awake()
- {
- Initialize();
- VRTK_SDKManager.AttemptAddBehaviourToToggleOnLoadedSetupChange(this);
- }
-
- protected virtual void Start()
- {
- interactableObject = gameObject.transform.parent.gameObject;
- if (interactableObject == null || interactableObject.GetComponent<VRTK_InteractableObject>() == null)
- {
- VRTK_Logger.Warn(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.REQUIRED_COMPONENT_MISSING_FROM_GAMEOBJECT, "PanelMenuController", "VRTK_InteractableObject", "a parent"));
- return;
- }
-
- interactableObject.GetComponent<VRTK_InteractableObject>().InteractableObjectGrabbed += new InteractableObjectEventHandler(DoInteractableObjectIsGrabbed);
- interactableObject.GetComponent<VRTK_InteractableObject>().InteractableObjectUngrabbed += new InteractableObjectEventHandler(DoInteractableObjectIsUngrabbed);
-
- canvasObject = gameObject.transform.GetChild(0).gameObject;
- if (canvasObject == null || canvasObject.GetComponent<Canvas>() == null)
- {
- VRTK_Logger.Warn(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.REQUIRED_COMPONENT_MISSING_FROM_GAMEOBJECT, "PanelMenuController", "Canvas", "a child"));
- }
- }
-
- protected virtual void OnDestroy()
- {
- VRTK_SDKManager.AttemptRemoveBehaviourToToggleOnLoadedSetupChange(this);
- }
-
- protected virtual void Update()
- {
- if (interactableObject != null)
- {
- if (rotateTowards == null)
- {
- rotateTowards = VRTK_DeviceFinder.HeadsetTransform().gameObject;
- if (rotateTowards == null)
- {
- VRTK_Logger.Warn(VRTK_Logger.GetCommonMessage(VRTK_Logger.CommonMessageKeys.COULD_NOT_FIND_OBJECT_FOR_ACTION, "PanelMenuController", "an object", "rotate towards"));
- }
- }
-
- if (isShown)
- {
- if (rotateTowards != null)
- {
- transform.rotation = Quaternion.LookRotation((rotateTowards.transform.position - transform.position) * -1, Vector3.up);
- }
- }
-
- if (isPendingSwipeCheck)
- {
- CalculateSwipeAction();
- }
- }
- }
-
- protected virtual void Initialize()
- {
- if (Application.isPlaying)
- {
- if (!isShown)
- {
- transform.localScale = Vector3.zero;
- }
- }
-
- if (controllerEvents == null)
- {
- transform.localPosition = new Vector3(transform.localPosition.x, transform.localPosition.y, transform.localPosition.z);
- controllerEvents = GetComponentInParent<VRTK_ControllerEvents>();
- }
- }
-
- protected virtual void BindControllerEvents()
- {
- controllerEvents.TouchpadPressed += new ControllerInteractionEventHandler(DoTouchpadPress);
- controllerEvents.TouchpadTouchStart += new ControllerInteractionEventHandler(DoTouchpadTouched);
- controllerEvents.TouchpadTouchEnd += new ControllerInteractionEventHandler(DoTouchpadUntouched);
- controllerEvents.TouchpadAxisChanged += new ControllerInteractionEventHandler(DoTouchpadAxisChanged);
- controllerEvents.TriggerPressed += new ControllerInteractionEventHandler(DoTriggerPressed);
- }
-
- protected virtual void UnbindControllerEvents()
- {
- controllerEvents.TouchpadPressed -= new ControllerInteractionEventHandler(DoTouchpadPress);
- controllerEvents.TouchpadTouchStart -= new ControllerInteractionEventHandler(DoTouchpadTouched);
- controllerEvents.TouchpadTouchEnd -= new ControllerInteractionEventHandler(DoTouchpadUntouched);
- controllerEvents.TouchpadAxisChanged -= new ControllerInteractionEventHandler(DoTouchpadAxisChanged);
- controllerEvents.TriggerPressed -= new ControllerInteractionEventHandler(DoTriggerPressed);
- }
-
- protected virtual void HandlePanelMenuItemControllerVisibility(VRTK_PanelMenuItemController targetPanelItemController)
- {
- if (isShown)
- {
- if (currentPanelMenuItemController == targetPanelItemController)
- {
- targetPanelItemController.Hide(interactableObject);
- currentPanelMenuItemController = null;
- HideMenu(true);
- }
- else
- {
- currentPanelMenuItemController.Hide(interactableObject);
- currentPanelMenuItemController = targetPanelItemController;
- }
- }
- else
- {
- currentPanelMenuItemController = targetPanelItemController;
- }
-
- if (currentPanelMenuItemController != null)
- {
- currentPanelMenuItemController.Show(interactableObject);
- ShowMenu();
- }
- }
-
- protected virtual void InitTweenMenuScale(bool show)
- {
- if (tweenMenuScaleRoutine != null)
- {
- StopCoroutine(tweenMenuScaleRoutine);
- }
- if (enabled)
- {
- tweenMenuScaleRoutine = StartCoroutine(TweenMenuScale(show));
- }
- }
-
- protected virtual IEnumerator TweenMenuScale(bool show)
- {
- float targetScale = 0;
- Vector3 direction = -1 * Vector3.one;
- if (show)
- {
- canvasObject.transform.localScale = new Vector3(CanvasScaleSize, CanvasScaleSize, CanvasScaleSize);
- targetScale = zoomScaleMultiplier;
- direction = Vector3.one;
- }
- int i = 0;
- while (i < 250 && ((show && transform.localScale.x < targetScale) || (!show && transform.localScale.x > targetScale)))
- {
- transform.localScale += direction * Time.deltaTime * 4f * zoomScaleMultiplier;
- yield return true;
- i++;
- }
- transform.localScale = direction * targetScale;
-
- if (!show)
- {
- canvasObject.transform.localScale = Vector3.zero;
- }
- }
-
- protected virtual void DoInteractableObjectIsGrabbed(object sender, InteractableObjectEventArgs e)
- {
- controllerEvents = e.interactingObject.GetComponentInParent<VRTK_ControllerEvents>();
- if (controllerEvents != null)
- {
- BindControllerEvents();
- }
- isGrabbed = true;
- }
-
- protected virtual void DoInteractableObjectIsUngrabbed(object sender, InteractableObjectEventArgs e)
- {
- isGrabbed = false;
- if (isShown)
- {
- HideMenuImmediate();
- }
-
- if (controllerEvents != null)
- {
- UnbindControllerEvents();
- controllerEvents = null;
- }
- }
-
- protected virtual void DoTouchpadPress(object sender, ControllerInteractionEventArgs e)
- {
- if (isGrabbed)
- {
- TouchpadPressPosition pressPosition = CalculateTouchpadPressPosition();
- switch (pressPosition)
- {
- case TouchpadPressPosition.Top:
- if (topPanelMenuItemController != null)
- {
- HandlePanelMenuItemControllerVisibility(topPanelMenuItemController);
- }
- break;
-
- case TouchpadPressPosition.Bottom:
- if (bottomPanelMenuItemController != null)
- {
- HandlePanelMenuItemControllerVisibility(bottomPanelMenuItemController);
- }
- break;
-
- case TouchpadPressPosition.Left:
- if (leftPanelMenuItemController != null)
- {
- HandlePanelMenuItemControllerVisibility(leftPanelMenuItemController);
- }
- break;
-
- case TouchpadPressPosition.Right:
- if (rightPanelMenuItemController != null)
- {
- HandlePanelMenuItemControllerVisibility(rightPanelMenuItemController);
- }
- break;
- }
- }
- }
-
- protected virtual void DoTouchpadTouched(object sender, ControllerInteractionEventArgs e)
- {
- touchStartPosition = new Vector2(e.touchpadAxis.x, e.touchpadAxis.y);
- touchStartTime = Time.time;
- isTrackingSwipe = true;
- }
-
- protected virtual void DoTouchpadUntouched(object sender, ControllerInteractionEventArgs e)
- {
- isTrackingSwipe = false;
- isPendingSwipeCheck = true;
- }
-
- protected virtual void DoTouchpadAxisChanged(object sender, ControllerInteractionEventArgs e)
- {
- ChangeAngle(CalculateAngle(e));
-
- if (isTrackingSwipe)
- {
- touchEndPosition = new Vector2(e.touchpadAxis.x, e.touchpadAxis.y);
- }
- }
-
- protected virtual void DoTriggerPressed(object sender, ControllerInteractionEventArgs e)
- {
- if (isGrabbed)
- {
- OnTriggerPressed();
- }
- }
-
- protected virtual void ChangeAngle(float angle, object sender = null)
- {
- currentAngle = angle;
- }
-
- protected virtual void CalculateSwipeAction()
- {
- isPendingSwipeCheck = false;
-
- float deltaTime = Time.time - touchStartTime;
- Vector2 swipeVector = touchEndPosition - touchStartPosition;
- float velocity = swipeVector.magnitude / deltaTime;
-
- if ((velocity > SwipeMinVelocity) && (swipeVector.magnitude > SwipeMinDist))
- {
- swipeVector.Normalize();
- float angleOfSwipe = Vector2.Dot(swipeVector, xAxis);
- angleOfSwipe = Mathf.Acos(angleOfSwipe) * Mathf.Rad2Deg;
-
- // Left / right
- if (angleOfSwipe < AngleTolerance)
- {
- OnSwipeRight();
- }
- else if ((180.0f - angleOfSwipe) < AngleTolerance)
- {
- OnSwipeLeft();
- }
- else
- {
- // Top / bottom
- angleOfSwipe = Vector2.Dot(swipeVector, yAxis);
- angleOfSwipe = Mathf.Acos(angleOfSwipe) * Mathf.Rad2Deg;
- if (angleOfSwipe < AngleTolerance)
- {
- OnSwipeTop();
- }
- else if ((180.0f - angleOfSwipe) < AngleTolerance)
- {
- OnSwipeBottom();
- }
- }
- }
- }
-
- protected virtual TouchpadPressPosition CalculateTouchpadPressPosition()
- {
- if (CheckAnglePosition(currentAngle, AngleTolerance, 0))
- {
- return TouchpadPressPosition.Top;
- }
- else if (CheckAnglePosition(currentAngle, AngleTolerance, 180))
- {
- return TouchpadPressPosition.Bottom;
- }
- else if (CheckAnglePosition(currentAngle, AngleTolerance, 270))
- {
- return TouchpadPressPosition.Left;
- }
- else if (CheckAnglePosition(currentAngle, AngleTolerance, 90))
- {
- return TouchpadPressPosition.Right;
- }
-
- return TouchpadPressPosition.None;
- }
-
- protected virtual void OnSwipeLeft()
- {
- if (currentPanelMenuItemController != null)
- {
- currentPanelMenuItemController.SwipeLeft(interactableObject);
- }
- }
-
- protected virtual void OnSwipeRight()
- {
- if (currentPanelMenuItemController != null)
- {
- currentPanelMenuItemController.SwipeRight(interactableObject);
- }
- }
-
- protected virtual void OnSwipeTop()
- {
- if (currentPanelMenuItemController != null)
- {
- currentPanelMenuItemController.SwipeTop(interactableObject);
- }
- }
-
- protected virtual void OnSwipeBottom()
- {
- if (currentPanelMenuItemController != null)
- {
- currentPanelMenuItemController.SwipeBottom(interactableObject);
- }
- }
-
- protected virtual void OnTriggerPressed()
- {
- if (currentPanelMenuItemController != null)
- {
- currentPanelMenuItemController.TriggerPressed(interactableObject);
- }
- }
-
- protected virtual float CalculateAngle(ControllerInteractionEventArgs e)
- {
- return e.touchpadAngle;
- }
-
- protected virtual float NormAngle(float currentDegree, float maxAngle = 360)
- {
- if (currentDegree < 0) currentDegree = currentDegree + maxAngle;
- return currentDegree % maxAngle;
- }
-
- protected virtual bool CheckAnglePosition(float currentDegree, float tolerance, float targetDegree)
- {
- float lowerBound = NormAngle(currentDegree - tolerance);
- float upperBound = NormAngle(currentDegree + tolerance);
-
- if (lowerBound > upperBound)
- {
- return targetDegree >= lowerBound || targetDegree <= upperBound;
- }
- return targetDegree >= lowerBound && targetDegree <= upperBound;
- }
- }
- }
|