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