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