VR4Medical/ICI/Library/PackageCache/com.unity.xr.interaction.toolkit@42ef3600567b/Runtime/Locomotion/LocomotionProvider.cs
2025-07-29 13:45:50 +03:00

275 lines
14 KiB
C#

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