363 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			363 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System.Collections.Generic;
 | |
| using UnityEngine.XR.Interaction.Toolkit;
 | |
| 
 | |
| namespace UnityEngine.XR.Content.Rendering
 | |
| {
 | |
|     /// <summary>
 | |
|     /// All-in-one controller for animated object highlights in different states - hovered, selected, and activated
 | |
|     /// </summary>
 | |
|     public class InteractableVisualsController : MonoBehaviour
 | |
|     {
 | |
|         const float k_ShineTime = 0.2f;
 | |
| 
 | |
|         enum PriorityHighlightingState
 | |
|         {
 | |
|             Unknown,
 | |
|             Highlighted,
 | |
|             Unhighlighted,
 | |
|         }
 | |
| 
 | |
|         static List<IXRTargetPriorityInteractor> s_InteractorList = new List<IXRTargetPriorityInteractor>();
 | |
| 
 | |
| #pragma warning disable 649
 | |
|         [Header("Audio")]
 | |
|         [SerializeField]
 | |
|         [Tooltip("The hover audio source.")]
 | |
|         AudioSource m_AudioHover;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("The click audio source.")]
 | |
|         AudioSource m_AudioClick;
 | |
| 
 | |
|         [Header("Visual")]
 | |
|         [SerializeField]
 | |
|         [Tooltip("Material capture settings.")]
 | |
|         HighlightController m_HighlightController = new HighlightController();
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("The outline highlight for selection.")]
 | |
|         OutlineHighlight m_OutlineHighlight;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("The material highlight for hover.")]
 | |
|         MaterialHighlight m_MaterialHighlight;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("The outline hover color.")]
 | |
|         Color m_HoverColor = new Color(0.25f, 0.7f, 0.9f, 1f);
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("The outline hover color when the Interactable has the highest priority for selection.")]
 | |
|         Color m_HoverPriorityColor = new Color(0.09411765f, 0.4392157f, 0.7137255f, 1f);
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("The outline selection color.")]
 | |
|         Color m_SelectionColor = new Color(1f, 0.4f, 0f, 1f);
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("To play material activate anim.")]
 | |
|         bool m_PlayMaterialActivateAnim;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("To play outline activate anim.")]
 | |
|         bool m_PlayOutlineActivateAnim;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("If true, the highlight state will be on during hover.")]
 | |
|         bool m_HighlightOnHover = true;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("If true, the highlight state will be on during hover when the Interactable has the highest priority for selection.")]
 | |
|         bool m_HighlightOnHoverPriority = true;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("If true, the highlight state will be on during select.")]
 | |
|         bool m_HighlightOnSelect = true;
 | |
| 
 | |
|         [SerializeField]
 | |
|         [Tooltip("If true, the highlight state will be on during activate.")]
 | |
|         bool m_HighlightOnActivate = true;
 | |
| 
 | |
| #pragma warning restore 649
 | |
| 
 | |
|         XRBaseInteractable m_Interactable;
 | |
| 
 | |
|         Material m_PulseMaterial;
 | |
|         float m_StartingAlpha;
 | |
|         float m_StartingWidth;
 | |
| 
 | |
|         int m_SelectedCount;
 | |
|         int m_HoveredCount;
 | |
|         bool m_Highlighting;
 | |
|         PriorityHighlightingState m_PriorityHighlightingState;
 | |
| 
 | |
|         bool m_PlayShine;
 | |
|         float m_ShineTimer;
 | |
| 
 | |
|         bool isActivated { get; set; }
 | |
|         bool isSelected => m_SelectedCount > 0;
 | |
|         bool isHovered => m_HoveredCount > 0;
 | |
| 
 | |
|         /// <summary>
 | |
|         /// See <see cref="MonoBehaviour"/>.
 | |
|         /// </summary>
 | |
|         protected void Awake()
 | |
|         {
 | |
|             // Find the grab interactable
 | |
|             m_Interactable = GetComponentInParent<XRBaseInteractable>();
 | |
| 
 | |
|             // Hook up to events
 | |
|             if (m_Interactable is IXRHoverInteractable hoverInteractable)
 | |
|             {
 | |
|                 hoverInteractable.hoverEntered.AddListener(OnHoverEntered);
 | |
|                 hoverInteractable.hoverExited.AddListener(OnHoverExited);
 | |
|             }
 | |
| 
 | |
|             if (m_Interactable is IXRSelectInteractable selectInteractable)
 | |
|             {
 | |
|                 selectInteractable.selectEntered.AddListener(OnSelectEntered);
 | |
|                 selectInteractable.selectExited.AddListener(OnSelectExited);
 | |
|             }
 | |
| 
 | |
|             if (m_Interactable is IXRActivateInteractable activateInteractable)
 | |
|             {
 | |
|                 activateInteractable.activated.AddListener(OnActivated);
 | |
|                 activateInteractable.deactivated.AddListener(OnDeactivated);
 | |
|             }
 | |
| 
 | |
|             // Cache materials for highlighting
 | |
|             m_HighlightController.rendererSource = m_Interactable.transform;
 | |
| 
 | |
|             // Tell the highlight objects to get renderers starting at the grab interactable down
 | |
|             if (m_MaterialHighlight != null)
 | |
|             {
 | |
|                 m_HighlightController.RegisterCacheUser(m_MaterialHighlight);
 | |
|                 m_PulseMaterial = m_MaterialHighlight.highlightMaterial;
 | |
| 
 | |
|                 if (m_PulseMaterial != null)
 | |
|                     m_StartingAlpha = m_PulseMaterial.GetFloat("_PulseMinAlpha");
 | |
|             }
 | |
|             if (m_OutlineHighlight != null)
 | |
|                 m_HighlightController.RegisterCacheUser(m_OutlineHighlight);
 | |
| 
 | |
|             m_HighlightController.Initialize();
 | |
|             m_StartingWidth = m_OutlineHighlight.outlineScale;
 | |
|         }
 | |
| 
 | |
|         /// <summary>
 | |
|         /// See <see cref="MonoBehaviour"/>.
 | |
|         /// </summary>
 | |
|         protected void Update()
 | |
|         {
 | |
|             UpdatePriorityHighlightingState();
 | |
|             m_HighlightController.Update();
 | |
|             if (m_MaterialHighlight != null)
 | |
|             {
 | |
|                 // Do timer count up/count down
 | |
|                 if (m_PlayShine)
 | |
|                 {
 | |
|                     m_ShineTimer += Time.deltaTime;
 | |
| 
 | |
|                     var shinePercent = Mathf.Clamp01(m_ShineTimer / k_ShineTime);
 | |
|                     var shineValue = Mathf.PingPong(shinePercent, 0.5f) * 2.0f;
 | |
| 
 | |
|                     m_PulseMaterial.SetFloat("_PulseMinAlpha", Mathf.Lerp(m_StartingAlpha, 1f, shineValue));
 | |
| 
 | |
|                     if (shinePercent >= 1.0f)
 | |
|                     {
 | |
|                         m_PlayShine = false;
 | |
|                         m_ShineTimer = 0.0f;
 | |
|                     }
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void UpdateHighlightState()
 | |
|         {
 | |
|             var shouldHighlight = false;
 | |
| 
 | |
|             if (isActivated)
 | |
|                 shouldHighlight = m_HighlightOnActivate;
 | |
|             else
 | |
|             {
 | |
|                 if (isSelected)
 | |
|                     shouldHighlight = m_HighlightOnSelect;
 | |
|                 else if (isHovered)
 | |
|                     shouldHighlight = m_HighlightOnHover || (m_HighlightOnHoverPriority && m_PriorityHighlightingState == PriorityHighlightingState.Highlighted);
 | |
|             }
 | |
| 
 | |
|             if (shouldHighlight == m_Highlighting)
 | |
|                 return;
 | |
| 
 | |
|             m_Highlighting = shouldHighlight;
 | |
| 
 | |
|             if (m_Highlighting)
 | |
|                 m_HighlightController.Highlight();
 | |
|             else
 | |
|                 m_HighlightController.Unhighlight();
 | |
|         }
 | |
| 
 | |
|         void OnHoverEntered(HoverEnterEventArgs args)
 | |
|         {
 | |
|             if (args.interactorObject is XRSocketInteractor)
 | |
|                 return;
 | |
| 
 | |
|             m_HoveredCount++;
 | |
| 
 | |
|             if (isSelected)
 | |
|                 return;
 | |
| 
 | |
|             if (m_AudioHover != null)
 | |
|                 m_AudioHover.Play();
 | |
| 
 | |
|             if (m_MaterialHighlight != null)
 | |
|                 m_PulseMaterial.color = m_HoverColor;
 | |
| 
 | |
|             if (m_OutlineHighlight != null)
 | |
|                 m_OutlineHighlight.outlineColor = m_HoverColor;
 | |
| 
 | |
|             m_PriorityHighlightingState = PriorityHighlightingState.Unknown;
 | |
|             UpdateHighlightState();
 | |
|         }
 | |
| 
 | |
|         void OnHoverExited(HoverExitEventArgs args)
 | |
|         {
 | |
|             if (args.interactorObject is XRSocketInteractor)
 | |
|                 return;
 | |
| 
 | |
|             m_HoveredCount--;
 | |
|             m_PriorityHighlightingState = PriorityHighlightingState.Unknown;
 | |
|             UpdateHighlightState();
 | |
|         }
 | |
| 
 | |
|         bool HasValidInteractor(List<IXRTargetPriorityInteractor> interactors)
 | |
|         {
 | |
|             foreach (var interactor in interactors)
 | |
|             {
 | |
|                 if (!(interactor is XRSocketInteractor))
 | |
|                     return true;
 | |
|             }
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         void UpdatePriorityHighlightingState()
 | |
|         {
 | |
|             if (!m_HighlightOnHoverPriority || !isHovered || isSelected)
 | |
|                 return;
 | |
| 
 | |
|             var manager = m_Interactable.interactionManager;
 | |
|             if (manager == null)
 | |
|                 return;
 | |
| 
 | |
|             var highestPriorityForSelection = manager.IsHighestPriorityTarget(m_Interactable, s_InteractorList);
 | |
|             if (!HasValidInteractor(s_InteractorList))
 | |
|                 return;
 | |
| 
 | |
|             if (highestPriorityForSelection && m_PriorityHighlightingState != PriorityHighlightingState.Highlighted)
 | |
|             {
 | |
|                 m_PriorityHighlightingState = PriorityHighlightingState.Highlighted;
 | |
| 
 | |
|                 if (m_PulseMaterial != null)
 | |
|                     m_PulseMaterial.color = m_HoverPriorityColor;
 | |
| 
 | |
|                 if (m_OutlineHighlight != null)
 | |
|                     m_OutlineHighlight.outlineColor = m_HoverPriorityColor;
 | |
| 
 | |
|                 UpdateHighlightState();
 | |
|             }
 | |
| 
 | |
|             if (!highestPriorityForSelection && m_PriorityHighlightingState != PriorityHighlightingState.Unhighlighted)
 | |
|             {
 | |
|                 m_PriorityHighlightingState = PriorityHighlightingState.Unhighlighted;
 | |
| 
 | |
|                 if (m_PulseMaterial != null)
 | |
|                     m_PulseMaterial.color = m_HoverColor;
 | |
| 
 | |
|                 if (m_OutlineHighlight != null)
 | |
|                     m_OutlineHighlight.outlineColor = m_HoverColor;
 | |
| 
 | |
|                 UpdateHighlightState();
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         void OnSelectEntered(SelectEnterEventArgs args)
 | |
|         {
 | |
|             if (args.interactorObject is XRSocketInteractor)
 | |
|                 return;
 | |
| 
 | |
|             if (m_AudioClick != null)
 | |
|                 m_AudioClick.Play();
 | |
| 
 | |
|             if (m_OutlineHighlight != null)
 | |
|             {
 | |
|                 m_OutlineHighlight.outlineColor = m_SelectionColor;
 | |
|                 m_OutlineHighlight.PlayPulseAnimation();
 | |
|             }
 | |
| 
 | |
|             if (m_MaterialHighlight != null)
 | |
|                 m_PulseMaterial.color = m_SelectionColor;
 | |
| 
 | |
|             m_SelectedCount++;
 | |
|             UpdateHighlightState();
 | |
|         }
 | |
| 
 | |
|         void OnSelectExited(SelectExitEventArgs args)
 | |
|         {
 | |
|             if (args.interactorObject is XRSocketInteractor)
 | |
|                 return;
 | |
| 
 | |
|             if (m_OutlineHighlight != null)
 | |
|                 m_OutlineHighlight.outlineColor = m_HoverColor;
 | |
|             if (m_MaterialHighlight != null)
 | |
|                 m_PulseMaterial.color = m_HoverColor;
 | |
| 
 | |
|             m_OutlineHighlight.PlayPulseAnimation();
 | |
| 
 | |
|             // In case the Interactable is dropped while activated.
 | |
|             isActivated = false;
 | |
|             m_SelectedCount--;
 | |
|             m_PriorityHighlightingState = PriorityHighlightingState.Unknown;
 | |
|             UpdateHighlightState();
 | |
|         }
 | |
| 
 | |
|         void OnActivated(ActivateEventArgs args)
 | |
|         {
 | |
|             if (args.interactorObject is XRSocketInteractor)
 | |
|                 return;
 | |
| 
 | |
|             if (m_OutlineHighlight != null)
 | |
|             {
 | |
|                 if (m_PlayMaterialActivateAnim)
 | |
|                     m_PlayShine = true;
 | |
| 
 | |
|                 if (m_PlayOutlineActivateAnim)
 | |
|                 {
 | |
|                     m_OutlineHighlight.outlineScale = 1f;
 | |
|                     m_OutlineHighlight.PlayPulseAnimation();
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             isActivated = true;
 | |
|             UpdateHighlightState();
 | |
|         }
 | |
| 
 | |
|         void OnDeactivated(DeactivateEventArgs args)
 | |
|         {
 | |
|             if (args.interactorObject is XRSocketInteractor)
 | |
|                 return;
 | |
| 
 | |
|             if (m_OutlineHighlight != null)
 | |
|             {
 | |
|                 if (m_PlayOutlineActivateAnim)
 | |
|                 {
 | |
|                     m_OutlineHighlight.outlineScale = m_StartingWidth;
 | |
|                     m_OutlineHighlight.PlayPulseAnimation();
 | |
|                 }
 | |
|             }
 | |
| 
 | |
|             isActivated = false;
 | |
|             UpdateHighlightState();
 | |
|         }
 | |
|     }
 | |
| }
 |