// -------------------------------------------------------------------------------------------------------------------- // // Copyright (c) VRMADA, All rights reserved. // // -------------------------------------------------------------------------------------------------------------------- using UltimateXR.Core.Components; using UltimateXR.Manipulation; using UltimateXR.Manipulation.Helpers; using UnityEngine; namespace UltimateXR.Examples.FullScene.Lab { /// /// Component that handles the generator door, which has battery locks to lock the battery in place. /// public class GeneratorDoor : UxrComponent { #region Inspector Properties/Serialized Fields [SerializeField] private UxrAutoSlideInAnchor _batteryAnchor; [SerializeField] private UxrGrabbableObject _grabbableLock; [SerializeField] private Transform[] _locks; [SerializeField] private float _lockHandleAngleClosed; [SerializeField] private float _lockHandleAngleOpen; [SerializeField] private bool _startLockOpen = true; #endregion #region Public Types & Data /// /// Gets whether the battery lock is open. /// public bool IsLockOpen { get => LockHandleOpenValue > 0.5f; private set { // Set rotation using the correct property to avoid interference between grabbable object constraint calculation and manually setting its transform. _grabbableLock.SingleRotationAxisDegrees = value ? _lockHandleAngleOpen : _lockHandleAngleClosed; for (int i = 0; i < _locks.Length; ++i) { _locks[i].transform.localRotation = _lockInitialRotation[i] * Quaternion.AngleAxis((value ? 1.0f : 0.0f) * (_lockHandleAngleOpen - _lockHandleAngleClosed), Vector3.right); } } } /// /// Gets whether there is a battery placed inside the generator and it is currently in contact with the bottom. This /// allows to switch things on when the battery is actually in contact instead of being placed, because there is a /// slide-in animation after the battery has been placed. /// public bool IsBatteryInContact { get; set; } #endregion #region Unity /// /// Initializes the component. /// protected override void Awake() { base.Awake(); _lockInitialRotation = new Quaternion[_locks.Length]; for (int i = 0; i < _locks.Length; ++i) { _lockInitialRotation[i] = _locks[i].localRotation; } } /// /// Subscribes to the events that help model the behavior. /// protected override void OnEnable() { base.OnEnable(); _batteryAnchor.Anchor.Placed += Battery_Placed; _batteryAnchor.Anchor.Removed += Battery_Removed; _grabbableLock.ConstraintsApplied += Lock_ConstraintsApplied; } /// /// Unsubscribes from the events. /// protected override void OnDisable() { base.OnDisable(); _batteryAnchor.Anchor.Placed -= Battery_Placed; _batteryAnchor.Anchor.Removed -= Battery_Removed; _grabbableLock.ConstraintsApplied -= Lock_ConstraintsApplied; } /// /// Initializes the lock open state. /// protected override void Start() { base.Start(); IsBatteryInContact = _batteryAnchor.Anchor.CurrentPlacedObject != null; IsLockOpen = _startLockOpen; } /// /// Updates the ability of the door to let a battery be placed inside. /// private void Update() { // The battery door anchor is disabled if the lock is closed and there is no battery inside if (_batteryAnchor.Anchor.CurrentPlacedObject == null && !IsLockOpen) { _batteryAnchor.enabled = false; } else { _batteryAnchor.enabled = true; } } #endregion #region Event Handling Methods /// /// Called after the lock has finished being manipulated so that additional constraints can be applied to it. /// /// Event sender /// Event parameters private void Lock_ConstraintsApplied(object sender, UxrApplyConstraintsEventArgs e) { float lockHandleOpenValue = LockHandleOpenValue; float locksOpenValue = 1.0f - (1.0f - lockHandleOpenValue) * (1.0f - lockHandleOpenValue); // Update small locks based on the main lock open value for (int i = 0; i < _locks.Length; ++i) { _locks[i].transform.localRotation = _lockInitialRotation[i] * Quaternion.AngleAxis(locksOpenValue * (_lockHandleAngleOpen - _lockHandleAngleClosed), Vector3.right); } // Main lock can be manipulated only while the battery is completely inside or there is no battery if (_batteryAnchor.Anchor.CurrentPlacedObject != null && _batteryAnchor.Anchor.CurrentPlacedObject.transform.localPosition.z > 0.01f) { _grabbableLock.IsLockedInPlace = true; } else { _grabbableLock.IsLockedInPlace = false; } } /// /// Called right after a battery was placed inside. /// /// Event sender /// Event parameters private void Battery_Placed(object sender, UxrManipulationEventArgs e) { // In order to make the lights turn on only when the battery reached the bottom, we control this from the Battery component. } /// /// Called right after the battery was removed. /// /// Event sender /// Event parameters private void Battery_Removed(object sender, UxrManipulationEventArgs e) { IsBatteryInContact = false; } #endregion #region Private Types & Data /// /// Returns a value between 0.0 and 1.0 telling how open the lock is. /// private float LockHandleOpenValue { get { float lockHandleOpenValue = Mathf.Clamp01((_grabbableLock.transform.localRotation.eulerAngles.z - _lockHandleAngleClosed) / (_lockHandleAngleOpen - _lockHandleAngleClosed)); return lockHandleOpenValue; } } private bool _isBatteryInContact; private Quaternion[] _lockInitialRotation; #endregion } }