307 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			307 lines
		
	
	
		
			9.3 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| using System.Collections.Generic;
 | |
| using UnityEngine.Events;
 | |
| using UnityEngine.XR.Interaction.Toolkit;
 | |
| 
 | |
| namespace UnityEngine.XR.Content.Interaction
 | |
| {
 | |
|     /// <summary>
 | |
|     /// An interactable that can be pushed by a direct interactor's movement
 | |
|     /// </summary>
 | |
|     public class XRPushButton : XRBaseInteractable
 | |
|     {
 | |
|         class PressInfo
 | |
|         {
 | |
|             internal IXRHoverInteractor m_Interactor;
 | |
|             internal bool m_InPressRegion = false;
 | |
|             internal bool m_WrongSide = false;
 | |
|         }
 | |
| 
 | |
|         [Serializable]
 | |
|         public class ValueChangeEvent : UnityEvent<float> { }
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("The object that is visually pressed down")]
 | |
|         Transform m_Button = null;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("The distance the button can be pressed")]
 | |
|         float m_PressDistance = 0.1f;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Extra distance for clicking the button down")]
 | |
|         float m_PressBuffer = 0.01f;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Offset from the button base to start testing for push")]
 | |
|         float m_ButtonOffset = 0.0f;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("How big of a surface area is available for pressing the button")]
 | |
|         float m_ButtonSize = 0.1f;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Treat this button like an on/off toggle")]
 | |
|         bool m_ToggleButton = false;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Events to trigger when the button is pressed")]
 | |
|         UnityEvent m_OnPress;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Events to trigger when the button is released")]
 | |
|         UnityEvent m_OnRelease;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("Events to trigger when the button pressed value is updated. Only called when the button is pressed")]
 | |
|         ValueChangeEvent m_OnValueChange;
 | |
|         
 | |
|         [SerializeField]
 | |
|         [Tooltip("Audio source to play when the button is pressed")]
 | |
|         AudioSource m_AudioSource;
 | |
| 
 | |
|         bool m_Pressed = false;
 | |
|         bool m_Toggled = false;
 | |
|         float m_Value = 0f;
 | |
|         Vector3 m_BaseButtonPosition = Vector3.zero;
 | |
| 
 | |
|         Dictionary<IXRHoverInteractor, PressInfo> m_HoveringInteractors = new Dictionary<IXRHoverInteractor, PressInfo>();
 | |
| 
 | |
|         /// <summary>
 | |
|         /// The object that is visually pressed down
 | |
|         /// </summary>
 | |
|         public Transform button
 | |
|         {
 | |
|             get => m_Button;
 | |
|             set => m_Button = value;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// The distance the button can be pressed
 | |
|         /// </summary>
 | |
|         public float pressDistance
 | |
|         {
 | |
|             get => m_PressDistance;
 | |
|             set => m_PressDistance = value;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// The distance (in percentage from 0 to 1) the button is currently being held down
 | |
|         /// </summary>
 | |
|         public float value => m_Value;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Events to trigger when the button is pressed
 | |
|         /// </summary>
 | |
|         public UnityEvent onPress => m_OnPress;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Events to trigger when the button is released
 | |
|         /// </summary>
 | |
|         public UnityEvent onRelease => m_OnRelease;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Events to trigger when the button distance value is changed. Only called when the button is pressed
 | |
|         /// </summary>
 | |
|         public ValueChangeEvent onValueChange => m_OnValueChange;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// Whether or not a toggle button is in the locked down position
 | |
|         /// </summary>
 | |
|         public bool toggleValue
 | |
|         {
 | |
|             get => m_ToggleButton && m_Toggled;
 | |
|             set
 | |
|             {
 | |
|                 if (!m_ToggleButton)
 | |
|                     return;
 | |
| 
 | |
|                 m_Toggled = value;
 | |
|                 if (m_Toggled)
 | |
|                     SetButtonHeight(-m_PressDistance);
 | |
|                 else
 | |
|                     SetButtonHeight(0.0f);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public override bool IsHoverableBy(IXRHoverInteractor interactor)
 | |
|         {
 | |
|             if (interactor is XRRayInteractor)
 | |
|                 return false;
 | |
| 
 | |
|             return base.IsHoverableBy(interactor);
 | |
|         }
 | |
| 
 | |
|         void Start()
 | |
|         {
 | |
|             if (m_Button != null)
 | |
|                 m_BaseButtonPosition = m_Button.position;
 | |
|         }
 | |
| 
 | |
|         protected override void OnEnable()
 | |
|         {
 | |
|             base.OnEnable();
 | |
| 
 | |
|             if (m_Toggled)
 | |
|                 SetButtonHeight(-m_PressDistance);
 | |
|             else
 | |
|                 SetButtonHeight(0.0f);
 | |
| 
 | |
|             hoverEntered.AddListener(StartHover);
 | |
|             hoverExited.AddListener(EndHover);
 | |
|         }
 | |
| 
 | |
|         protected override void OnDisable()
 | |
|         {
 | |
|             hoverEntered.RemoveListener(StartHover);
 | |
|             hoverExited.RemoveListener(EndHover);
 | |
|             base.OnDisable();
 | |
|         }
 | |
| 
 | |
|         void StartHover(HoverEnterEventArgs args)
 | |
|         {
 | |
|             m_HoveringInteractors.Add(args.interactorObject, new PressInfo { m_Interactor = args.interactorObject });
 | |
|         }
 | |
| 
 | |
|         void EndHover(HoverExitEventArgs args)
 | |
|         {
 | |
|             m_HoveringInteractors.Remove(args.interactorObject);
 | |
| 
 | |
|             if (m_HoveringInteractors.Count == 0)
 | |
|             {
 | |
|                 if (m_ToggleButton && m_Toggled)
 | |
|                     SetButtonHeight(-m_PressDistance);
 | |
|                 else
 | |
|                     SetButtonHeight(0.0f);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase)
 | |
|         {
 | |
|             base.ProcessInteractable(updatePhase);
 | |
| 
 | |
|             if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic)
 | |
|             {
 | |
|                 if (m_HoveringInteractors.Count > 0)
 | |
|                 {
 | |
|                     UpdatePress();
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void UpdatePress()
 | |
|         {
 | |
|             var minimumHeight = 0.0f;
 | |
| 
 | |
|             if (m_ToggleButton && m_Toggled)
 | |
|                 minimumHeight = -m_PressDistance;
 | |
| 
 | |
|             // Go through each interactor
 | |
|             foreach (var pressInfo in m_HoveringInteractors.Values)
 | |
|             {
 | |
|                 var interactorTransform = pressInfo.m_Interactor.GetAttachTransform(this);
 | |
|                 var localOffset = transform.InverseTransformVector(interactorTransform.position - m_BaseButtonPosition);
 | |
| 
 | |
|                 var withinButtonRegion = (Mathf.Abs(localOffset.x) < m_ButtonSize && Mathf.Abs(localOffset.z) < m_ButtonSize);
 | |
|                 if (withinButtonRegion)
 | |
|                 {
 | |
|                     if (!pressInfo.m_InPressRegion)
 | |
|                     {
 | |
|                         pressInfo.m_WrongSide = (localOffset.y < m_ButtonOffset);
 | |
|                     }
 | |
| 
 | |
|                     if (!pressInfo.m_WrongSide)
 | |
|                         minimumHeight = Mathf.Min(minimumHeight, localOffset.y - m_ButtonOffset);
 | |
|                 }
 | |
| 
 | |
|                 pressInfo.m_InPressRegion = withinButtonRegion;
 | |
|                 Debug.Log("Button was pressed by: " + pressInfo.m_Interactor);
 | |
|             }
 | |
| 
 | |
|             minimumHeight = Mathf.Max(minimumHeight, -(m_PressDistance + m_PressBuffer));
 | |
| 
 | |
|             // If button height goes below certain amount, enter press mode
 | |
|             var pressed = m_ToggleButton ? (minimumHeight <= -(m_PressDistance + m_PressBuffer)) : (minimumHeight < -m_PressDistance);
 | |
| 
 | |
|             var currentDistance = Mathf.Max(0f, -minimumHeight - m_PressBuffer);
 | |
|             m_Value = currentDistance / m_PressDistance;
 | |
| 
 | |
|             if (m_ToggleButton)
 | |
|             {
 | |
|                 if (pressed)
 | |
|                 {
 | |
|                     if (!m_Pressed)
 | |
|                     {
 | |
|                         m_Toggled = !m_Toggled;
 | |
| 
 | |
|                         if (m_Toggled)
 | |
|                         {
 | |
|                             m_OnPress.Invoke();
 | |
|                         }
 | |
|                             
 | |
|                             
 | |
|                         else
 | |
|                             m_OnRelease.Invoke();
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|             else
 | |
|             {
 | |
|                 if (pressed)
 | |
|                 {
 | |
|                     if (!m_Pressed)
 | |
|                         m_OnPress.Invoke();
 | |
|                 }
 | |
|                 else
 | |
|                 {
 | |
|                     if (m_Pressed)
 | |
|                         m_OnRelease.Invoke();
 | |
|                         // Play sound if the audio source is set
 | |
|                         if (m_AudioSource != null)
 | |
|                         {
 | |
|                             m_AudioSource.Play();
 | |
|                         }
 | |
|                 }
 | |
|             }
 | |
|             m_Pressed = pressed;
 | |
| 
 | |
|             // Call value change event
 | |
|             if (m_Pressed)
 | |
|                 m_OnValueChange.Invoke(m_Value);
 | |
| 
 | |
|             SetButtonHeight(minimumHeight);
 | |
|         }
 | |
| 
 | |
|         void SetButtonHeight(float height)
 | |
|         {
 | |
|             if (m_Button == null)
 | |
|                 return;
 | |
| 
 | |
|             Vector3 newPosition = m_Button.localPosition;
 | |
|             newPosition.y = height;
 | |
|             m_Button.localPosition = newPosition;
 | |
|         }
 | |
| 
 | |
|         void OnDrawGizmosSelected()
 | |
|         {
 | |
|             var pressStartPoint = Vector3.zero;
 | |
| 
 | |
|             if (m_Button != null)
 | |
|             {
 | |
|                 pressStartPoint = m_Button.localPosition;
 | |
|             }
 | |
| 
 | |
|             pressStartPoint.y += m_ButtonOffset - (m_PressDistance * 0.5f);
 | |
| 
 | |
|             Gizmos.color = Color.green;
 | |
|             Gizmos.matrix = transform.localToWorldMatrix;
 | |
|             Gizmos.DrawWireCube(pressStartPoint, new Vector3(m_ButtonSize, m_PressDistance, m_ButtonSize));
 | |
|         }
 | |
| 
 | |
|         void OnValidate()
 | |
|         {
 | |
|             SetButtonHeight(0.0f);
 | |
|         }
 | |
|     }
 | |
| }
 |