using UnityEngine.UI; using UnityEngine.XR.Interaction.Toolkit.Interactables; using UnityEngine.XR.Interaction.Toolkit.Interactables.Visuals; using UnityEngine.XR.Interaction.Toolkit.Interactors.Visuals; using UnityEngine.XR.Interaction.Toolkit.Locomotion.Teleportation; namespace UnityEngine.XR.Interaction.Toolkit.Samples.StarterAssets { /// /// A custom reticle for a that displays its progress towards evaluating /// a destination anchor and an indicator pointing in the direction of the destination anchor. /// public class MultiAnchorTeleportReticle : MonoBehaviour, IXRInteractableCustomReticle { [SerializeField] [Tooltip("Filled image that displays the progress towards evaluating a destination anchor.")] Image m_TimerProgressFilledImage; /// /// image that displays the progress towards evaluating a destination anchor. /// public Image timerProgressFilledImage { get => m_TimerProgressFilledImage; set => m_TimerProgressFilledImage = value; } [SerializeField] [Tooltip("Object that is rotated about its Z axis to point at the destination anchor.")] GameObject m_DestinationIndicator; /// /// Object that is rotated about its Z axis to point at the destination anchor. /// public GameObject destinationIndicator { get => m_DestinationIndicator; set => m_DestinationIndicator = value; } [SerializeField] [Tooltip("Object that is rotated about its Z axis to point at the potential destination while still evaluating.")] GameObject m_PotentialDestinationIndicator; /// /// Object that is rotated about its Z axis to point at the potential destination while still evaluating. /// public GameObject potentialDestinationIndicator { get => m_PotentialDestinationIndicator; set => m_PotentialDestinationIndicator = value; } [SerializeField] [Tooltip("The amount of time, in seconds, between updates to the indicator pointing at the potential destination.")] float m_PotentialIndicatorUpdateFrequency = 0.1f; /// /// The amount of time, in seconds, between updates to the indicator pointing at the potential destination. /// public float potentialIndicatorUpdateFrequency { get => m_PotentialIndicatorUpdateFrequency; set => m_PotentialIndicatorUpdateFrequency = value; } TeleportationMultiAnchorVolume m_AnchorVolume; float m_LastPotentialIndicatorUpdateTime; /// public void OnReticleAttached(XRBaseInteractable interactable, IXRCustomReticleProvider reticleProvider) { m_AnchorVolume = interactable as TeleportationMultiAnchorVolume; m_PotentialDestinationIndicator.SetActive(false); m_DestinationIndicator.SetActive(false); m_TimerProgressFilledImage.type = Image.Type.Filled; m_TimerProgressFilledImage.fillAmount = 0f; if (m_AnchorVolume == null) return; m_AnchorVolume.destinationAnchorChanged += OnDestinationAnchorChanged; } /// public void OnReticleDetaching() { if (m_AnchorVolume == null) return; m_AnchorVolume.destinationAnchorChanged -= OnDestinationAnchorChanged; m_AnchorVolume = null; } /// /// See . /// protected void Update() { if (m_AnchorVolume == null) return; var destinationAnchor = m_AnchorVolume.destinationAnchor; if (destinationAnchor != null) { PointAtTarget(m_DestinationIndicator.transform, destinationAnchor.position); return; } m_TimerProgressFilledImage.fillAmount = m_AnchorVolume.destinationEvaluationProgress; if (Time.time - m_LastPotentialIndicatorUpdateTime >= m_PotentialIndicatorUpdateFrequency) UpdatePotentialDestinationIndicator(); } void UpdatePotentialDestinationIndicator() { m_LastPotentialIndicatorUpdateTime = Time.time; if (!m_AnchorVolume.destinationEvaluationSettings.Value.pollForDestinationChange) { m_PotentialDestinationIndicator.SetActive(false); return; } var potentialDestinationIndex = m_AnchorVolume.destinationEvaluationFilter.GetDestinationAnchorIndex(m_AnchorVolume); var anchors = m_AnchorVolume.anchorTransforms; if (potentialDestinationIndex < 0 || potentialDestinationIndex >= anchors.Count) { m_PotentialDestinationIndicator.SetActive(false); return; } var potentialDestination = anchors[potentialDestinationIndex]; if (potentialDestination == null) { m_PotentialDestinationIndicator.SetActive(false); return; } m_PotentialDestinationIndicator.SetActive(true); PointAtTarget(m_PotentialDestinationIndicator.transform, potentialDestination.position); } void OnDestinationAnchorChanged(TeleportationMultiAnchorVolume anchorVolume) { var destinationAnchor = anchorVolume.destinationAnchor; if (destinationAnchor != null) { m_TimerProgressFilledImage.fillAmount = 1f; m_PotentialDestinationIndicator.SetActive(false); m_DestinationIndicator.SetActive(true); PointAtTarget(m_DestinationIndicator.transform, destinationAnchor.position); } else { m_TimerProgressFilledImage.fillAmount = 0f; m_DestinationIndicator.SetActive(false); } } static void PointAtTarget(Transform indicatorTransform, Vector3 targetPosition) { indicatorTransform.rotation = Quaternion.LookRotation(indicatorTransform.forward, targetPosition - indicatorTransform.position); } } }