using System;
using System.Collections.Generic;
using Unity.XR.CoreUtils;
using UnityEngine.XR.Interaction.Toolkit.Utilities;
using UnityEngine.XR.Interaction.Toolkit.Utilities.Internal;
namespace UnityEngine.XR.Interaction.Toolkit.Locomotion
{
///
/// Behavior that manages user locomotion via transformation of an . This behavior
/// applies queued s every .
///
[AddComponentMenu("XR/Locomotion/XR Body Transformer", 11)]
[HelpURL(XRHelpURLConstants.k_XRBodyTransformer)]
[DefaultExecutionOrder(XRInteractionUpdateOrder.k_XRBodyTransformer)]
public class XRBodyTransformer : MonoBehaviour
{
struct OrderedTransformation
{
public IXRBodyTransformation transformation;
public int priority;
}
[SerializeField]
[Tooltip("The XR Origin to transform (will find one if None).")]
XROrigin m_XROrigin;
///
/// The XR Origin whose to transform (will find one if ).
///
///
/// Setting this property at runtime also re-links the to the new origin.
///
public XROrigin xrOrigin
{
get => m_XROrigin;
set
{
m_XROrigin = value;
if (Application.isPlaying)
InitializeMovableBody();
}
}
[SerializeField]
[RequireInterface(typeof(IXRBodyPositionEvaluator))]
[Tooltip("Object that determines the position of the user's body. If set to None, this behavior will estimate " +
"the position to be the camera position projected onto the XZ plane of the XR Origin.")]
Object m_BodyPositionEvaluatorObject;
IXRBodyPositionEvaluator m_BodyPositionEvaluator;
///
/// Object supplied to transformations that determines the position of the user's body. If
/// on , this will be set to a shared instance of .
///
///
/// Setting this property at runtime also re-links the to the new evaluator.
///
public IXRBodyPositionEvaluator bodyPositionEvaluator
{
get => m_BodyPositionEvaluator;
set
{
m_BodyPositionEvaluator = value;
if (Application.isPlaying)
InitializeMovableBody();
}
}
[SerializeField]
[RequireInterface(typeof(IConstrainedXRBodyManipulator))]
[Tooltip("Object used to perform movement that is constrained by collision (optional, may be None).")]
Object m_ConstrainedBodyManipulatorObject;
IConstrainedXRBodyManipulator m_ConstrainedBodyManipulator;
///
/// Object supplied to transformations that can be used to perform movement that is constrained by collision
/// (optional, may be ).
///
///
/// Setting this property at runtime unlinks the previous manipulator from the body and links the new manipulator
/// to the body.
///
public IConstrainedXRBodyManipulator constrainedBodyManipulator
{
get => m_ConstrainedBodyManipulator;
set
{
m_ConstrainedBodyManipulator = value;
if (m_MovableBody != null)
{
m_MovableBody.UnlinkConstrainedManipulator();
if (m_ConstrainedBodyManipulator != null)
m_MovableBody.LinkConstrainedManipulator(m_ConstrainedBodyManipulator);
}
}
}
[SerializeField]
[Tooltip("When enabled and if a Constrained Manipulator is not already assigned, this behavior will use the XR " +
"Origin's Character Controller to perform constrained movement, if one exists on the XR Origin's base GameObject.")]
bool m_UseCharacterControllerIfExists = true;
///
/// If and if a is not already assigned, this
/// behavior will check in if the has a
/// . If so, it will set to a shared
/// instance of , so that the Character Controller is used
/// to perform constrained movement.
///
public bool useCharacterControllerIfExists
{
get => m_UseCharacterControllerIfExists;
set => m_UseCharacterControllerIfExists = value;
}
///
/// Calls the methods in its invocation list every before transformations are applied.
///
public event Action beforeApplyTransformations;
bool m_UsingDynamicBodyPositionEvaluator;
bool m_UsingDynamicConstrainedBodyManipulator;
XRMovableBody m_MovableBody;
readonly LinkedList m_TransformationsQueue = new LinkedList();
///
/// See .
///
protected virtual void Reset()
{
m_XROrigin = ComponentLocatorUtility.FindComponent();
}
///
/// See .
///
protected virtual void OnEnable()
{
if (m_XROrigin == null)
{
if (!ComponentLocatorUtility.TryFindComponent(out m_XROrigin))
{
Debug.LogError("XR Body Transformer requires an XR Origin in the scene.", this);
enabled = false;
return;
}
}
m_BodyPositionEvaluator = m_BodyPositionEvaluatorObject as IXRBodyPositionEvaluator;
if (m_BodyPositionEvaluator == null)
{
m_UsingDynamicBodyPositionEvaluator = true;
m_BodyPositionEvaluator = ScriptableSingletonCache.GetInstance(this);
}
m_ConstrainedBodyManipulator = m_ConstrainedBodyManipulatorObject as IConstrainedXRBodyManipulator;
if (m_ConstrainedBodyManipulator == null && m_UseCharacterControllerIfExists)
{
if (m_XROrigin.Origin.TryGetComponent(out _))
{
m_UsingDynamicConstrainedBodyManipulator = true;
m_ConstrainedBodyManipulator =
ScriptableSingletonCache.GetInstance(this);
}
}
InitializeMovableBody();
}
///
/// See .
///
protected virtual void OnDisable()
{
m_MovableBody?.UnlinkConstrainedManipulator();
m_MovableBody = null;
if (m_UsingDynamicBodyPositionEvaluator)
{
ScriptableSingletonCache.ReleaseInstance(this);
m_UsingDynamicBodyPositionEvaluator = false;
}
if (m_UsingDynamicConstrainedBodyManipulator)
{
ScriptableSingletonCache.ReleaseInstance(this);
m_UsingDynamicConstrainedBodyManipulator = false;
}
}
///
/// See .
///
protected virtual void Update()
{
beforeApplyTransformations?.Invoke(this);
while (m_TransformationsQueue.Count > 0)
{
m_TransformationsQueue.First.Value.transformation.Apply(m_MovableBody);
m_TransformationsQueue.RemoveFirst();
}
}
void InitializeMovableBody()
{
m_MovableBody = new XRMovableBody(m_XROrigin, m_BodyPositionEvaluator);
if (m_ConstrainedBodyManipulator != null)
m_MovableBody.LinkConstrainedManipulator(m_ConstrainedBodyManipulator);
}
///
/// Queues a transformation to be applied during the next . Transformations are applied
/// sequentially based on ascending . Transformations with the same priority are
/// applied in the order they were queued. Each transformation is removed from the queue after it is applied.
///
/// 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.
public void QueueTransformation(IXRBodyTransformation transformation, int priority = 0)
{
var orderedTransformation = new OrderedTransformation
{
transformation = transformation,
priority = priority,
};
var node = m_TransformationsQueue.First;
if (node == null || node.Value.priority > priority)
{
m_TransformationsQueue.AddFirst(orderedTransformation);
return;
}
while (node.Next != null && node.Next.Value.priority <= priority)
{
node = node.Next;
}
m_TransformationsQueue.AddAfter(node, orderedTransformation);
}
///
/// See .
///
protected virtual void OnDrawGizmosSelected()
{
if (m_UseCharacterControllerIfExists && m_ConstrainedBodyManipulator != null &&
m_ConstrainedBodyManipulator is CharacterControllerBodyManipulator characterControllerManipulator &&
characterControllerManipulator.characterController != null)
{
var characterController = characterControllerManipulator.characterController;
var center = characterController.center + characterController.transform.position + (Vector3.up * ((characterController.stepOffset - characterController.skinWidth) * 0.5f));
var height = characterController.height + characterController.stepOffset + characterController.skinWidth;
var radius = characterController.radius + characterController.skinWidth;
GizmoHelpers.DrawCapsule(center, height, radius, Vector3.up, new Color(1.0f, 0.92f, 0.016f, 0.5f));
}
}
}
}