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