namespace Tilia.Interactions.PointerInteractors { using System.Collections; using System.Collections.Generic; using Tilia.Indicators.ObjectPointers; using Tilia.Interactions.Interactables.Interactables; using Tilia.Interactions.Interactables.Interactables.Grab.Action; using Tilia.Interactions.Interactables.Interactors; using UnityEngine; using Zinnia.Action; using Zinnia.Data.Attribute; using Zinnia.Data.Type; using Zinnia.Event.Proxy; using Zinnia.Extension; using Zinnia.Rule.Collection; using Zinnia.Tracking.Modification; using Zinnia.Utility; /// /// Sets up the DistanceGrabber Prefab based on the provided user settings. /// public class DistanceGrabberConfigurator : MonoBehaviour { #region Facade Settings [Header("Facade Settings")] [Tooltip("The public facade.")] [SerializeField] [Restricted] private DistanceGrabberFacade facade; /// /// The public facade. /// public DistanceGrabberFacade Facade { get { return facade; } set { facade = value; } } #endregion #region Functionality Settings [Header("Functionality Settings")] [Tooltip("Determines whether the grab point will be created always or only if the InteractableFacade has a follow action that is set to GrabInteractableFollowAction.OffsetType.PrecisionPoint.")] [SerializeField] private bool alwaysCreateGrabPoint; /// /// Determines whether the grab point will be created always or only if the has a follow action that is set to . /// public bool AlwaysCreateGrabPoint { get { return alwaysCreateGrabPoint; } set { alwaysCreateGrabPoint = value; } } [Tooltip("Forces the InteractableFacade.InteractableRigidbody to be kinematic during the transition.")] [SerializeField] private bool forceKinematicOnTransition = true; /// /// Forces the to be kinematic during the transition. /// public bool ForceKinematicOnTransition { get { return forceKinematicOnTransition; } set { forceKinematicOnTransition = value; } } [Tooltip("Whether to disable the pointer logic when the Facade.Interactor touches the InteractableFacade.")] [SerializeField] private bool disablePointerOnInteractorTouch = true; /// /// Whether to disable the pointer logic when the touches the . /// public bool DisablePointerOnInteractorTouch { get { return disablePointerOnInteractorTouch; } set { disablePointerOnInteractorTouch = value; } } #endregion #region Reference Settings [Header("Reference Settings")] [Tooltip("The containing GameObject of the pointer logic.")] [SerializeField] [Restricted] private GameObject pointerContainer; /// /// The containing of the pointer logic. /// public GameObject PointerContainer { get { return pointerContainer; } set { pointerContainer = value; } } [Tooltip("The PointerFacade to initiate the grabbing.")] [SerializeField] [Restricted] private PointerFacade pointer; /// /// The to initiate the grabbing. /// public PointerFacade Pointer { get { return pointer; } set { pointer = value; } } [Tooltip("The InteractableGrabber that initiates the grabbing.")] [SerializeField] [Restricted] private InteractableGrabber grabber; /// /// The that initiates the grabbing. /// public InteractableGrabber Grabber { get { return grabber; } set { grabber = value; } } [Tooltip("The TransformPropertyApplier that transitions the Interactable to the Interactor.")] [SerializeField] [Restricted] private TransformPropertyApplier propertyApplier; /// /// The that transitions the Interactable to the Interactor. /// public TransformPropertyApplier PropertyApplier { get { return propertyApplier; } set { propertyApplier = value; } } [Tooltip("The EmptyEventProxyEmitter that runs the grab logic.")] [SerializeField] [Restricted] private EmptyEventProxyEmitter grabListener; /// /// The that runs the grab logic. /// public EmptyEventProxyEmitter GrabListener { get { return grabListener; } set { grabListener = value; } } [Tooltip("The EmptyEventProxyEmitter that runs the ungrab logic.")] [SerializeField] [Restricted] private EmptyEventProxyEmitter ungrabListener; /// /// The that runs the ungrab logic. /// public EmptyEventProxyEmitter UngrabListener { get { return ungrabListener; } set { ungrabListener = value; } } [Tooltip("The BooleanAction that proxies the Interactor's grab action.")] [SerializeField] [Restricted] private BooleanAction grabProxy; /// /// The that proxies the Interactor's grab action. /// public BooleanAction GrabProxy { get { return grabProxy; } set { grabProxy = value; } } [Tooltip("The EmptyEventProxyEmitter that is executed on the Interactor's grab action.")] [SerializeField] [Restricted] private EmptyEventProxyEmitter grabProxyActions; /// /// The that is executed on the Interactor's grab action. /// public EmptyEventProxyEmitter GrabProxyActions { get { return grabProxyActions; } set { grabProxyActions = value; } } [Tooltip("The CountdownTimer that controls when the pointer is reactivated.")] [SerializeField] [Restricted] private CountdownTimer reactivatePointerTimer; /// /// The that controls when the pointer is reactivated. /// public CountdownTimer ReactivatePointerTimer { get { return reactivatePointerTimer; } set { reactivatePointerTimer = value; } } [Tooltip("The container for the logic that enables the pointer.")] [SerializeField] [Restricted] private GameObject enablePointerContainer; /// /// The container for the logic that enables the pointer. /// public GameObject EnablePointerContainer { get { return enablePointerContainer; } set { enablePointerContainer = value; } } [Tooltip("The logic that enables the pointer.")] [SerializeField] [Restricted] private EmptyEventProxyEmitter enablePointerLogic; /// /// The logic that enables the pointer. /// public EmptyEventProxyEmitter EnablePointerLogic { get { return enablePointerLogic; } set { enablePointerLogic = value; } } [Tooltip("The RuleContainerObservableList that controls pointer target validity.")] [SerializeField] [Restricted] private RuleContainerObservableList targetValidityRules; /// /// The that controls pointer target validity. /// public RuleContainerObservableList TargetValidityRules { get { return targetValidityRules; } set { targetValidityRules = value; } } [Tooltip("An InteractorFacade that is used to simulate touch events with the pointer.")] [SerializeField] [Restricted] private InteractorFacade simulatedInteractor; /// /// An that is used to simulate touch events with the pointer. /// public InteractorFacade SimulatedInteractor { get { return simulatedInteractor; } set { simulatedInteractor = value; } } [Tooltip("The container for the simulate touch logic.")] [SerializeField] [Restricted] private GameObject simulateTouchContainer; /// /// The container for the simulate touch logic. /// public GameObject SimulateTouchContainer { get { return simulateTouchContainer; } set { simulateTouchContainer = value; } } [Tooltip("The container for the simulate grab logic.")] [SerializeField] [Restricted] private GameObject simulateGrabContainer; /// /// The container for the simulate grab logic. /// public GameObject SimulateGrabContainer { get { return simulateGrabContainer; } set { simulateGrabContainer = value; } } #endregion /// /// Whether to ignore the re-enabling of the pointer logic when the untouches. /// public bool ShouldIgnoreEnablePointer { get; set; } /// /// Whether the Interactor events have been subscribed to. /// protected bool hasSubscribedToInteractorEvents; /// /// The point at which to use as the grab point offset for the transition. /// protected GameObject grabPoint; /// /// The kinematic state of the before the transition period. /// protected bool cachedKinematicState; /// /// The current grab precognition value for the associated Interactor. /// protected float cachedInteractorPrecognitionValue; /// /// A coroutine for managing the if interactable is active check. /// protected Coroutine checkInteractableActiveRoutine; /// /// A global static list of for look up. /// public static List ValidConfigurators = new List(); /// /// Configures the relevant components that require knowledge of the Interactor. /// public virtual void ConfigureInteractor() { Pointer.FollowSource = Facade.FollowSource != null ? Facade.FollowSource : Facade.Interactor.gameObject; Grabber.Interactor = Facade.Interactor; PropertyApplier.SetSource(Facade.Interactor.gameObject); GrabProxy.RunWhenActiveAndEnabled(() => GrabProxy.ClearSources()); GrabProxy.RunWhenActiveAndEnabled(() => GrabProxy.AddSource(Facade.Interactor.GrabAction)); UnregisterInteractorListeners(); RegisterInteractorListeners(); } /// /// Configures the property applier. /// public virtual void ConfigurePropertyApplier() { PropertyApplier.TransitionDuration = Facade.TransitionDuration.ApproxEquals(0f) ? 0.0001f : Facade.TransitionDuration; } /// /// Configures the reactivate pointer timer. /// public virtual void ConfigureReactivatePointerTimer() { ReactivatePointerTimer.StartTime = Facade.UngrabDelay; } /// /// Configures the rules on the pointer. /// public virtual void ConfigurePointerRules() { if (TargetValidityRules.NonSubscribableElements.Count > 1) { TargetValidityRules.RunWhenActiveAndEnabled(() => TargetValidityRules.RemoveAt(1)); } if (Facade.TargetValidity.Interface != null) { TargetValidityRules.RunWhenActiveAndEnabled(() => TargetValidityRules.Add(Facade.TargetValidity)); } Pointer.RaycastRules = Facade.RaycastRules; } /// /// Creates the grab point offset to transition the target Interactable towards. /// /// The data to create the point with. public virtual void CreateGrabPoint(RaycastHit hitData) { InteractableFacade interactable = hitData.transform.gameObject.TryGetComponent(true, true); if (interactable == null) { return; } DestroyGrabPoint(); if (AlwaysCreateGrabPoint || CanCreateGrabPoint(interactable.Configuration.GrabConfiguration.PrimaryAction) || CanCreateGrabPoint(interactable.Configuration.GrabConfiguration.SecondaryAction)) { grabPoint = new GameObject($"[Zinnia][GrabPointContainer][{hitData.transform.name}]"); grabPoint.transform.position = hitData.point; grabPoint.transform.SetParent(hitData.transform); PropertyApplier.Offset = grabPoint; } } /// /// Destroys any existing grab point. /// public virtual void DestroyGrabPoint() { PropertyApplier.Offset = null; Destroy(grabPoint); } /// /// Makes the kinematic. /// public virtual void MakeInteractableKinematic() { if (Facade.CurrentInteractable == null || !ForceKinematicOnTransition) { return; } cachedKinematicState = Facade.CurrentInteractable.InteractableRigidbody.isKinematic; Facade.CurrentInteractable.InteractableRigidbody.isKinematic = true; } /// /// Restores the kinematic state to the value of . /// public virtual void RestoreCachedInteractableKinematicState() { if (Facade == null || Facade.CurrentInteractable == null || !ForceKinematicOnTransition) { return; } Facade.CurrentInteractable.InteractableRigidbody.isKinematic = cachedKinematicState; cachedKinematicState = Facade.CurrentInteractable.InteractableRigidbody.isKinematic; } /// /// Emits the event. /// /// The payload to emit with. public virtual void NotifyBeforeGrabbed(InteractableFacade interactable) { if (interactable == null) { return; } cachedInteractorPrecognitionValue = Facade.Interactor.GrabPrecognition; Facade.Interactor.GrabPrecognition = 0f; foreach (DistanceGrabberConfigurator configurator in ValidConfigurators) { if (!configurator.Equals(this)) { configurator.GrabProxyActions.Receive(); } } interactable.Ungrab(); Facade.BeforeGrabbed?.Invoke(interactable); } /// /// Emits the event. /// public virtual void NotifyGrabCanceled() { if (Grabber == null || Grabber.Interactable == null) { return; } RestoreInteractorPrecognitionValue(); Facade.GrabCanceled?.Invoke(Grabber.Interactable); } /// /// Emits the event. /// /// The payload to emit with. public virtual void NotifyAfterGrabbed(InteractableFacade interactable) { if (interactable == null) { return; } RestoreInteractorPrecognitionValue(); Facade.AfterGrabbed?.Invoke(interactable); } /// /// Simulates a touch on the given Interactable. /// /// The Interactable to simulate the touch on. public virtual void SimulateTouch(InteractableFacade interactable) { if (interactable == null || !interactable.TouchEnabled) { return; } interactable.Configuration.TouchConfiguration.NotifyTouch(SimulatedInteractor.gameObject); } /// /// Simulates an untouch on the given Interactable. /// /// The Interactable to simulate the untouch on. public virtual void SimulateUntouch(InteractableFacade interactable) { if (interactable == null || !interactable.TouchEnabled) { return; } interactable.Configuration.TouchConfiguration.NotifyUntouch(SimulatedInteractor.gameObject); } public virtual void CheckGrabValidity(SurfaceData data) { if (data == null || data.CollisionData.transform == null) { return; } InteractableFacade interactable = data.CollisionData.transform.GetComponent(); if (interactable != null && !interactable.GrabEnabled) { SimulateGrabContainer.SetActive(false); } } protected virtual void OnEnable() { ConfigureInteractor(); ConfigurePropertyApplier(); ConfigureReactivatePointerTimer(); ConfigurePointerRules(); if (!ValidConfigurators.Contains(this)) { ValidConfigurators.Add(this); } } protected virtual IEnumerator Start() { yield return new WaitForEndOfFrame(); OnEnable(); } protected virtual void OnDisable() { RestoreInteractorPrecognitionValue(); RestoreCachedInteractableKinematicState(); UnregisterInteractorListeners(); ValidConfigurators.Remove(this); } /// /// Restores the cached Interactor precognition value. /// protected virtual void RestoreInteractorPrecognitionValue() { if (Facade == null || Facade.Interactor == null) { return; } Facade.Interactor.GrabPrecognition = cachedInteractorPrecognitionValue; cachedInteractorPrecognitionValue = Facade.Interactor.GrabPrecognition; } /// /// Determines whether the grab point can be created for the given action. /// /// The action to check whether a grab point can be created for. /// Whether the grab point can be created. protected virtual bool CanCreateGrabPoint(GrabInteractableAction action) { if (action == null || action.GetType() != typeof(GrabInteractableFollowAction)) { return false; } GrabInteractableFollowAction followAction = (GrabInteractableFollowAction)action; return followAction != null && followAction.GrabOffset == GrabInteractableFollowAction.OffsetType.PrecisionPoint; } /// /// Registers the Interactor listeners. /// protected virtual void RegisterInteractorListeners() { if (Facade.Interactor != null) { Facade.Interactor.Touched.AddListener(HasTouched); Facade.Interactor.Untouched.AddListener(HasUntouched); Facade.Interactor.Grabbed.AddListener(PerformGrab); Facade.Interactor.Ungrabbed.AddListener(PerformUngrab); hasSubscribedToInteractorEvents = true; } } /// /// Unregisters the Interactor listeners. /// protected virtual void UnregisterInteractorListeners() { if (hasSubscribedToInteractorEvents) { Facade.Interactor.Touched.RemoveListener(HasTouched); Facade.Interactor.Untouched.RemoveListener(HasUntouched); Facade.Interactor.Grabbed.RemoveListener(PerformGrab); Facade.Interactor.Ungrabbed.RemoveListener(PerformUngrab); hasSubscribedToInteractorEvents = false; } } /// /// Handles the touching state. /// /// The Interactable being touched. protected virtual void HasTouched(InteractableFacade interactable) { if (!DisablePointerOnInteractorTouch) { return; } PointerContainer.SetActive(false); EnablePointerContainer.SetActive(false); } /// /// Handles the untouching state. /// /// The Interactable being touched. protected virtual void HasUntouched(InteractableFacade interactable) { EnablePointerContainer.SetActive(true); if (ShouldIgnoreEnablePointer) { if (interactable == null || !interactable.gameObject.activeInHierarchy) { EnablePointerLogic.Receive(); ShouldIgnoreEnablePointer = false; } return; } if (!DisablePointerOnInteractorTouch) { return; } PointerContainer.SetActive(true); } /// /// Performs the grab logic. /// /// The Interactable being grabbed. protected virtual void PerformGrab(InteractableFacade interactable) { ShouldIgnoreEnablePointer = true; GrabListener.Receive(); } /// /// Performs the ungrab logic. /// /// The Interactable being grabbed. protected virtual void PerformUngrab(InteractableFacade interactable) { if (interactable != null && checkInteractableActiveRoutine == null) { checkInteractableActiveRoutine = StartCoroutine(CheckInteractableIsNotActiveAtEndOfFrame(interactable)); } UngrabListener.Receive(); } /// /// Cancels the if interactable is active coroutine check. /// protected virtual void CancelCheckInteractableActiveRoutine() { if (checkInteractableActiveRoutine != null) { StopCoroutine(checkInteractableActiveRoutine); checkInteractableActiveRoutine = null; } } /// /// Checks if the given interactable is not active after the end of the frame and peforms an ungrab if it is no longer active. /// /// The interactable to check. /// An Enumerator to manage the running of the Coroutine. protected virtual IEnumerator CheckInteractableIsNotActiveAtEndOfFrame(InteractableFacade interactable) { if (interactable == null) { yield break; } yield return new WaitForEndOfFrame(); if (!interactable.gameObject.activeInHierarchy) { EnablePointerContainer.SetActive(true); PerformUngrab(interactable); } checkInteractableActiveRoutine = null; } } }