namespace Tilia.Interactions.Interactables.Interactors
{
using System.Collections;
using System.Collections.Generic;
using Tilia.Interactions.Interactables.Interactables;
using UnityEngine;
using Zinnia.Action;
using Zinnia.Data.Attribute;
using Zinnia.Data.Collection.List;
using Zinnia.Extension;
using Zinnia.Tracking.Collision;
using Zinnia.Tracking.Collision.Active;
using Zinnia.Tracking.Velocity;
using Zinnia.Utility;
///
/// Sets up the Interactor Prefab grab settings based on the provided user settings.
///
public class GrabInteractorConfigurator : MonoBehaviour
{
#region Facade Settings
[Header("Facade Settings")]
[Tooltip("The public interface facade.")]
[SerializeField]
[Restricted]
private InteractorFacade facade;
///
/// The public interface facade.
///
public InteractorFacade Facade
{
get
{
return facade;
}
set
{
facade = value;
}
}
#endregion
#region Grab Settings
[Header("Grab Settings")]
[Tooltip("The BooleanAction that will initiate the Interactor grab mechanism.")]
[SerializeField]
[Restricted]
private BooleanAction grabAction;
///
/// The that will initiate the Interactor grab mechanism.
///
public BooleanAction GrabAction
{
get
{
return grabAction;
}
set
{
grabAction = value;
}
}
[Tooltip("The VelocityTrackerProcessor to measure the interactors current velocity for throwing on release.")]
[SerializeField]
[Restricted]
private VelocityTrackerProcessor velocityTracker;
///
/// The to measure the interactors current velocity for throwing on release.
///
public VelocityTrackerProcessor VelocityTracker
{
get
{
return velocityTracker;
}
set
{
velocityTracker = value;
if (this.IsMemberChangeAllowed())
{
OnAfterVelocityTrackerChange();
}
}
}
[Tooltip("The ActiveCollisionPublisher for checking valid start grabbing action.")]
[SerializeField]
[Restricted]
private ActiveCollisionPublisher startGrabbingPublisher;
///
/// The for checking valid start grabbing action.
///
public ActiveCollisionPublisher StartGrabbingPublisher
{
get
{
return startGrabbingPublisher;
}
set
{
startGrabbingPublisher = value;
}
}
[Tooltip("The ActiveCollisionPublisher for checking valid stop grabbing action.")]
[SerializeField]
[Restricted]
private ActiveCollisionPublisher stopGrabbingPublisher;
///
/// The for checking valid stop grabbing action.
///
public ActiveCollisionPublisher StopGrabbingPublisher
{
get
{
return stopGrabbingPublisher;
}
set
{
stopGrabbingPublisher = value;
}
}
[Tooltip("The processor for initiating an instant grab.")]
[SerializeField]
[Restricted]
private GameObject instantGrabProcessor;
///
/// The processor for initiating an instant grab.
///
public GameObject InstantGrabProcessor
{
get
{
return instantGrabProcessor;
}
set
{
instantGrabProcessor = value;
}
}
[Tooltip("The processor for initiating a precognitive grab.")]
[SerializeField]
[Restricted]
private GameObject precognitionGrabProcessor;
///
/// The processor for initiating a precognitive grab.
///
public GameObject PrecognitionGrabProcessor
{
get
{
return precognitionGrabProcessor;
}
set
{
precognitionGrabProcessor = value;
}
}
[Tooltip("The CountdownTimer to determine grab precognition.")]
[SerializeField]
[Restricted]
private CountdownTimer precognitionTimer;
///
/// The to determine grab precognition.
///
public CountdownTimer PrecognitionTimer
{
get
{
return precognitionTimer;
}
set
{
precognitionTimer = value;
}
}
[Tooltip("The minimum timer value for the grab precognition CountdownTimer.")]
[SerializeField]
[Restricted]
private float minPrecognitionTimer = 0.01f;
///
/// The minimum timer value for the grab precognition .
///
public float MinPrecognitionTimer
{
get
{
return minPrecognitionTimer;
}
set
{
minPrecognitionTimer = value;
}
}
[Tooltip("The GameObjectObservableSet containing the currently grabbed objects.")]
[SerializeField]
[Restricted]
private GameObjectObservableList grabbedObjectsCollection;
///
/// The containing the currently grabbed objects.
///
public GameObjectObservableList GrabbedObjectsCollection
{
get
{
return grabbedObjectsCollection;
}
set
{
grabbedObjectsCollection = value;
}
}
[Tooltip("A BooleanAction for holding the state of whether the Interactor is grabbing something.")]
[SerializeField]
[Restricted]
private BooleanAction isGrabbingAction;
///
/// A for holding the state of whether the Interactor is grabbing something.
///
public BooleanAction IsGrabbingAction
{
get
{
return isGrabbingAction;
}
set
{
isGrabbingAction = value;
}
}
[Tooltip("Whether to simulate a touch before force grabbing.")]
[SerializeField]
[Restricted]
private bool touchBeforeForceGrab = true;
///
/// Whether to simulate a touch before force grabbing.
///
public bool TouchBeforeForceGrab
{
get
{
return touchBeforeForceGrab;
}
set
{
touchBeforeForceGrab = value;
}
}
#endregion
///
/// A collection of currently grabbed GameObjects.
///
public virtual IReadOnlyList GrabbedObjects => GrabbedObjectsCollection.NonSubscribableElements;
///
/// Manages the reset simulate touch logic.
///
protected Coroutine simulateTouchResetRoutine;
///
/// A reusable instance of event data.
///
protected readonly ActiveCollisionsContainer.EventData activeCollisionsEventData = new ActiveCollisionsContainer.EventData();
///
/// Clears .
///
public virtual void ClearVelocityTracker()
{
if (!this.IsValidState())
{
return;
}
VelocityTracker = default;
}
///
/// Configures the action used to control grabbing.
///
public virtual void ConfigureGrabAction()
{
if (GrabAction != null && Facade != null && Facade.GrabAction != null)
{
GrabAction.RunWhenActiveAndEnabled(() => GrabAction.ClearSources());
GrabAction.RunWhenActiveAndEnabled(() => GrabAction.AddSource(Facade.GrabAction));
}
}
///
/// Configures the velocity tracker used for grabbing.
///
public virtual void ConfigureVelocityTrackers()
{
if (VelocityTracker != null && Facade != null && Facade.VelocityTracker != null)
{
VelocityTracker.VelocityTrackers.RunWhenActiveAndEnabled(() => VelocityTracker.VelocityTrackers.Clear());
VelocityTracker.VelocityTrackers.RunWhenActiveAndEnabled(() => VelocityTracker.VelocityTrackers.Add(Facade.VelocityTracker));
}
}
///
/// Configures the components for grab precognition.
///
public virtual void ConfigureGrabPrecognition()
{
if (Facade.GrabPrecognition < MinPrecognitionTimer && !Facade.GrabPrecognition.ApproxEquals(0f))
{
Facade.GrabPrecognition = MinPrecognitionTimer;
}
PrecognitionTimer.StartTime = Facade.GrabPrecognition;
ChooseGrabProcessor();
}
///
/// Attempt to grab an Interactable to the current Interactor utilizing custom collision data and ungrabs any existing grab.
///
/// The Interactable to attempt to grab.
/// Custom collision data.
/// Custom collider data.
public virtual void Grab(InteractableFacade interactable, Collision collision, Collider collider)
{
Grab(interactable, collision, collider, true);
}
///
/// Attempt to grab an Interactable to the current Interactor utilizing custom collision data and does not ungrab any existing grab..
///
/// The Interactable to attempt to grab.
/// Custom collision data.
/// Custom collider data.
public virtual void GrabIgnoreUngrab(InteractableFacade interactable, Collision collision, Collider collider)
{
Grab(interactable, collision, collider, false);
}
///
/// Attempt to grab an Interactable to the current Interactor utilizing custom collision data.
///
/// The Interactable to attempt to grab.
/// Custom collision data.
/// Custom collider data.
/// Whether to ungrab any existing grab.
public virtual void Grab(InteractableFacade interactable, Collision collision, Collider collider, bool ungrabExistingGrab)
{
if (interactable == null || !this.IsValidState())
{
return;
}
if (ungrabExistingGrab)
{
Ungrab();
}
if (TouchBeforeForceGrab)
{
Facade.SimulateTouch(interactable);
CancelSimulateTouchResetRoutine();
simulateTouchResetRoutine = StartCoroutine(
ResetSimulateTouchState(interactable,
interactable.Configuration.CollisionNotifier.enabled,
interactable.Configuration.CollisionNotifier.ProcessCollisionsWhenDisabled));
interactable.Configuration.CollisionNotifier.ProcessCollisionsWhenDisabled = false;
interactable.Configuration.CollisionNotifier.enabled = false;
}
StartGrabbingPublisher.SetActiveCollisions(CreateActiveCollisionsEventData(interactable.gameObject, collision, collider));
ProcessGrabAction(StartGrabbingPublisher, true);
if (interactable.IsGrabTypeToggle)
{
ProcessGrabAction(StartGrabbingPublisher, false);
}
}
///
/// Attempt to ungrab currently grabbed Interactables to the current Interactor.
///
public virtual void Ungrab()
{
if (GrabbedObjects.Count == 0)
{
return;
}
InteractableFacade interactable = GrabbedObjects[0].TryGetComponent(true, true);
if (interactable.IsGrabTypeToggle)
{
if (StartGrabbingPublisher.Payload.ActiveCollisions.Count == 0)
{
StartGrabbingPublisher.SetActiveCollisions(CreateActiveCollisionsEventData(interactable.gameObject, null, null));
}
ProcessGrabAction(StartGrabbingPublisher, true);
}
ProcessGrabAction(StopGrabbingPublisher, false);
}
///
/// Attempts to automatically emit precognition grab if there are registered consumers.
///
public virtual void PrecognitionGrabForRegisteredConsumers()
{
if (StartGrabbingPublisher.RegisteredConsumerContainer != null &&
StartGrabbingPublisher.RegisteredConsumerContainer.RegisteredConsumers.Count > 0)
{
PrecognitionTimer.EmitStatus();
}
}
protected virtual void OnEnable()
{
ConfigureGrabAction();
ConfigureVelocityTrackers();
ConfigureGrabPrecognition();
}
protected virtual void OnDisable()
{
CancelSimulateTouchResetRoutine();
}
///
/// Cancels the coroutine.
///
protected virtual void CancelSimulateTouchResetRoutine()
{
if (simulateTouchResetRoutine != null)
{
StopCoroutine(simulateTouchResetRoutine);
simulateTouchResetRoutine = null;
}
}
///
/// Resets the state set by the simulate touch logic.
///
/// The interactable to reset on.
/// The value to set the value to.
/// The value to set the value to.
/// An enumerator for controlling the coroutine.
protected virtual IEnumerator ResetSimulateTouchState(InteractableFacade interactable, bool collisionNotifierEnabled, bool processCollisionsWhenDisabled)
{
yield return new WaitForFixedUpdate();
if (interactable != null)
{
interactable.Configuration.CollisionNotifier.enabled = collisionNotifierEnabled;
interactable.Configuration.CollisionNotifier.ProcessCollisionsWhenDisabled = processCollisionsWhenDisabled;
}
simulateTouchResetRoutine = null;
}
///
/// Chooses which grab processing to perform on the grab action.
///
protected virtual void ChooseGrabProcessor()
{
bool disablePrecognition = PrecognitionTimer.StartTime.ApproxEquals(0f);
InstantGrabProcessor.SetActive(disablePrecognition);
PrecognitionGrabProcessor.SetActive(!disablePrecognition);
}
///
/// Processes the given collision data into a grab action based on the given state.
///
/// The collision data to process.
/// The grab state to check against.
protected virtual void ProcessGrabAction(ActiveCollisionPublisher publisher, bool actionState)
{
InstantGrabProcessor.SetActive(false);
PrecognitionGrabProcessor.SetActive(false);
if (GrabAction.Value != actionState)
{
GrabAction.Receive(actionState);
}
if (GrabAction.Value)
{
publisher.Publish();
}
ChooseGrabProcessor();
}
///
/// Creates Active Collision data based on the given parameters.
///
/// The source of the forwarding the collision data.
/// The data on the point of the collision for precision grabbing.
/// The collider that has been collided with.
///
protected virtual ActiveCollisionsContainer.EventData CreateActiveCollisionsEventData(GameObject forwardSource, Collision collision = null, Collider collider = null)
{
collider = collider == null ? forwardSource.GetComponentInChildren() : collider;
if (activeCollisionsEventData.ActiveCollisions.Count == 0)
{
activeCollisionsEventData.ActiveCollisions.Add(new CollisionNotifier.EventData());
}
activeCollisionsEventData.ActiveCollisions[0].Set(forwardSource.TryGetComponent(), collider.isTrigger, collision, collider);
return activeCollisionsEventData;
}
///
/// Called after has been changed.
///
protected virtual void OnAfterVelocityTrackerChange()
{
ConfigureVelocityTrackers();
}
}
}