using System.Collections; using System.Collections.Generic; using System.Linq; using UnityEngine; using UnityEngine.UI; using UnityEngine.XR; namespace BNG { /// /// A trigger collider that handles grabbing grabbables. /// [RequireComponent(typeof(GrabbablesInTrigger))] public class Grabber : MonoBehaviour { [Header("Hand Side")] /// /// Which controller side. None if not attached to a controller. /// [Tooltip("Which controller side. None if not attached to a controller.")] public ControllerHand HandSide = ControllerHand.Left; [Header("Grab Settings")] /// /// Which controller side. None if not attached to a controller. /// [Tooltip("The default hold type for all Grabbables. A Grabbable can manually override this default.")] public HoldType DefaultHoldType = HoldType.HoldDown; /// /// The default grab button for all Grabbables. A Grabbable can manually override this default. /// [Tooltip("The default grab button for all Grabbables. A Grabbable can manually override this default.")] public GrabButton DefaultGrabButton = GrabButton.Grip; [Header("Hold / Release")] /// /// 0-1 determine how much to consider a grip. /// Example : 0.75 is holding the grip down 3/4 of the way /// [Tooltip("0-1 determine how much to consider a grip. Example : 0.75 is holding the grip down 3/4 of the way.")] [Range(0.0f, 1f)] public float GripAmount = 0.9f; /// /// How much grip considered to release an ob ect (0-1) /// [Tooltip("How much grip considered to release an object (0-1). Example : 0.75 is holding the grip down 3/4 of the way")] [Range(0.0f, 1f)] public float ReleaseGripAmount = 0.5f; /// /// How many seconds to check for grab input while Grip is held down. After grip is held down for this long, grip will need to be repressed in order to pick up an object. /// [Tooltip("How many seconds to check for grab input while Grip is held down. After grip is held down for this long, grip will need to be repressed in order to pick up an object.")] public float GrabCheckSeconds = 0.5f; float currentGrabTime; [Header("Equip on Start")] /// /// Assign a Grabbable here if you want to auto equip it on Start /// [Tooltip("Assign a Grabbable here if you want to auto equip it on Start")] public Grabbable EquipGrabbableOnStart; [Header("Hand Graphics")] /// /// Root transform that holds hands models. We may want to hide these while holding certain objects. /// [Tooltip("Root transform that holds hands models. We may want to hide these while holding certain objects, or parent this object to the grabbable so they follow the object perfectly.")] public Transform HandsGraphics; Transform handsGraphicsParent; Vector3 handsGraphicsPosition; Quaternion handsGraphicsRotation; [Header("Shown for Debug :")] /// /// The Grabbable we are currently holding. Null if not holding anything. /// [Tooltip("The Grabbable we are currently holding. Null if not holding anything.")] public Grabbable HeldGrabbable; /// /// Same as holding down grip if set to true. Should not have same value as ForceRelease. /// [Tooltip("Same as holding down grip if set to true. Should not have same value as ForceRelease.")] public bool ForceGrab = false; /// /// Force the release of grip /// [Tooltip("Force the release of grip if set to true. Should not have same value as ForceGrab.")] public bool ForceRelease = false; [Tooltip("Time.time when we last dropped a Grabbable")] public float LastDropTime; Grabbable previousClosest; Grabbable previousClosestRemote; /// /// Are we currently holding any valid items? /// public bool HoldingItem { get { return HeldGrabbable != null; } } /// /// Are we currently pulling a remote grabbable towards us? /// public bool RemoteGrabbingItem { get { return flyingGrabbable != null; } } /// /// Keep track of all grabbables in trigger /// GrabbablesInTrigger grabsInTrigger; public GrabbablesInTrigger GrabsInTrigger { get { return grabsInTrigger; } } /// /// Returns the Grabbable object if we are currenly remote grabbing an object /// public Grabbable RemoteGrabbingGrabbable { get { return flyingGrabbable; } } Grabbable flyingGrabbable; // How long the object has been flying at our hand float flyingTime = 0; // Offset Hand Models are from Grabber public Vector3 handsGraphicsGrabberOffset { get; private set; } public Vector3 handsGraphicsGrabberOffsetRotation { get; private set; } [HideInInspector] public Vector3 PreviousPosition; /// /// Can be used to position hands independently from model /// [HideInInspector] public Transform DummyTransform; Rigidbody rb; InputBridge input; ConfigurableJoint joint; // Is this a fresh grab / has the control been depressed [HideInInspector] public bool FreshGrip = true; [Header("Grabber Events")] [Tooltip("Called immediately before a Grabbable object is officially grabbed")] public GrabbableEvent onGrabEvent; [Tooltip("Called immediately after a Grabbable object is grabbed. Use this if you need the Grabbable object to be setup before accessing it")] public GrabbableEvent onAfterGrabEvent; [Tooltip("Called immediately before droppping an item")] public GrabbableEvent onReleaseEvent; // For tracking velocity [HideInInspector] public VelocityTracker velocityTracker; void Start() { rb = GetComponent(); grabsInTrigger = GetComponent(); joint = GetComponent(); input = InputBridge.Instance; // Setup defaults if (joint == null) { joint = gameObject.AddComponent(); joint.rotationDriveMode = RotationDriveMode.Slerp; JointDrive slerpDrive = joint.slerpDrive; slerpDrive.positionSpring = 600; JointDrive xDrive = joint.xDrive; xDrive.positionSpring = 2500; JointDrive yDrive = joint.yDrive; yDrive.positionSpring = 2500; JointDrive zDrive = joint.zDrive; zDrive.positionSpring = 2500; } if(HandsGraphics) { handsGraphicsParent = HandsGraphics.transform.parent; handsGraphicsPosition = HandsGraphics.transform.localPosition; handsGraphicsRotation = HandsGraphics.transform.localRotation; handsGraphicsGrabberOffset = transform.InverseTransformPoint(HandsGraphics.position); handsGraphicsGrabberOffsetRotation = transform.localEulerAngles; } // Make Collision Dynamic so we don't miss any collisions if (rb && rb.isKinematic) { rb.collisionDetectionMode = CollisionDetectionMode.ContinuousSpeculative; } // Should we auto equip an item if(EquipGrabbableOnStart != null) { GrabGrabbable(EquipGrabbableOnStart); } // Velocity Tracking if(velocityTracker == null) { velocityTracker = GetComponent(); } // Add Velocity Tracker if one was not provided if (velocityTracker == null) { velocityTracker = gameObject.AddComponent(); velocityTracker.controllerHand = HandSide; } } void Update() { // Keep track of how long an object has been trying to fly to our hand if(flyingGrabbable != null) { flyingTime += Time.deltaTime; // Only allow an object to fly towards us float maxFlyingGrabbableTime = 5; if(flyingTime > maxFlyingGrabbableTime) { resetFlyingGrabbable(); } } // Make sure grab is valid updateFreshGrabStatus(); // Fire off updates checkGrabbableEvents(); // Check for input to grab or release item if ((HoldingItem == false && InputCheckGrab()) || ForceGrab) { TryGrab(); } else if(((HoldingItem || RemoteGrabbingItem) && inputCheckRelease()) || ForceRelease) { TryRelease(); } } void updateFreshGrabStatus() { // Update Fresh Grab status if (getGrabInput(GrabButton.Grip) <= ReleaseGripAmount) { // We release grab, so this is considered fresh FreshGrip = true; currentGrabTime = 0; } // Increment fresh grab time if (getGrabInput(GrabButton.Grip) > GripAmount) { currentGrabTime += Time.deltaTime; } // Not considered a valid grab if holding down for too long if (currentGrabTime > GrabCheckSeconds) { FreshGrip = false; } } void checkGrabbableEvents() { // Bail if nothing in our trigger area if(grabsInTrigger == null) { return; } // If last closest was this one let event know and remove validator if (previousClosest != grabsInTrigger.ClosestGrabbable) { if (previousClosest != null) { // Fire Off Events GrabbableEvents[] ge = previousClosest.GetComponents(); if (ge != null) { for (int x = 0; x < ge.Length; x++) { ge[x].OnNoLongerClosestGrabbable(HandSide); ge[x].OnNoLongerClosestGrabbable(this); } } previousClosest.RemoveValidGrabber(this); } // Update closest Grabbable if (grabsInTrigger.ClosestGrabbable != null && !HoldingItem) { // Fire Off Events GrabbableEvents[] ge = grabsInTrigger.ClosestGrabbable.GetComponents(); if (ge != null) { for (int x = 0; x < ge.Length; x++) { ge[x].OnBecomesClosestGrabbable(HandSide); ge[x].OnBecomesClosestGrabbable(this); } } grabsInTrigger.ClosestGrabbable.AddValidGrabber(this); } } if (grabsInTrigger.ClosestGrabbable != null && !HoldingItem) { grabsInTrigger.ClosestGrabbable.AddValidGrabber(this); } // Remote Grabbable Events // If last closest was this one, unhighlight object if (previousClosestRemote != grabsInTrigger.ClosestRemoteGrabbable) { if (previousClosestRemote != null) { // Fire Off Events GrabbableEvents[] ge = previousClosestRemote.GetComponents(); if (ge != null) { for (int x = 0; x < ge.Length; x++) { ge[x].OnNoLongerClosestRemoteGrabbable(HandSide); ge[x].OnNoLongerClosestRemoteGrabbable(this); } } previousClosestRemote.RemoveValidGrabber(this); } // Update closest remote Grabbable if (grabsInTrigger.ClosestRemoteGrabbable != null && !HoldingItem) { // Fire Off Events GrabbableEvents[] ge = grabsInTrigger.ClosestRemoteGrabbable.GetComponents(); if (ge != null) { for (int x = 0; x < ge.Length; x++) { ge[x].OnBecomesClosestRemoteGrabbable(HandSide); ge[x].OnBecomesClosestRemoteGrabbable(this); } } grabsInTrigger.ClosestRemoteGrabbable.AddValidGrabber(this); } } // Set this as previous closest previousClosest = grabsInTrigger.ClosestGrabbable; previousClosestRemote = grabsInTrigger.ClosestRemoteGrabbable; } // See if we are inputting controls to grab an item public virtual bool InputCheckGrab() { // Nothing nearby to grab Grabbable closest = getClosestOrRemote(); return GetInputDownForGrabbable(closest); } public virtual bool GetInputDownForGrabbable(Grabbable grabObject) { if(grabObject == null) { return false; } // Check Hold Controls HoldType closestHoldType = getHoldType(grabObject); GrabButton closestGrabButton = GetGrabButton(grabObject); // Hold to grab controls if (closestHoldType == HoldType.HoldDown) { bool grabInput = getGrabInput(closestGrabButton) >= GripAmount; if (closestGrabButton == GrabButton.Grip && !FreshGrip) { return false; } //if(HandSide == ControllerHand.Left && InputBridge.Instance.LeftTrigger > 0.9f) { // Debug.Log("Trigger Down"); //} return grabInput; } // Check Toggle Controls else if (closestHoldType == HoldType.Toggle) { return getToggleInput(closestGrabButton); } return false; } HoldType getHoldType(Grabbable grab) { HoldType closestHoldType = grab.Grabtype; // Inherit from Grabber if (closestHoldType == HoldType.Inherit) { closestHoldType = DefaultHoldType; } // Inherit isn't a value in itself. Use "hold down" instead and warn the user if (closestHoldType == HoldType.Inherit) { closestHoldType = HoldType.HoldDown; Debug.LogWarning("Inherit found on both Grabber and Grabbable. Consider updating the Grabber's DefaultHoldType"); } return closestHoldType; } public virtual GrabButton GetGrabButton(Grabbable grab) { GrabButton grabButton = grab.GrabButton; // Inherit from Grabber if (grabButton == GrabButton.Inherit) { grabButton = DefaultGrabButton; } // Inherit isn't a value in itself. Use "Grip" instead and warn the user if (grabButton == GrabButton.Inherit) { grabButton = GrabButton.Grip; Debug.LogWarning("Inherit found on both Grabber and Grabbable. Consider updating the Grabber's DefaultHoldType"); } return grabButton; } Grabbable getClosestOrRemote() { if (grabsInTrigger.ClosestGrabbable != null) { return grabsInTrigger.ClosestGrabbable; } else if (grabsInTrigger.ClosestRemoteGrabbable != null) { return grabsInTrigger.ClosestRemoteGrabbable; } return null; } // Release conditions are a little different than grab bool inputCheckRelease() { var grabbingGrabbable = RemoteGrabbingItem ? flyingGrabbable : HeldGrabbable; // Can't release anything we're not holding if (grabbingGrabbable == null) { return false; } // Check Hold Controls HoldType closestHoldType = getHoldType(grabbingGrabbable); GrabButton closestGrabButton = GetGrabButton(grabbingGrabbable); if (closestHoldType == HoldType.HoldDown) { return getGrabInput(closestGrabButton) <= ReleaseGripAmount; } // Check for toggle controls else if (closestHoldType == HoldType.Toggle) { return getToggleInput(closestGrabButton); } return false; } float getGrabInput(GrabButton btn) { float gripValue = 0; if(input == null) { return 0; } // Left Hand if (HandSide == ControllerHand.Left) { if (btn == GrabButton.Grip) { gripValue = input.LeftGrip; } else if (btn == GrabButton.Trigger) { gripValue = input.LeftTrigger; } } // Right Hand else if (HandSide == ControllerHand.Right) { if (btn == GrabButton.Grip) { gripValue = input.RightGrip; } else if (btn == GrabButton.Trigger) { gripValue = input.RightTrigger; } } return gripValue; } bool getToggleInput(GrabButton btn) { if (input == null) { return false; } // Left Hand if (HandSide == ControllerHand.Left) { if (btn == GrabButton.Grip) { return input.LeftGripDown; } else if (btn == GrabButton.Trigger) { return input.LeftTriggerDown; } } // Right Hand else if (HandSide == ControllerHand.Right) { if (btn == GrabButton.Grip) { return input.RightGripDown; } else if (btn == GrabButton.Trigger) { return input.RightTriggerDown; } } return false; } public virtual bool TryGrab() { // Already holding something if (HeldGrabbable != null) { return false; } // Activate Nearby Grabbable if (grabsInTrigger.ClosestGrabbable != null) { GrabGrabbable(grabsInTrigger.ClosestGrabbable); return true; } // If no immediate grabbable, see if remote is available to pull else if(grabsInTrigger.ClosestRemoteGrabbable != null && flyingGrabbable == null) { flyingGrabbable = grabsInTrigger.ClosestRemoteGrabbable; flyingGrabbable.GrabRemoteItem(this); } return false; } // Assign new held Item, then grab the item into our hand / controller public virtual void GrabGrabbable(Grabbable item) { // We are trying to grab something else if(flyingGrabbable != null && item != flyingGrabbable) { return; } // Make sure we aren't flying an object at us still resetFlyingGrabbable(); // Drop whatever we were holding if (HeldGrabbable != null && HeldGrabbable) { TryRelease(); } // Assign new grabbable HeldGrabbable = item; // Just grabbed something, no longer fresh. FreshGrip = false; // Fire off Grabber 'before' grab event onGrabEvent?.Invoke(item); // Let item know it's been grabbed item.GrabItem(this); // Fire off Grabber 'after' grab event onAfterGrabEvent?.Invoke(item); } // Dropped whatever was in hand public virtual void DidDrop() { // Fire off Grabber Release event if (onReleaseEvent != null && HeldGrabbable != null) { onReleaseEvent.Invoke(HeldGrabbable); } HeldGrabbable = null; transform.localEulerAngles = Vector3.zero; LastDropTime = Time.time; resetFlyingGrabbable(); ResetHandGraphics(); } public virtual void HideHandGraphics() { if (HandsGraphics != null) { HandsGraphics.gameObject.SetActive(false); } } public virtual void ResetHandGraphics() { if(HandsGraphics != null) { try { // Make visible again HandsGraphics.gameObject.SetActive(true); // Move parent back to where it was originally if (handsGraphicsParent != null) { { var leftHandedControllers = new List(); var dc = InputDeviceCharacteristics.HeldInHand | InputDeviceCharacteristics.Left | InputDeviceCharacteristics.Controller; InputDevices.GetDevicesWithCharacteristics(dc, leftHandedControllers); var all = leftHandedControllers.ToList(); var left = leftHandedControllers.FirstOrDefault(); } HandsGraphics.transform.parent = handsGraphicsParent; HandsGraphics.transform.localPosition = handsGraphicsPosition; HandsGraphics.transform.localRotation = handsGraphicsRotation; } } catch (System.Exception ex) { Debug.LogError(ex.Message); } } } public virtual void TryRelease() { if (HeldGrabbable != null && HeldGrabbable.CanBeDropped) { HeldGrabbable.DropItem(this); } // No longer try to bring flying grabbable to us resetFlyingGrabbable(); } void resetFlyingGrabbable() { // No longer flying at us if (flyingGrabbable != null) { flyingGrabbable.ResetGrabbing(); flyingGrabbable = null; flyingTime = 0; } } public virtual Vector3 GetGrabberAveragedVelocity() { return velocityTracker.GetAveragedVelocity(); } public virtual Vector3 GetGrabberAveragedAngularVelocity() { return velocityTracker.GetAveragedAngularVelocity(); } } }