namespace Tilia.Interactions.Interactables.Interactors
{
using System;
using System.Collections;
using System.Collections.Generic;
using Tilia.Interactions.Interactables.Interactables;
using UnityEngine;
using UnityEngine.Events;
using Zinnia.Action;
using Zinnia.Data.Attribute;
using Zinnia.Data.Type;
using Zinnia.Extension;
using Zinnia.Tracking.Collision;
using Zinnia.Tracking.Velocity;
///
/// The public interface into the Interactor Prefab.
///
public class InteractorFacade : MonoBehaviour
{
///
/// Defines the event with the .
///
[Serializable]
public class UnityEvent : UnityEvent { }
#region Interactor Settings
[Header("Interactor Settings")]
[Tooltip("The BooleanAction that will initiate the Interactor grab mechanism.")]
[SerializeField]
private BooleanAction grabAction;
///
/// The that will initiate the Interactor grab mechanism.
///
public BooleanAction GrabAction
{
get
{
return grabAction;
}
set
{
grabAction = value;
if (this.IsMemberChangeAllowed())
{
OnAfterGrabActionChange();
}
}
}
[Tooltip("The VelocityTracker to measure the interactors current velocity.")]
[SerializeField]
private VelocityTracker velocityTracker;
///
/// The to measure the interactors current velocity.
///
public VelocityTracker VelocityTracker
{
get
{
return velocityTracker;
}
set
{
velocityTracker = value;
if (this.IsMemberChangeAllowed())
{
OnAfterVelocityTrackerChange();
}
}
}
[Tooltip("The time between initiating the GrabAction and touching an Interactable to be considered a valid grab.")]
[SerializeField]
private float grabPrecognition = 0.1f;
///
/// The time between initiating the and touching an Interactable to be considered a valid grab.
///
public float GrabPrecognition
{
get
{
return grabPrecognition;
}
set
{
grabPrecognition = value;
if (this.IsMemberChangeAllowed())
{
OnAfterGrabPrecognitionChange();
}
}
}
#endregion
#region Interactor Events
///
/// Emitted when the Interactor starts touching a valid Interactable.
///
[Header("Interactor Events")]
public UnityEvent Touched = new UnityEvent();
///
/// Emitted when the Interactor stops touching a valid Interactable.
///
public UnityEvent Untouched = new UnityEvent();
///
/// Emitted when the Interactor starts grabbing a valid Interactable.
///
public UnityEvent Grabbed = new UnityEvent();
///
/// Emitted when the Interactor stops grabbing a valid Interactable.
///
public UnityEvent Ungrabbed = new UnityEvent();
#endregion
#region Reference Settings
[Header("Reference Settings")]
[Tooltip("The point at which the grabbed Interactable will be attached to the Interactor.")]
[SerializeField]
private GameObject grabAttachPoint;
///
/// The point at which the grabbed Interactable will be attached to the Interactor.
///
public GameObject GrabAttachPoint
{
get
{
return grabAttachPoint;
}
set
{
grabAttachPoint = value;
}
}
[Tooltip("The point at which the grabbed Interactable will be attached to the Interactor via precision grabbing.")]
[SerializeField]
[Restricted]
private GameObject precisionAttachPoint;
///
/// The point at which the grabbed Interactable will be attached to the Interactor via precision grabbing.
///
public GameObject PrecisionAttachPoint
{
get
{
return precisionAttachPoint;
}
set
{
precisionAttachPoint = value;
}
}
[Tooltip("The container of the Interactor avatar components.")]
[SerializeField]
[Restricted]
private GameObject avatarContainer;
///
/// The container of the Interactor avatar components.
///
public GameObject AvatarContainer
{
get
{
return avatarContainer;
}
set
{
avatarContainer = value;
}
}
[Tooltip("The linked Touch Internal Setup.")]
[SerializeField]
[Restricted]
private TouchInteractorConfigurator touchConfiguration;
///
/// The linked Touch Internal Setup.
///
public TouchInteractorConfigurator TouchConfiguration
{
get
{
return touchConfiguration;
}
set
{
touchConfiguration = value;
}
}
[Tooltip("The linked Grab Internal Setup.")]
[SerializeField]
[Restricted]
private GrabInteractorConfigurator grabConfiguration;
///
/// The linked Grab Internal Setup.
///
public GrabInteractorConfigurator GrabConfiguration
{
get
{
return grabConfiguration;
}
set
{
grabConfiguration = value;
}
}
#endregion
///
/// A collection of currently touched GameObjects.
///
public virtual IReadOnlyList TouchedObjects => TouchConfiguration.TouchedObjects;
///
/// The currently active touched GameObject.
///
public virtual GameObject ActiveTouchedObject => TouchConfiguration.ActiveTouchedObject;
///
/// A collection of currently grabbed GameObjects.
///
public virtual IReadOnlyList GrabbedObjects => GrabConfiguration.GrabbedObjects;
///
/// The routine for clearing grab state.
///
protected Coroutine ClearGrabStateRoutine;
///
/// A reusable instance of .
///
protected WaitForEndOfFrame delayInstruction = new WaitForEndOfFrame();
///
/// Clears .
///
public virtual void ClearGrabAction()
{
if (!this.IsValidState())
{
return;
}
GrabAction = default;
}
///
/// Clears .
///
public virtual void ClearVelocityTracker()
{
if (!this.IsValidState())
{
return;
}
VelocityTracker = default;
}
///
/// Clears .
///
public virtual void ClearGrabAttachPoint()
{
if (!this.IsValidState())
{
return;
}
GrabAttachPoint = default;
}
///
/// Simulates this Interactor touching a given Interactable.
///
/// The GameObject containing the Interactable.
public virtual void SimulateTouch(GameObject interactable)
{
if (interactable == null)
{
return;
}
GetComponent().OnCollisionStarted(CreateCollisionPayload(interactable));
}
///
/// Simulates this Interactor touching a given Interactable.
///
/// The Interactable.
public virtual void SimulateTouch(InteractableFacade interactable)
{
if (interactable == null)
{
return;
}
SimulateTouch(interactable.gameObject);
}
///
/// Simulates this Interactor untouching a given Interactable.
///
/// The GameObject containing the Interactable.
public virtual void SimulateUntouch(GameObject interactable)
{
if (interactable == null)
{
return;
}
GetComponent().OnCollisionStopped(CreateCollisionPayload(interactable));
}
///
/// Simulates this Interactor untouching a given Interactable.
///
/// The Interactable.
public virtual void SimulateUntouch(InteractableFacade interactable)
{
if (interactable == null)
{
return;
}
SimulateUntouch(interactable.gameObject);
}
///
/// Attempt to attach a that contains an to this and ungrabs any existing grab.
///
/// The GameObject that the Interactable is on.
public virtual void Grab(GameObject interactable)
{
Grab(interactable, true);
}
///
/// Attempt to attach a that contains an to this and does not ungrab any existing grab.
///
/// The GameObject that the Interactable is on.
public virtual void GrabIgnoreUngrab(GameObject interactable)
{
Grab(interactable, false);
}
///
/// Attempt to attach a that contains an to this .
///
/// The GameObject that the Interactable is on.
/// Whether to ungrab any existing grab.
public virtual void Grab(GameObject interactable, bool ungrabExistingGrab)
{
Grab(interactable.TryGetComponent(true, true), ungrabExistingGrab);
}
///
/// Attempt to attach an to this and ungrabs any existing grab.
///
/// The Interactable to attempt to grab.
public virtual void Grab(InteractableFacade interactable)
{
Grab(interactable, true);
}
///
/// Attempt to attach an to this and does not ungrab any existing grab.
///
/// The Interactable to attempt to grab.
public virtual void GrabIgnoreUngrab(InteractableFacade interactable)
{
Grab(interactable, false);
}
///
/// Attempt to attach an to this .
///
/// The Interactable to attempt to grab.
/// Whether to ungrab any existing grab.
public virtual void Grab(InteractableFacade interactable, bool ungrabExistingGrab)
{
Grab(interactable, null, null, ungrabExistingGrab);
}
///
/// Attempt to attach an found in the given to this and ungrabs any existing grab.
///
/// The collision data containing a valid Interactable.
public virtual void Grab(SurfaceData data)
{
Grab(data, true);
}
///
/// Attempt to attach an found in the given to this and does not ungrab any existing grab.
///
/// The collision data containing a valid Interactable.
public virtual void GrabIgnoreUngrab(SurfaceData data)
{
Grab(data, false);
}
///
/// Attempt to attach an found in the given to this .
///
/// The collision data containing a valid Interactable.
/// Whether to ungrab any existing grab.
public virtual void Grab(SurfaceData data, bool ungrabExistingGrab)
{
if (data == null || data.CollisionData.transform == null)
{
return;
}
Grab(data.CollisionData.transform.gameObject.TryGetComponent(true, true), null, null, ungrabExistingGrab);
}
///
/// Attempt to attach an to this 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)
{
GrabConfiguration.Grab(interactable, collision, collider, true);
}
///
/// Attempt to attach an to this 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)
{
GrabConfiguration.Grab(interactable, collision, collider, false);
}
///
/// Attempt to attach an to this 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)
{
GrabConfiguration.Grab(interactable, collision, collider, ungrabExistingGrab);
}
///
/// Attempt to ungrab currently grabbed Interactables to the current Interactor.
///
public virtual void Ungrab()
{
GrabConfiguration.Ungrab();
}
///
/// Notifies the Interactor that it is touching an Interactable.
///
/// The Interactable being touched.
public virtual void NotifyOfTouch(InteractableFacade interactable)
{
TouchConfiguration.IsTouchingAction.Receive(true);
Touched?.Invoke(interactable);
}
///
/// Notifies the Interactor that it is no longer touching an Interactable.
///
/// The Interactable being untouched.
public virtual void NotifyOfUntouch(InteractableFacade interactable)
{
TouchConfiguration.IsTouchingAction.Receive(false);
Untouched?.Invoke(interactable);
}
///
/// Notifies the Interactor that it is grabbing an Interactable.
///
/// The Interactable being grabbed.
public virtual void NotifyOfGrab(InteractableFacade interactable)
{
GrabConfiguration.GrabbedObjectsCollection.AddUnique(interactable.gameObject);
if (ClearGrabStateRoutine != null)
{
StopCoroutine(ClearGrabStateRoutine);
ClearGrabStateRoutine = null;
}
GrabConfiguration.IsGrabbingAction.Receive(true);
Grabbed?.Invoke(interactable);
}
///
/// Notifies the Interactor that it is no longer grabbing an Interactable.
///
/// The Interactable being ungrabbed.
public virtual void NotifyOfUngrab(InteractableFacade interactable)
{
ClearGrabState(interactable);
Ungrabbed?.Invoke(interactable);
ClearGrabStateRoutine = StartCoroutine(ClearGrabStateAtEndOfFrame(interactable));
}
///
/// Snaps the orientation of all grabbed Interactables to this Interactor.
///
public virtual void SnapAllGrabbedInteractableOrientations()
{
for (int index = 0; index < GrabbedObjects.Count; index++)
{
SnapGrabbedInteractableOrientation(index);
}
}
///
/// Snaps the orientation of the grabbed Interactable at the given index to this Interactor.
///
/// The index of the grabbed Interactable.
public virtual void SnapGrabbedInteractableOrientation(int index)
{
if (index < 0 || index >= GrabbedObjects.Count)
{
return;
}
InteractableFacade interactable = GrabbedObjects[index].TryGetComponent(true, true);
if (interactable != null)
{
interactable.SnapFollowOrientation();
}
}
///
/// Creates a collision payload for a given Interactable
///
/// The GameObject that the Interactable is on.
/// A collision payload.
protected virtual CollisionNotifier.EventData CreateCollisionPayload(GameObject interactable)
{
return new CollisionNotifier.EventData()
{
ForwardSource = GetComponent(),
IsTrigger = true,
CollisionData = null,
ColliderData = interactable.GetComponentInChildren()
};
}
///
/// Clears the grab state for the given .
///
/// The Interactable to clear grab state on.
protected virtual void ClearGrabState(InteractableFacade interactable)
{
GrabConfiguration.IsGrabbingAction.Receive(false);
GrabConfiguration.GrabbedObjectsCollection.Remove(interactable.TryGetGameObject());
GrabConfiguration.GrabbedObjectsCollection.Remove(interactable.Configuration.ConsumerContainer);
GrabConfiguration.StopGrabbingPublisher.ClearActiveCollisions();
GrabConfiguration.StartGrabbingPublisher.RegisteredConsumerContainer.UnregisterConsumersOnContainer(interactable.Configuration.ConsumerContainer);
}
///
/// Clears the grab state at the end of the frame.
///
///
/// An Enumerator to manage the running of the Coroutine.
protected virtual IEnumerator ClearGrabStateAtEndOfFrame(InteractableFacade interactable)
{
yield return delayInstruction;
ClearGrabState(interactable);
ClearGrabStateRoutine = null;
}
///
/// Called after has been changed.
///
protected virtual void OnAfterGrabActionChange()
{
GrabConfiguration.ConfigureGrabAction();
}
///
/// Called after has been changed.
///
protected virtual void OnAfterVelocityTrackerChange()
{
GrabConfiguration.ConfigureVelocityTrackers();
}
///
/// Called after has been changed.
///
protected virtual void OnAfterGrabPrecognitionChange()
{
GrabConfiguration.ConfigureGrabPrecognition();
}
}
}