using System; using System.Collections.Generic; 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 changes . /// /// public event Action locomotionStateChanged; /// /// 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 /// and all of this provider's queued transformation(s) have been applied, if any. /// /// 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 /// or , and only if the provider /// has queued at least one transformation that frame. /// /// /// This is invoked before the applies the transformations from other /// providers as well. /// /// public event Action beforeStepLocomotion; /// /// Calls the methods in its invocation list just after the applies this /// provider's transformation(s). This is invoked at most once per frame while is /// or , and only if the provider /// has queued at least one transformation that frame. /// /// /// This is invoked after the applies the transformations from other /// providers as well. /// /// public event Action afterStepLocomotion; /// /// (Read Only) List of active Locomotion Provider component instances. /// /// internal static List locomotionProviders { get; } = new List(); /// /// Calls the methods in its invocation list when a provider is added. /// /// internal static event Action locomotionProvidersChanged; XRBodyTransformer m_ActiveBodyTransformer; XRBodyTransformer m_SubscribedTransformer; bool m_AnyTransformationsQueued; /// /// 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 locomotionProviders.Add(this); locomotionProvidersChanged?.Invoke(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. /// /// 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() => 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() => 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() => m_Mediator != null && m_Mediator.TryEndLocomotion(this); /// /// Called when locomotion enters the state, after the provider gains /// access to the and before it invokes . /// protected virtual void OnLocomotionStarting() { } /// /// Called when locomotion enters the state, after the provider invokes /// and before it loses access to the . /// protected virtual void OnLocomotionEnding() { } /// /// Called when the locomotion state changes before it invokes . /// /// The locomotion state this provider is changing to. protected virtual void OnLocomotionStateChanging(LocomotionState state) { } internal void OnLocomotionStateChanging(LocomotionState oldState, LocomotionState state, XRBodyTransformer transformer) { if (state == LocomotionState.Moving) { if (oldState == LocomotionState.Ended && m_AnyTransformationsQueued) Debug.LogWarning($"LocomotionProvider ({GetType().Name}) changed state from LocomotionState.Ended to LocomotionState.Moving" + " before its queued transformations have been applied. The deferred OnLocomotionEnding method call and locomotionEnded event will not be invoked.", this); m_ActiveBodyTransformer = transformer; Subscribe(transformer); } else if (state == LocomotionState.Ended) { m_ActiveBodyTransformer = null; } OnLocomotionStateChanging(state); locomotionStateChanged?.Invoke(this, state); if (state == LocomotionState.Moving) { OnLocomotionStarting(); locomotionStarted?.Invoke(this); } else if (state == LocomotionState.Ended && !m_AnyTransformationsQueued) { // Unsubscribe from the transformer events if there are no transformations queued. // Otherwise, it will happen during the next callback, triggered by clearing the transformer reference. Unsubscribe(); OnLocomotionEnding(); locomotionEnded?.Invoke(this); } } /// /// 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_AnyTransformationsQueued = 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_AnyTransformationsQueued = 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 Subscribe(XRBodyTransformer transformer) { if (m_SubscribedTransformer == transformer) return; Unsubscribe(); transformer.beforeApplyTransformations += OnBeforeApplyTransformations; transformer.afterApplyTransformations += OnAfterApplyTransformations; m_SubscribedTransformer = transformer; } void Unsubscribe() { if (m_SubscribedTransformer == null) return; m_SubscribedTransformer.beforeApplyTransformations -= OnBeforeApplyTransformations; m_SubscribedTransformer.afterApplyTransformations -= OnAfterApplyTransformations; m_SubscribedTransformer = null; } void OnBeforeApplyTransformations(XRBodyTransformer transformer) { if (m_AnyTransformationsQueued) beforeStepLocomotion?.Invoke(this); } void OnAfterApplyTransformations(ApplyBodyTransformationsEventArgs args) { if (m_AnyTransformationsQueued) afterStepLocomotion?.Invoke(this); m_AnyTransformationsQueued = false; // Faster equivalent to checking if locomotionState == LocomotionState.Ended. // This value is cleared to real null when changing to LocomotionState.Ended. // Since this callback is invoked every frame while the provider is moving, // this is cheaper than a full UnityEngine.Object operator== check. if (m_ActiveBodyTransformer is null) { Unsubscribe(); OnLocomotionEnding(); locomotionEnded?.Invoke(this); } } } }