using UnityEngine.Events; using UnityEngine.XR.Interaction.Toolkit; namespace UnityEngine.XR.Content.Interaction { /// /// An interactable lever that snaps into an on or off position by a direct interactor /// public class XRLever : XRBaseInteractable { const float k_LeverDeadZone = 0.1f; // Prevents rapid switching between on and off states when right in the middle [SerializeField] [Tooltip("The object that is visually grabbed and manipulated")] Transform m_Handle = null; [SerializeField] [Tooltip("The value of the lever")] bool m_Value = false; [SerializeField] [Tooltip("If enabled, the lever will snap to the value position when released")] bool m_LockToValue; [SerializeField] [Tooltip("Angle of the lever in the 'on' position")] [Range(-90.0f, 90.0f)] float m_MaxAngle = 90.0f; [SerializeField] [Tooltip("Angle of the lever in the 'off' position")] [Range(-90.0f, 90.0f)] float m_MinAngle = -90.0f; [SerializeField] [Tooltip("Events to trigger when the lever activates")] UnityEvent m_OnLeverActivate = new UnityEvent(); [SerializeField] [Tooltip("Events to trigger when the lever deactivates")] UnityEvent m_OnLeverDeactivate = new UnityEvent(); IXRSelectInteractor m_Interactor; /// /// The object that is visually grabbed and manipulated /// public Transform handle { get => m_Handle; set => m_Handle = value; } /// /// The value of the lever /// public bool value { get => m_Value; set => SetValue(value, true); } /// /// If enabled, the lever will snap to the value position when released /// public bool lockToValue { get; set; } /// /// Angle of the lever in the 'on' position /// public float maxAngle { get => m_MaxAngle; set => m_MaxAngle = value; } /// /// Angle of the lever in the 'off' position /// public float minAngle { get => m_MinAngle; set => m_MinAngle = value; } /// /// Events to trigger when the lever activates /// public UnityEvent onLeverActivate => m_OnLeverActivate; /// /// Events to trigger when the lever deactivates /// public UnityEvent onLeverDeactivate => m_OnLeverDeactivate; void Start() { SetValue(m_Value, true); } protected override void OnEnable() { base.OnEnable(); selectEntered.AddListener(StartGrab); selectExited.AddListener(EndGrab); } protected override void OnDisable() { selectEntered.RemoveListener(StartGrab); selectExited.RemoveListener(EndGrab); base.OnDisable(); } void StartGrab(SelectEnterEventArgs args) { m_Interactor = args.interactorObject; } void EndGrab(SelectExitEventArgs args) { SetValue(m_Value, true); m_Interactor = null; } public override void ProcessInteractable(XRInteractionUpdateOrder.UpdatePhase updatePhase) { base.ProcessInteractable(updatePhase); if (updatePhase == XRInteractionUpdateOrder.UpdatePhase.Dynamic) { if (isSelected) { UpdateValue(); } } } Vector3 GetLookDirection() { Vector3 direction = m_Interactor.GetAttachTransform(this).position - m_Handle.position; direction = transform.InverseTransformDirection(direction); direction.x = 0; return direction.normalized; } void UpdateValue() { var lookDirection = GetLookDirection(); var lookAngle = Mathf.Atan2(lookDirection.z, lookDirection.y) * Mathf.Rad2Deg; if (m_MinAngle < m_MaxAngle) lookAngle = Mathf.Clamp(lookAngle, m_MinAngle, m_MaxAngle); else lookAngle = Mathf.Clamp(lookAngle, m_MaxAngle, m_MinAngle); var maxAngleDistance = Mathf.Abs(m_MaxAngle - lookAngle); var minAngleDistance = Mathf.Abs(m_MinAngle - lookAngle); if (m_Value) maxAngleDistance *= (1.0f - k_LeverDeadZone); else minAngleDistance *= (1.0f - k_LeverDeadZone); var newValue = (maxAngleDistance < minAngleDistance); SetHandleAngle(lookAngle); SetValue(newValue); } void SetValue(bool isOn, bool forceRotation = false) { if (m_Value == isOn) { if (forceRotation) SetHandleAngle(m_Value ? m_MaxAngle : m_MinAngle); return; } m_Value = isOn; if (m_Value) { m_OnLeverActivate.Invoke(); } else { m_OnLeverDeactivate.Invoke(); } if (!isSelected && (m_LockToValue || forceRotation)) SetHandleAngle(m_Value ? m_MaxAngle : m_MinAngle); } void SetHandleAngle(float angle) { if (m_Handle != null) m_Handle.localRotation = Quaternion.Euler(angle, 0.0f, 0.0f); } void OnDrawGizmosSelected() { var angleStartPoint = transform.position; if (m_Handle != null) angleStartPoint = m_Handle.position; const float k_AngleLength = 0.25f; var angleMaxPoint = angleStartPoint + transform.TransformDirection(Quaternion.Euler(m_MaxAngle, 0.0f, 0.0f) * Vector3.up) * k_AngleLength; var angleMinPoint = angleStartPoint + transform.TransformDirection(Quaternion.Euler(m_MinAngle, 0.0f, 0.0f) * Vector3.up) * k_AngleLength; Gizmos.color = Color.green; Gizmos.DrawLine(angleStartPoint, angleMaxPoint); Gizmos.color = Color.red; Gizmos.DrawLine(angleStartPoint, angleMinPoint); } void OnValidate() { SetHandleAngle(m_Value ? m_MaxAngle : m_MinAngle); } } }