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;
}
}
}