using System.Collections; using System.Collections.Generic; using UnityEngine; namespace BNG { public class HandPhysics : MonoBehaviour { /// /// This is the object our physical hand should try to follow / match /// [Tooltip("This is the object our physical hand should try to follow / match. Should typically be an object on the controller Transform")] public Transform AttachTo; [Tooltip("Amount of Velocity to apply to hands when trying to reach anchor point")] public float HandVelocity = 1500f; [Tooltip("If true, Hand COlliders will be disabled while grabbing an object")] public bool DisableHandCollidersOnGrab = true; [Tooltip("If the hand exceeds this distance from it's origin it will snap back to the original position. Specified in meters.")] public float SnapBackDistance = 1f; [Tooltip("This is the Grabber to use when this hand is active.")] public Grabber ThisGrabber; [Tooltip("Disable this Grabber when this hand is active. (Optional)")] public Grabber DisableGrabber; [Tooltip("This is the RemoteGrabber to use when this hand is active.")] public RemoteGrabber ThisRemoteGrabber; [Tooltip("Disable this RemoteGrabber when this hand is active. (Optional)")] public RemoteGrabber DisableRemoteGrabber; [Tooltip("Assign Hand Colliders this material if provided")] public PhysicMaterial ColliderMaterial; public Transform HandModel; public Transform HandModelOffset; public bool HoldingObject { get { return ThisGrabber != null && ThisGrabber.HeldGrabbable != null; } } // Colliders that live in the hand model List handColliders; Rigidbody rigid; ConfigurableJoint configJoint; Grabbable heldGrabbable; List collisions = new List(); LineRenderer line; Vector3 localHandOffset; Vector3 localHandOffsetRotation; bool wasHoldingObject = false; void Start() { rigid = GetComponent(); configJoint = GetComponent(); line = GetComponent(); // Create Attach Point based on current position and rotation if(AttachTo == null) { AttachTo = new GameObject("AttachToTransform").transform; } AttachTo.parent = transform.parent; AttachTo.SetPositionAndRotation(transform.position, transform.rotation); // Connect config joint to our AttachPoint's Rigidbody Rigidbody attachRB = AttachTo.gameObject.AddComponent(); attachRB.useGravity = false; attachRB.isKinematic = true; attachRB.constraints = RigidbodyConstraints.FreezeAll; // configJoint.connectedBody = attachRB; Destroy(configJoint); localHandOffset = HandModel.localPosition; localHandOffsetRotation = HandModel.localEulerAngles; initHandColliders(); _priorParent = transform.parent; // Physics Hands typically want to have no parent at all transform.parent = null; } void Update() { updateHandGraphics(); // Line indicating our object is far away drawDistanceLine(); // Check if we should ignore collision with an object that is being remotely pulled towards us checkRemoteCollision(); // Check if hand has gotten too far away checkBreakDistance(); // Our root object is disabled if (!AttachTo.gameObject.activeSelf) { transform.parent = AttachTo; transform.localPosition = Vector3.zero; transform.localRotation = Quaternion.identity; return; } // If we are holding something, move the hands in Update, ignoring physics. if (HoldingObject) { // Call On Grabbed Event if our first grab if (!wasHoldingObject) { OnGrabbedObject(ThisGrabber.HeldGrabbable); } // If we are holding something, move the hands in Update, ignoring physics. //transform.position = AttachTo.position; //transform.rotation = AttachTo.rotation; } else { if (wasHoldingObject) { OnReleasedObject(heldGrabbable); } } wasHoldingObject = HoldingObject; } void FixedUpdate() { // Move object directly to our hand since the hand joint is controlling movement now if (HoldingObject && ThisGrabber.HeldGrabbable.DidParentHands) { rigid.MovePosition(AttachTo.position); rigid.MoveRotation(AttachTo.rotation); } else { // Move using Velocity Vector3 positionDelta = AttachTo.position - transform.position; rigid.velocity = Vector3.MoveTowards(rigid.velocity, (positionDelta * HandVelocity) * Time.fixedDeltaTime, 5f); // Rotate using angular velocity float angle; Vector3 axis; Quaternion rotationDelta = AttachTo.rotation * Quaternion.Inverse(transform.rotation); rotationDelta.ToAngleAxis(out angle, out axis); // Fix rotation angle if (angle > 180) { angle -= 360; } if (angle != 0) { Vector3 angularTarget = angle * axis; angularTarget = (angularTarget * 60f) * Time.fixedDeltaTime; rigid.angularVelocity = Vector3.MoveTowards(rigid.angularVelocity, angularTarget, 20f); } } // Reset Collisions every physics update collisions = new List(); } void initHandColliders() { handColliders = new List(); // Only accept non-trigger colliders. var tempColliders = GetComponentsInChildren(false); for (int x = 0; x < tempColliders.Length; x++) { Collider c = tempColliders[x]; if (!c.isTrigger && c.enabled) { if (ColliderMaterial) { c.material = ColliderMaterial; } handColliders.Add(c); } } // Ignore all other hand collider for (int x = 0; x < handColliders.Count; x++) { Collider thisCollider = handColliders[x]; for (int y = 0; y < handColliders.Count; y++) { Physics.IgnoreCollision(thisCollider, handColliders[y], true); } } } Grabbable remoteIgnoredGrabbable; void checkRemoteCollision() { // Should we unignore this object if we are no longer pulling it towards us? if(remoteIgnoredGrabbable != null && ThisGrabber.RemoteGrabbingGrabbable != remoteIgnoredGrabbable) { // If we are holding this object then let the settings take care of it if(ThisGrabber.HeldGrabbable == remoteIgnoredGrabbable) { remoteIgnoredGrabbable = null; } // Otherwise we dropped it mid flight and should unignore it else { IgnoreGrabbableCollisions(remoteIgnoredGrabbable, false); remoteIgnoredGrabbable = null; } } // Ignore collision with object we started pulling towards us if(ThisGrabber.RemoteGrabbingGrabbable != null && ThisGrabber.RemoteGrabbingGrabbable != remoteIgnoredGrabbable) { remoteIgnoredGrabbable = ThisGrabber.RemoteGrabbingGrabbable; IgnoreGrabbableCollisions(remoteIgnoredGrabbable, true); } } // Line indicating our object is far away void drawDistanceLine() { if (line) { if (Vector3.Distance(transform.position, AttachTo.position) > 0.05f) { line.enabled = true; line.SetPosition(0, transform.position); line.SetPosition(1, AttachTo.position); } else { line.enabled = false; } } } void checkBreakDistance() { if (SnapBackDistance > 0 && Vector3.Distance(transform.position, AttachTo.position) > SnapBackDistance) { transform.position = AttachTo.position; } } void updateHandGraphics() { bool holdingObject = ThisGrabber.HeldGrabbable != null; if (!holdingObject) { if (HandModelOffset) { HandModelOffset.parent = HandModel; HandModelOffset.localPosition = Vector3.zero; HandModelOffset.localEulerAngles = Vector3.zero; } return; } // Position Hand Model if (HandModelOffset && ThisGrabber.HandsGraphics) { HandModelOffset.parent = ThisGrabber.HandsGraphics; HandModelOffset.localPosition = localHandOffset; HandModelOffset.localEulerAngles = localHandOffsetRotation; } } IEnumerator UnignoreAllCollisions() { var thisGrabbable = heldGrabbable; heldGrabbable = null; // Delay briefly so any held objects don't automatically clip yield return new WaitForSeconds(0.1f); IgnoreGrabbableCollisions(thisGrabbable, false); } public void IgnoreGrabbableCollisions(Grabbable grab, bool ignorePhysics) { var grabColliders = grab.GetComponentsInChildren(); // Ignore all other hand collider for (int x = 0; x < grabColliders.Length; x++) { Collider thisGrabCollider = grabColliders[x]; for (int y = 0; y < handColliders.Count; y++) { Physics.IgnoreCollision(thisGrabCollider, handColliders[y], ignorePhysics); } } } public void DisableHandColliders() { for (int x = 0; x < handColliders.Count; x++) { if(handColliders[x] != null && handColliders[x].enabled) { handColliders[x].enabled = false; } } } public void EnableHandColliders() { for (int x = 0; x < handColliders.Count; x++) { if (handColliders[x] != null && handColliders[x].enabled == false) { handColliders[x].enabled = true; } } } public virtual void OnGrabbedObject(Grabbable grabbedObject) { heldGrabbable = grabbedObject; if (DisableHandCollidersOnGrab) { DisableHandColliders(); } // Make the hand ignore the grabbable's colliders else { IgnoreGrabbableCollisions(heldGrabbable, true); } } Transform _priorParent; public virtual void LockLocalPosition() { _priorParent = transform.parent; transform.parent = AttachTo; } public virtual void UnlockLocalPosition() { transform.parent = _priorParent; } public virtual void OnReleasedObject(Grabbable grabbedObject) { if (heldGrabbable != null) { // Make sure hand colliders come back if (DisableHandCollidersOnGrab) { EnableHandColliders(); } // Unignore the grabbable's colliders else { StartCoroutine(UnignoreAllCollisions()); } } heldGrabbable = null; } void OnEnable() { if(DisableGrabber) { DisableGrabber.enabled = false; } if (ThisGrabber) { ThisGrabber.enabled = true; } if (ThisRemoteGrabber) { ThisRemoteGrabber.enabled = true; DisableRemoteGrabber.enabled = false; } // Move events PlayerTeleport.OnBeforeTeleport += LockLocalPosition; PlayerTeleport.OnAfterTeleport += UnlockLocalPosition; PlayerRotation.OnBeforeRotate += LockLocalPosition; PlayerRotation.OnAfterRotate += UnlockLocalPosition; SmoothLocomotion.OnBeforeMove += LockOffset; SmoothLocomotion.OnAfterMove += UnlockOffset; } Vector3 _priorLocalOffsetPosition; public virtual void LockOffset() { _priorLocalOffsetPosition = AttachTo.InverseTransformPoint(transform.position); } public virtual void UnlockOffset() { Vector3 dest = AttachTo.TransformPoint(_priorLocalOffsetPosition); float dist = Vector3.Distance(transform.position, dest); // Only move if gone far enough if (dist > 0.0005f) { transform.position = dest; } } void OnDisable() { if (ThisGrabber) { ThisGrabber.enabled = false; } if (DisableGrabber) { DisableGrabber.enabled = true; } if (ThisRemoteGrabber) { ThisRemoteGrabber.enabled = false; } if (DisableRemoteGrabber) { DisableRemoteGrabber.enabled = true; } // Move events PlayerTeleport.OnBeforeTeleport -= LockLocalPosition; PlayerTeleport.OnAfterTeleport -= UnlockLocalPosition; PlayerRotation.OnBeforeRotate -= LockLocalPosition; PlayerRotation.OnAfterRotate -= UnlockLocalPosition; SmoothLocomotion.OnBeforeMove -= LockOffset; SmoothLocomotion.OnAfterMove -= UnlockOffset; } void OnCollisionStay(Collision collision) { for (int x = 0; x < collision.contacts.Length; x++) { ContactPoint contact = collision.contacts[x]; // Keep track of how many objects we are colliding with if (IsValidCollision(contact.otherCollider) && !collisions.Contains(contact.otherCollider)) { collisions.Add(contact.otherCollider); } } } public bool IsValidCollision(Collider col) { return true; } } }