using System; using UnityEngine.Scripting.APIUpdating; using UnityEngine.XR.Interaction.Toolkit.Utilities; namespace UnityEngine.XR.Interaction.Toolkit.Locomotion { /// /// Base for a behavior that implements a specific type of user locomotion. This behavior communicates with a /// to gain access to the mediator's , which the /// provider can use to queue s that move the user. /// [DefaultExecutionOrder(XRInteractionUpdateOrder.k_LocomotionProviders)] [MovedFrom("UnityEngine.XR.Interaction.Toolkit")] public abstract partial class LocomotionProvider : MonoBehaviour { [SerializeField] [Tooltip("The behavior that this provider communicates with for access to the mediator's XR Body Transformer. " + "If one is not provided, this provider will attempt to locate one during its Awake call.")] LocomotionMediator m_Mediator; /// /// The behavior that this provider communicates with for access to the mediator's . /// If one is not provided, this provider will attempt to locate one during its call. /// public LocomotionMediator mediator { get => m_Mediator; set => m_Mediator = value; } [SerializeField] [Tooltip("The queue order of this provider's transformations of the XR Origin. The lower the value, the " + "earlier the transformations are applied.")] int m_TransformationPriority; /// /// The queue order of this provider's transformations of the XR Origin. The lower the value, the earlier the /// transformations are applied. /// /// public int transformationPriority { get => m_TransformationPriority; set => m_TransformationPriority = value; } /// /// The current state of locomotion. The determines this state based on the provider's /// requests for the . /// /// /// /// public LocomotionState locomotionState => m_Mediator != null ? m_Mediator.GetProviderLocomotionState(this) : LocomotionState.Idle; /// /// Whether the provider is actively preparing or performing locomotion. This is when /// is or , /// otherwise. /// /// public bool isLocomotionActive => locomotionState.IsActive(); /// /// Whether the provider has finished preparing for locomotion and is ready to enter the state. /// This only applies when is , so there is /// no need for this implementation to query . /// public virtual bool canStartMoving => true; /// /// Calls the methods in its invocation list when the provider has entered the state. /// /// public event Action locomotionStarted; /// /// Calls the methods in its invocation list when the provider has entered the state. /// /// public event Action locomotionEnded; /// /// Calls the methods in its invocation list just before the applies this /// provider's transformation(s). This is invoked at most once per frame while is /// , and only if the provider has queued at least one transformation. /// /// This is invoked before the applies the transformations from other /// providers as well. public event Action beforeStepLocomotion; XRBodyTransformer m_ActiveBodyTransformer; bool m_AnyTransformationsThisFrame; /// /// See . /// protected virtual void Awake() { #pragma warning disable CS0618 // Type or member is obsolete if (m_System == null) { m_System = GetComponentInParent(); if (m_System == null) { ComponentLocatorUtility.TryFindComponent(out m_System); } } if (m_Mediator == null) { m_Mediator = GetComponentInParent(); if (m_Mediator == null) { ComponentLocatorUtility.TryFindComponent(out m_Mediator); } } if (m_Mediator == null && m_System == null) { Debug.LogError("Locomotion Provider requires a Locomotion Mediator or Locomotion System (legacy) in the scene.", this); enabled = false; } #pragma warning restore CS0618 // Type or member is obsolete } /// /// Attempts to transition this provider into the state. This succeeds /// if was when this was called. /// /// Returns if was /// when this was called, otherwise. /// /// If this succeeds, then the provider can enter the state either by calling /// or by waiting for the 's next /// in which the provider's /// is . When the provider enters the state, it will /// invoke and gain access to the . /// protected bool TryPrepareLocomotion() { return m_Mediator != null && m_Mediator.TryPrepareLocomotion(this); } /// /// Attempts to transition this provider into the state. This succeeds if /// was not already when /// this was called. /// /// Returns if was not already /// when this was called, otherwise. /// /// This method bypasses the check for . /// After this provider enters the state, it will invoke /// and gain access to the . /// protected bool TryStartLocomotionImmediately() { return m_Mediator != null && m_Mediator.TryStartLocomotion(this); } /// /// Attempts to transition this provider into the state. This succeeds if /// was when this was called. /// /// Returns if was /// when this was called, otherwise. /// /// After this provider enters the state, it will invoke /// and lose access to the . Then during the /// 's in the next frame, the provider will enter /// the state, unless it has called or /// again. /// protected bool TryEndLocomotion() { return m_Mediator != null && m_Mediator.TryEndLocomotion(this); } internal void OnLocomotionStart(XRBodyTransformer transformer) { m_ActiveBodyTransformer = transformer; m_ActiveBodyTransformer.beforeApplyTransformations += OnBeforeTransformationsApplied; m_AnyTransformationsThisFrame = false; OnLocomotionStarting(); locomotionStarted?.Invoke(this); } /// /// Called when locomotion enters the state, after the provider gains /// access to the and before it invokes . /// protected virtual void OnLocomotionStarting() { } internal void OnLocomotionEnd() { locomotionEnded?.Invoke(this); OnLocomotionEnding(); if (m_ActiveBodyTransformer != null) m_ActiveBodyTransformer.beforeApplyTransformations -= OnBeforeTransformationsApplied; m_ActiveBodyTransformer = null; } /// /// Called when locomotion enters the state, after the provider invokes /// and before it loses access to the . /// protected virtual void OnLocomotionEnding() { } /// /// Attempts to queue a transformation to be applied during the active 's next /// . The provider's determines when /// the transformation is applied in relation to others. The queue attempt only succeeds if the provider is in /// the state. /// /// The transformation that will receive a call to /// in the next . /// Returns if the provider has access to the , /// otherwise. /// This should only be called when is , /// otherwise this method will do nothing and return . /// protected bool TryQueueTransformation(IXRBodyTransformation bodyTransformation) { if (!CanQueueTransformation()) return false; m_ActiveBodyTransformer.QueueTransformation(bodyTransformation, m_TransformationPriority); m_AnyTransformationsThisFrame = true; return true; } /// /// Attempts to queue a transformation to be applied during the active 's next /// . The given determines when the /// transformation is applied in relation to others. The queue attempt only succeeds if the provider is in the /// state. /// /// The transformation that will receive a call to /// in the next . /// Value that determines when to apply the transformation. Transformations with lower /// priority values are applied before those with higher priority values. /// Returns if the provider has access to the , /// otherwise. /// This should only be called when is , /// otherwise this method will do nothing and return . /// protected bool TryQueueTransformation(IXRBodyTransformation bodyTransformation, int priority) { if (!CanQueueTransformation()) return false; m_ActiveBodyTransformer.QueueTransformation(bodyTransformation, priority); m_AnyTransformationsThisFrame = true; return true; } bool CanQueueTransformation() { if (m_ActiveBodyTransformer == null) { if (locomotionState == LocomotionState.Moving) { Debug.LogError("Cannot queue transformation because reference to active XR Body Transformer " + "is null, even though Locomotion Provider is in Moving state. This should not happen.", this); } return false; } return true; } void OnBeforeTransformationsApplied(XRBodyTransformer bodyTransformer) { if (m_AnyTransformationsThisFrame) beforeStepLocomotion?.Invoke(this); m_AnyTransformationsThisFrame = false; } } }