using System; using UnityEngine.InputSystem.LowLevel; namespace UnityEngine.InputSystem.XR { /// /// The component applies the current pose value of a tracked device /// to the of the . /// can track multiple types of devices including XR HMDs, controllers, and remotes. /// /// /// For and , if an action is directly defined /// in the , as opposed to a reference to an action externally defined /// in an , the action will automatically be enabled and disabled by this /// behavior during and . The enabled state for actions /// externally defined must be managed externally from this behavior. /// [Serializable] [AddComponentMenu("XR/Tracked Pose Driver (Input System)")] public class TrackedPoseDriver : MonoBehaviour, ISerializationCallbackReceiver { /// /// Options for which properties to update. /// /// public enum TrackingType { /// /// Update both rotation and position. /// RotationAndPosition, /// /// Update rotation only. /// RotationOnly, /// /// Update position only. /// PositionOnly, } [SerializeField] TrackingType m_TrackingType; /// /// The tracking type being used by the Tracked Pose Driver /// to control which properties to update. /// /// public TrackingType trackingType { get => m_TrackingType; set => m_TrackingType = value; } /// /// Options for which phases of the player loop will update properties. /// /// public enum UpdateType { /// /// Update after the Input System has completed an update and right before rendering. /// /// /// UpdateAndBeforeRender, /// /// Update after the Input System has completed an update. /// /// Update, /// /// Update right before rendering. /// /// BeforeRender, } [SerializeField] UpdateType m_UpdateType = UpdateType.UpdateAndBeforeRender; /// /// The update type being used by the Tracked Pose Driver /// to control which phases of the player loop will update properties. /// /// public UpdateType updateType { get => m_UpdateType; set => m_UpdateType = value; } [SerializeField] InputActionProperty m_PositionInput; /// /// The action to read the position value of a tracked device. /// Must support reading a value of type . /// public InputActionProperty positionInput { get => m_PositionInput; set { if (Application.isPlaying) UnbindPosition(); m_PositionInput = value; if (Application.isPlaying && isActiveAndEnabled) BindPosition(); } } [SerializeField] InputActionProperty m_RotationInput; /// /// The action to read the rotation value of a tracked device. /// Must support reading a value of type . /// public InputActionProperty rotationInput { get => m_RotationInput; set { if (Application.isPlaying) UnbindRotation(); m_RotationInput = value; if (Application.isPlaying && isActiveAndEnabled) BindRotation(); } } Vector3 m_CurrentPosition = Vector3.zero; Quaternion m_CurrentRotation = Quaternion.identity; bool m_RotationBound; bool m_PositionBound; void BindActions() { BindPosition(); BindRotation(); } void BindPosition() { if (m_PositionBound) return; var action = m_PositionInput.action; if (action == null) return; action.performed += OnPositionPerformed; action.canceled += OnPositionCanceled; m_PositionBound = true; if (m_PositionInput.reference == null) { action.Rename($"{gameObject.name} - TPD - Position"); action.Enable(); } } void BindRotation() { if (m_RotationBound) return; var action = m_RotationInput.action; if (action == null) return; action.performed += OnRotationPerformed; action.canceled += OnRotationCanceled; m_RotationBound = true; if (m_RotationInput.reference == null) { action.Rename($"{gameObject.name} - TPD - Rotation"); action.Enable(); } } void UnbindActions() { UnbindPosition(); UnbindRotation(); } void UnbindPosition() { if (!m_PositionBound) return; var action = m_PositionInput.action; if (action == null) return; if (m_PositionInput.reference == null) action.Disable(); action.performed -= OnPositionPerformed; action.canceled -= OnPositionCanceled; m_PositionBound = false; } void UnbindRotation() { if (!m_RotationBound) return; var action = m_RotationInput.action; if (action == null) return; if (m_RotationInput.reference == null) action.Disable(); action.performed -= OnRotationPerformed; action.canceled -= OnRotationCanceled; m_RotationBound = false; } void OnPositionPerformed(InputAction.CallbackContext context) { Debug.Assert(m_PositionBound, this); m_CurrentPosition = context.ReadValue(); } void OnPositionCanceled(InputAction.CallbackContext context) { Debug.Assert(m_PositionBound, this); m_CurrentPosition = Vector3.zero; } void OnRotationPerformed(InputAction.CallbackContext context) { Debug.Assert(m_RotationBound, this); m_CurrentRotation = context.ReadValue(); } void OnRotationCanceled(InputAction.CallbackContext context) { Debug.Assert(m_RotationBound, this); m_CurrentRotation = Quaternion.identity; } /// /// This function is called when the script instance is being loaded. /// protected virtual void Awake() { #if UNITY_INPUT_SYSTEM_ENABLE_VR && ENABLE_VR if (HasStereoCamera()) { UnityEngine.XR.XRDevice.DisableAutoXRCameraTracking(GetComponent(), true); } #endif } /// /// This function is called when the object becomes enabled and active. /// protected void OnEnable() { InputSystem.onAfterUpdate += UpdateCallback; BindActions(); } /// /// This function is called when the object becomes disabled or inactive. /// protected void OnDisable() { UnbindActions(); InputSystem.onAfterUpdate -= UpdateCallback; } /// /// This function is called when the will be destroyed. /// protected virtual void OnDestroy() { #if UNITY_INPUT_SYSTEM_ENABLE_VR && ENABLE_VR if (HasStereoCamera()) { UnityEngine.XR.XRDevice.DisableAutoXRCameraTracking(GetComponent(), false); } #endif } protected void UpdateCallback() { if (InputState.currentUpdateType == InputUpdateType.BeforeRender) OnBeforeRender(); else OnUpdate(); } protected virtual void OnUpdate() { if (m_UpdateType == UpdateType.Update || m_UpdateType == UpdateType.UpdateAndBeforeRender) { PerformUpdate(); } } protected virtual void OnBeforeRender() { if (m_UpdateType == UpdateType.BeforeRender || m_UpdateType == UpdateType.UpdateAndBeforeRender) { PerformUpdate(); } } protected virtual void SetLocalTransform(Vector3 newPosition, Quaternion newRotation) { if (m_TrackingType == TrackingType.RotationAndPosition || m_TrackingType == TrackingType.RotationOnly) { transform.localRotation = newRotation; } if (m_TrackingType == TrackingType.RotationAndPosition || m_TrackingType == TrackingType.PositionOnly) { transform.localPosition = newPosition; } } bool HasStereoCamera() { var cameraComponent = GetComponent(); return cameraComponent != null && cameraComponent.stereoEnabled; } protected virtual void PerformUpdate() { SetLocalTransform(m_CurrentPosition, m_CurrentRotation); } #region DEPRECATED // Disable warnings that these fields are never assigned to. They are set during Unity deserialization and migrated. // ReSharper disable UnassignedField.Local #pragma warning disable 0649 [Obsolete] [SerializeField, HideInInspector] InputAction m_PositionAction; public InputAction positionAction { get => m_PositionInput.action; set => positionInput = new InputActionProperty(value); } [Obsolete] [SerializeField, HideInInspector] InputAction m_RotationAction; public InputAction rotationAction { get => m_RotationInput.action; set => rotationInput = new InputActionProperty(value); } #pragma warning restore 0649 // ReSharper restore UnassignedField.Local /// /// Stores whether the fields of type have been migrated to fields of type . /// [SerializeField, HideInInspector] bool m_HasMigratedActions; /// /// This function is called when the user hits the Reset button in the Inspector's context menu /// or when adding the component the first time. This function is only called in editor mode. /// protected void Reset() { m_HasMigratedActions = true; } /// void ISerializationCallbackReceiver.OnBeforeSerialize() { } /// void ISerializationCallbackReceiver.OnAfterDeserialize() { if (m_HasMigratedActions) return; #pragma warning disable 0612 m_PositionInput = new InputActionProperty(m_PositionAction); m_RotationInput = new InputActionProperty(m_RotationAction); m_HasMigratedActions = true; #pragma warning restore 0612 } #endregion } }