using System;
using Unity.XR.CoreUtils;
using UnityEngine.Serialization;
using UnityEngine.XR.Interaction.Toolkit.Inputs.Haptics;
namespace UnityEngine.XR.Interaction.Toolkit
{
///
/// Interprets feature values on a tracked input controller device into XR Interaction states, such as Select.
/// Additionally, it applies the current pose value of a tracked device to the transform of the GameObject.
///
///
[DefaultExecutionOrder(XRInteractionUpdateOrder.k_Controllers)]
[DisallowMultipleComponent]
[Obsolete("XRBaseController has been deprecated in version 3.0.0. Its functionality has been distributed into different components.")]
public abstract class XRBaseController : MonoBehaviour, IXRHapticImpulseProvider
{
///
/// The time within the frame that controller pose will be sampled.
///
///
public enum UpdateType
{
///
/// Sample input at both update and directly before rendering. For smooth controller pose tracking,
/// we recommend using this value as it will provide the lowest input latency for the device.
/// This is the default value for the UpdateType option.
///
UpdateAndBeforeRender,
///
/// Only sample input during the update phase of the frame.
///
Update,
///
/// Only sample input directly before rendering.
///
BeforeRender,
///
/// Sample input corresponding to .
///
Fixed,
}
///
/// (Deprecated) Gets the state of the controller.
///
/// When this method returns, contains the object representing the state of the controller.
/// Returns .
///
[Obsolete("GetControllerState has been deprecated. Use currentControllerState instead.", true)]
public virtual bool GetControllerState(out XRControllerState controllerState)
{
controllerState = default;
return default;
}
///
/// (Deprecated) Sets the state of the controller.
///
/// The state of the controller to set.
///
[Obsolete("SetControllerState has been deprecated. Use currentControllerState instead.", true)]
public virtual void SetControllerState(XRControllerState controllerState)
{
}
///
/// modelTransform has been deprecated due to being renamed. Use instead.
[Obsolete("modelTransform has been deprecated due to being renamed. Use modelParent instead. (UnityUpgradable) -> modelParent", true)]
public Transform modelTransform
{
get => default;
set => _ = value;
}
///
/// (Deprecated) Defines the deadzone values for device-based input when performing translate or rotate anchor actions.
///
///
///
///
/// anchorControlDeadzone has been deprecated. Please configure deadzone on the Rotate Anchor and Translate Anchor Actions.
///
[Obsolete("anchorControlDeadzone is obsolete. Please configure deadzone on the Rotate Anchor and Translate Anchor Actions.", true)]
public float anchorControlDeadzone
{
get => default;
set => _ = value;
}
///
/// (Deprecated) Defines the off-axis deadzone values for device-based input when performing translate or rotate anchor actions.
///
///
///
/// anchorControlOffAxisDeadzone has been deprecated. Please configure deadzone on the Rotate Anchor and Translate Anchor Actions.
///
[Obsolete("anchorControlOffAxisDeadzone is obsolete. Please configure deadzone on the Rotate Anchor and Translate Anchor Actions.", true)]
public float anchorControlOffAxisDeadzone
{
get => default;
set => _ = value;
}
[SerializeField]
UpdateType m_UpdateTrackingType = UpdateType.UpdateAndBeforeRender;
///
/// The time within the frame that the controller samples tracking input.
///
///
public UpdateType updateTrackingType
{
get => m_UpdateTrackingType;
set => m_UpdateTrackingType = value;
}
[SerializeField]
bool m_EnableInputTracking = true;
///
/// Whether input pose tracking is enabled for this controller.
/// When enabled, Unity reads the current tracking pose input of the controller device each frame.
///
///
/// You can disable this in order to drive the controller state manually instead of from reading current inputs,
/// such as when playing back recorded pose inputs.
///
///
public bool enableInputTracking
{
get => m_EnableInputTracking;
set => m_EnableInputTracking = value;
}
[SerializeField]
bool m_EnableInputActions = true;
///
/// Whether input for XR Interaction events is enabled for this controller.
/// When enabled, Unity reads the current input of the controller device each frame.
///
///
/// You can disable this in order to drive the controller state manually instead of from reading current inputs,
/// such as when playing back recorded inputs.
///
///
public bool enableInputActions
{
get => m_EnableInputActions;
set => m_EnableInputActions = value;
}
[SerializeField]
Transform m_ModelPrefab;
///
/// The prefab of a controller model to show for this controller that this behavior automatically instantiates.
///
///
/// This behavior automatically instantiates an instance of the prefab as a child
/// of upon startup unless is already set,
/// in which case this value is ignored.
///
///
public Transform modelPrefab
{
get => m_ModelPrefab;
set => m_ModelPrefab = value;
}
[SerializeField, FormerlySerializedAs("m_ModelTransform")]
Transform m_ModelParent;
///
/// The transform that this behavior uses as the parent for the model prefab when it is instantiated.
///
///
/// Automatically instantiated and set in if not already set.
/// Setting this will not automatically destroy the previous object.
///
public Transform modelParent
{
get => m_ModelParent;
set
{
m_ModelParent = value;
if (m_Model != null)
m_Model.parent = m_ModelParent;
}
}
[SerializeField]
Transform m_Model;
///
/// The instance of the controller model in the scene. You can set this to an existing object instead of using .
///
///
/// If set, it should reference a child GameObject of this behavior so it will update with the controller pose.
///
public Transform model
{
get => m_Model;
set => m_Model = value;
}
[SerializeField]
bool m_AnimateModel;
///
/// Whether to animate the model in response to interaction events. When enabled, the animation trigger will be set for the corresponding
/// animator component on the controller model when a select or deselect interaction events occurs.
///
///
///
public bool animateModel
{
get => m_AnimateModel;
set => m_AnimateModel = value;
}
[SerializeField]
string m_ModelSelectTransition;
///
/// The animation trigger name to activate upon selecting.
///
///
public string modelSelectTransition
{
get => m_ModelSelectTransition;
set => m_ModelSelectTransition = value;
}
[SerializeField]
string m_ModelDeSelectTransition;
///
/// The animation trigger name to activate upon deselecting.
///
///
public string modelDeSelectTransition
{
get => m_ModelDeSelectTransition;
set => m_ModelDeSelectTransition = value;
}
bool m_HideControllerModel;
///
/// Whether to hide the controller model.
///
///
///
public bool hideControllerModel
{
get => m_HideControllerModel;
set
{
m_HideControllerModel = value;
if (m_Model != null)
m_Model.gameObject.SetActive(!m_HideControllerModel);
}
}
InteractionState m_SelectInteractionState;
///
/// (Read Only) The current select interaction state.
///
public InteractionState selectInteractionState => m_SelectInteractionState;
InteractionState m_ActivateInteractionState;
///
/// (Read Only) The current activate interaction state.
///
public InteractionState activateInteractionState => m_ActivateInteractionState;
InteractionState m_UIPressInteractionState;
///
/// (Read Only) The current UI press interaction state.
///
public InteractionState uiPressInteractionState => m_UIPressInteractionState;
Vector2 m_UIScrollValue;
///
/// (Read Only) The current UI scroll value.
///
public Vector2 uiScrollValue => m_UIScrollValue;
XRControllerState m_ControllerState;
///
/// The current state of the controller.
///
public XRControllerState currentControllerState
{
get
{
SetupControllerState();
return m_ControllerState;
}
set
{
m_ControllerState = value;
m_CreateControllerState = false;
}
}
bool m_CreateControllerState = true;
#if ANIMATION_MODULE_PRESENT
///
/// The on .
///
Animator m_ModelAnimator;
#endif
bool m_HasWarnedAnimatorMissing;
///
/// A boolean value that indicates setup should be performed on Update.
///
bool m_PerformSetup = true;
HapticImpulseChannel m_HapticChannel;
HapticImpulseSingleChannelGroup m_HapticChannelGroup;
///
/// See .
///
protected virtual void Awake()
{
// Create empty container transform for the model if none specified.
// This is not strictly necessary to create since this GameObject could be used
// as the parent for the instantiated prefab, but doing so anyway for backwards compatibility.
if (m_ModelParent == null)
{
m_ModelParent = new GameObject($"[{gameObject.name}] Model Parent").transform;
m_ModelParent.SetParent(transform, false);
m_ModelParent.SetLocalPose(Pose.identity);
}
}
///
/// See .
///
protected virtual void OnEnable()
{
Application.onBeforeRender += OnBeforeRender;
}
///
/// See .
///
protected virtual void OnDisable()
{
Application.onBeforeRender -= OnBeforeRender;
}
///
/// See .
///
protected void Update()
{
UpdateController();
}
void SetupModel()
{
if (m_Model == null)
{
var prefab = GetModelPrefab();
if (prefab != null)
m_Model = Instantiate(prefab, m_ModelParent).transform;
}
if (m_Model != null)
m_Model.gameObject.SetActive(!m_HideControllerModel);
}
void SetupControllerState()
{
if (m_ControllerState == null && m_CreateControllerState)
m_ControllerState = new XRControllerState();
}
///
/// Gets the prefab that should be instantiated upon startup.
///
/// Returns the prefab that should be instantiated upon startup.
protected virtual GameObject GetModelPrefab()
{
return m_ModelPrefab != null ? m_ModelPrefab.gameObject : null;
}
///
/// Updates the controller every frame.
/// This is called automatically from .
///
protected virtual void UpdateController()
{
if (m_PerformSetup)
{
SetupModel();
SetupControllerState();
m_PerformSetup = false;
}
if (m_EnableInputTracking &&
(m_UpdateTrackingType == UpdateType.Update ||
m_UpdateTrackingType == UpdateType.UpdateAndBeforeRender))
{
UpdateTrackingInput(m_ControllerState);
}
if (m_EnableInputActions)
{
UpdateInput(m_ControllerState);
UpdateControllerModelAnimation();
}
ApplyControllerState(XRInteractionUpdateOrder.UpdatePhase.Dynamic, m_ControllerState);
}
///
/// This method is automatically called for "Just Before Render" input updates for VR devices.
///
///
protected virtual void OnBeforeRender()
{
if (m_EnableInputTracking &&
(m_UpdateTrackingType == UpdateType.BeforeRender ||
m_UpdateTrackingType == UpdateType.UpdateAndBeforeRender))
{
UpdateTrackingInput(m_ControllerState);
}
ApplyControllerState(XRInteractionUpdateOrder.UpdatePhase.OnBeforeRender, m_ControllerState);
}
///
/// Corresponds to . It has the frequency of the physics system and is called
/// every fixed framerate frame.
///
protected virtual void FixedUpdate()
{
if (m_EnableInputTracking && m_UpdateTrackingType == UpdateType.Fixed)
{
UpdateTrackingInput(m_ControllerState);
}
ApplyControllerState(XRInteractionUpdateOrder.UpdatePhase.Fixed, m_ControllerState);
}
///
/// Applies the given controller state to this .
/// Depending on the update phase, the XR Interaction states may be copied
/// and/or the pose value may be applied to the transform of the GameObject.
/// Unity calls this automatically from , , and .
///
/// The update phase during this call.
/// The state of the controller to apply.
protected virtual void ApplyControllerState(XRInteractionUpdateOrder.UpdatePhase updatePhase, XRControllerState controllerState)
{
if (controllerState == null)
return;
if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic)
{
// Sync the controller actions from the interaction state in the controller
m_SelectInteractionState = controllerState.selectInteractionState;
m_ActivateInteractionState = controllerState.activateInteractionState;
m_UIPressInteractionState = controllerState.uiPressInteractionState;
m_UIScrollValue = controllerState.uiScrollValue;
}
if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic ||
updatePhase == XRInteractionUpdateOrder.UpdatePhase.OnBeforeRender ||
updatePhase == XRInteractionUpdateOrder.UpdatePhase.Fixed)
{
var hasPosition = (controllerState.inputTrackingState & InputTrackingState.Position) != 0;
var hasRotation = (controllerState.inputTrackingState & InputTrackingState.Rotation) != 0;
if (hasPosition && hasRotation)
transform.SetLocalPose(new Pose(controllerState.position, controllerState.rotation));
else if (hasPosition)
transform.localPosition = controllerState.position;
else if (hasRotation)
transform.localRotation = controllerState.rotation;
}
}
///
/// Updates the pose values in the given controller state based on the current tracking input of the controller device.
/// Unity calls this automatically from , , and so explicit calls
/// to this function are not required.
///
/// The state of the controller.
protected virtual void UpdateTrackingInput(XRControllerState controllerState)
{
}
///
/// Updates the XR Interaction states in the given controller state based on the current inputs of the controller device.
/// Unity calls this automatically during so explicit calls to this function are not required.
///
/// The state of the controller.
protected virtual void UpdateInput(XRControllerState controllerState)
{
}
///
/// Updates the animation on the model instance (if the model contains an ).
/// Unity calls this automatically from .
///
///
///
///
protected virtual void UpdateControllerModelAnimation()
{
#if ANIMATION_MODULE_PRESENT
if (m_AnimateModel && m_Model != null)
{
// Update the Animator reference if necessary
if (m_ModelAnimator == null || m_ModelAnimator.gameObject != m_Model.gameObject)
{
if (!m_Model.TryGetComponent(out m_ModelAnimator))
{
if (!m_HasWarnedAnimatorMissing)
{
Debug.LogWarning("Animate Model is enabled, but there is no Animator component on the model." +
" Unable to activate named triggers to animate the model.", this);
m_HasWarnedAnimatorMissing = true;
}
return;
}
}
if (m_SelectInteractionState.activatedThisFrame)
m_ModelAnimator.SetTrigger(m_ModelSelectTransition);
else if (m_SelectInteractionState.deactivatedThisFrame)
m_ModelAnimator.SetTrigger(m_ModelDeSelectTransition);
}
#endif
}
///
/// Play a haptic impulse on the controller if one is available.
///
/// Amplitude (from 0.0 to 1.0) to play impulse at.
/// Duration (in seconds) to play haptic impulse.
/// Returns if successful. Otherwise, returns .
public virtual bool SendHapticImpulse(float amplitude, float duration) => false;
///
IXRHapticImpulseChannelGroup IXRHapticImpulseProvider.GetChannelGroup()
{
m_HapticChannel ??= new HapticImpulseChannel(this);
return m_HapticChannelGroup ??= new HapticImpulseSingleChannelGroup(m_HapticChannel);
}
class HapticImpulseChannel : IXRHapticImpulseChannel
{
readonly XRBaseController m_Controller;
bool m_WarningLogged;
public HapticImpulseChannel(XRBaseController controller)
{
m_Controller = controller;
}
///
public bool SendHapticImpulse(float amplitude, float duration, float frequency)
{
if (frequency > 0f && !m_WarningLogged)
{
Debug.LogWarning($"Frequency is not supported when using {m_Controller} as the haptic impulse channel." +
$" You may need to update the {nameof(HapticImpulsePlayer)} to use an Input Action Reference with a Haptic control binding" +
" rather than using an Object Reference to the controller.", m_Controller);
m_WarningLogged = true;
}
return m_Controller.SendHapticImpulse(amplitude, duration);
}
}
}
}