using System.Diagnostics; using UnityEngine.XR.Interaction.Toolkit.Interactables; using UnityEngine.XR.Interaction.Toolkit.Interactors; using UnityEngine.XR.Interaction.Toolkit.Utilities; using UnityEngine.XR.Interaction.Toolkit.Utilities.Internal; namespace UnityEngine.XR.Interaction.Toolkit.Feedback { /// /// Component that responds to select and hover events by playing audio clips. /// /// [AddComponentMenu("XR/Feedback/Simple Audio Feedback", 11)] [HelpURL(XRHelpURLConstants.k_SimpleAudioFeedback)] public class SimpleAudioFeedback : MonoBehaviour { [SerializeField] [RequireInterface(typeof(IXRInteractor))] Object m_InteractorSourceObject; [SerializeField] AudioSource m_AudioSource; /// /// The Audio Source component to use to play audio clips. /// public AudioSource audioSource { get => m_AudioSource; set => m_AudioSource = value; } [SerializeField] bool m_PlaySelectEntered; /// /// Whether to play a sound when the interactor starts selecting an interactable. /// public bool playSelectEntered { get => m_PlaySelectEntered; set => m_PlaySelectEntered = value; } [SerializeField] AudioClip m_SelectEnteredClip; /// /// The audio clip to play when the interactor starts selecting an interactable. /// public AudioClip selectEnteredClip { get => m_SelectEnteredClip; set => m_SelectEnteredClip = value; } [SerializeField] bool m_PlaySelectExited; /// /// Whether to play a sound when the interactor stops selecting an interactable without being canceled. /// public bool playSelectExited { get => m_PlaySelectExited; set => m_PlaySelectExited = value; } [SerializeField] AudioClip m_SelectExitedClip; /// /// The audio clip to play when the interactor stops selecting an interactable without being canceled. /// public AudioClip selectExitedClip { get => m_SelectExitedClip; set => m_SelectExitedClip = value; } [SerializeField] bool m_PlaySelectCanceled; /// /// Whether to play a sound when the interactor stops selecting an interactable due to being canceled. /// public bool playSelectCanceled { get => m_PlaySelectCanceled; set => m_PlaySelectCanceled = value; } [SerializeField] AudioClip m_SelectCanceledClip; /// /// The audio clip to play when the interactor stops selecting an interactable due to being canceled. /// public AudioClip selectCanceledClip { get => m_SelectCanceledClip; set => m_SelectCanceledClip = value; } [SerializeField] bool m_PlayHoverEntered; /// /// Whether to play a sound when the interactor starts hovering over an interactable. /// public bool playHoverEntered { get => m_PlayHoverEntered; set => m_PlayHoverEntered = value; } [SerializeField] AudioClip m_HoverEnteredClip; /// /// The audio clip to play when the interactor starts hovering over an interactable. /// public AudioClip hoverEnteredClip { get => m_HoverEnteredClip; set => m_HoverEnteredClip = value; } [SerializeField] bool m_PlayHoverExited; /// /// Whether to play a sound when the interactor stops hovering over an interactable without being canceled. /// public bool playHoverExited { get => m_PlayHoverExited; set => m_PlayHoverExited = value; } [SerializeField] AudioClip m_HoverExitedClip; /// /// The audio clip to play when the interactor stops hovering over an interactable without being canceled. /// public AudioClip hoverExitedClip { get => m_HoverExitedClip; set => m_HoverExitedClip = value; } [SerializeField] bool m_PlayHoverCanceled; /// /// Whether to play a sound when the interactor stops hovering over an interactable due to being canceled. /// public bool playHoverCanceled { get => m_PlayHoverCanceled; set => m_PlayHoverCanceled = value; } [SerializeField] AudioClip m_HoverCanceledClip; /// /// The audio clip to play when the interactor stops hovering over an interactable due to being canceled. /// public AudioClip hoverCanceledClip { get => m_HoverCanceledClip; set => m_HoverCanceledClip = value; } [SerializeField] bool m_AllowHoverAudioWhileSelecting; /// /// Whether to allow hover audio to play while the interactor is selecting an interactable. /// public bool allowHoverAudioWhileSelecting { get => m_AllowHoverAudioWhileSelecting; set => m_AllowHoverAudioWhileSelecting = value; } readonly UnityObjectReferenceCache m_InteractorSource = new UnityObjectReferenceCache(); /// /// See . /// [Conditional("UNITY_EDITOR")] protected void Reset() { #if UNITY_EDITOR m_InteractorSourceObject = GetComponentInParent(true) as Object; m_AudioSource = GetComponent(); #endif } /// /// See . /// protected void Awake() { if (m_InteractorSourceObject == null) m_InteractorSourceObject = GetComponentInParent(true) as Object; if (m_PlaySelectEntered || m_PlaySelectExited || m_PlaySelectCanceled || m_PlayHoverEntered || m_PlayHoverExited || m_PlayHoverCanceled) { if (m_AudioSource == null) CreateAudioSource(); } } /// /// See . /// protected void OnEnable() { Subscribe(GetInteractorSource()); } /// /// See . /// protected void OnDisable() { Unsubscribe(GetInteractorSource()); } /// /// Gets the interactor this behavior should subscribe to for events. /// /// Returns the interactor this behavior should subscribe to for events. /// public IXRInteractor GetInteractorSource() { return m_InteractorSource.Get(m_InteractorSourceObject); } /// /// Sets the interactor this behavior should subscribe to for events. /// /// The interactor this behavior should subscribe to for events. /// /// This also sets the serialized field to the given interactor as a Unity Object. /// /// public void SetInteractorSource(IXRInteractor interactor) { if (Application.isPlaying && isActiveAndEnabled) Unsubscribe(m_InteractorSource.Get(m_InteractorSourceObject)); m_InteractorSource.Set(ref m_InteractorSourceObject, interactor); if (Application.isPlaying && isActiveAndEnabled) Subscribe(interactor); } /// /// Play the given audio clip. /// /// The audio clip to play. protected void PlayAudio(AudioClip clip) { if (clip == null) return; if (m_AudioSource == null) CreateAudioSource(); m_AudioSource.PlayOneShot(clip); } void CreateAudioSource() { if (!TryGetComponent(out m_AudioSource)) m_AudioSource = gameObject.AddComponent(); m_AudioSource.loop = false; m_AudioSource.playOnAwake = false; } void Subscribe(IXRInteractor interactor) { if (interactor == null || (interactor is Object interactorObject && interactorObject == null)) return; if (interactor is IXRSelectInteractor selectInteractor) { selectInteractor.selectEntered.AddListener(OnSelectEntered); selectInteractor.selectExited.AddListener(OnSelectExited); } if (interactor is IXRHoverInteractor hoverInteractor) { hoverInteractor.hoverEntered.AddListener(OnHoverEntered); hoverInteractor.hoverExited.AddListener(OnHoverExited); } } void Unsubscribe(IXRInteractor interactor) { if (interactor == null || (interactor is Object interactorObject && interactorObject == null)) return; if (interactor is IXRSelectInteractor selectInteractor) { selectInteractor.selectEntered.RemoveListener(OnSelectEntered); selectInteractor.selectExited.RemoveListener(OnSelectExited); } if (interactor is IXRHoverInteractor hoverInteractor) { hoverInteractor.hoverEntered.RemoveListener(OnHoverEntered); hoverInteractor.hoverExited.RemoveListener(OnHoverExited); } } void OnSelectEntered(SelectEnterEventArgs args) { if (m_PlaySelectEntered) PlayAudio(m_SelectEnteredClip); } void OnSelectExited(SelectExitEventArgs args) { if (m_PlaySelectCanceled && args.isCanceled) PlayAudio(m_SelectCanceledClip); if (m_PlaySelectExited && !args.isCanceled) PlayAudio(m_SelectExitedClip); } void OnHoverEntered(HoverEnterEventArgs args) { if (m_PlayHoverEntered && IsHoverAudioAllowed(args.interactorObject, args.interactableObject)) PlayAudio(m_HoverEnteredClip); } void OnHoverExited(HoverExitEventArgs args) { if (!IsHoverAudioAllowed(args.interactorObject, args.interactableObject)) return; if (m_PlayHoverCanceled && args.isCanceled) PlayAudio(m_HoverCanceledClip); if (m_PlayHoverExited && !args.isCanceled) PlayAudio(m_HoverExitedClip); } bool IsHoverAudioAllowed(IXRInteractor interactor, IXRInteractable interactable) { return m_AllowHoverAudioWhileSelecting || !IsSelecting(interactor, interactable); } static bool IsSelecting(IXRInteractor interactor, IXRInteractable interactable) { return interactor is IXRSelectInteractor selectInteractor && interactable is IXRSelectInteractable selectable && selectInteractor.IsSelecting(selectable); } } }