using UnityEngine; namespace Autohand{ [HelpURL("https://app.gitbook.com/s/5zKO0EvOjzUDeT2aiFk3/auto-hand/hand/finger-component")] public class Finger : MonoBehaviour{ [Header("Tips")] [Tooltip("This transfrom will represent the tip/stopper of the finger")] public Transform tip; [Tooltip("This determines the radius of the spherecast check when bending fingers")] public float tipRadius = 0.01f; [Tooltip("This will offset the fingers bend (0 is no bend, 1 is full bend)")] [Range(0, 1f)] public float bendOffset; public float fingerSmoothSpeed = 1; [HideInInspector] public float secondaryOffset = 0; float currBendOffset = 0; float bend = 0; [SerializeField] [HideInInspector] Quaternion[] minGripRotPose; [SerializeField] [HideInInspector] Vector3[] minGripPosPose; [SerializeField] [HideInInspector] Quaternion[] maxGripRotPose; [SerializeField] [HideInInspector] Vector3[] maxGripPosPose; [SerializeField] [HideInInspector] Transform[] fingerJoints; public Transform[] FingerJoints { get { return fingerJoints; } } float lastHitBend; Collider[] results = new Collider[2]; void Update() { SlowBend(); } /// Forces the finger to a bend until it hits something on the given physics layer /// The number of steps and physics checks it will make lerping from 0 to 1 public bool BendFingerUntilHit(int steps, int layermask) { ResetBend(); lastHitBend = 0; for(float i = 0; i <= steps / 5f; i++) { results[0] = null; lastHitBend = i / (steps / 5f); for(int j = 0; j < fingerJoints.Length; j++) { fingerJoints[j].localPosition = Vector3.Lerp(minGripPosPose[j], maxGripPosPose[j], lastHitBend); fingerJoints[j].localRotation = Quaternion.Lerp(minGripRotPose[j], maxGripRotPose[j], lastHitBend); } Physics.OverlapSphereNonAlloc(tip.transform.position, tipRadius, results, layermask, QueryTriggerInteraction.Ignore); if(results[0] != null) { lastHitBend = Mathf.Clamp01(lastHitBend); if(i == 0) return true; break; } } lastHitBend -= (5f / steps); for(int i = 0; i <= steps / 10f; i++) { results[0] = null; lastHitBend += (1f / steps); for(int j = 0; j < fingerJoints.Length; j++) { fingerJoints[j].localPosition = Vector3.Lerp(minGripPosPose[j], maxGripPosPose[j], lastHitBend); fingerJoints[j].localRotation = Quaternion.Lerp(minGripRotPose[j], maxGripRotPose[j], lastHitBend); } Physics.OverlapSphereNonAlloc(tip.transform.position, tipRadius, results, layermask, QueryTriggerInteraction.Ignore); if(results[0] != null) { bend = lastHitBend; currBendOffset = lastHitBend; lastHitBend = Mathf.Clamp01(lastHitBend); return true; } if(lastHitBend >= 1) { lastHitBend = Mathf.Clamp01(lastHitBend); return true; } } return false; } /// Bends the finger unless its hitting something /// 0 is no bend / 1 is full bend public bool UpdateFingerBend(float bend, int layermask) { var results = new Collider[]{ null }; Physics.OverlapSphereNonAlloc(tip.transform.position, tipRadius, results, layermask, QueryTriggerInteraction.Ignore); if(this.bend > bend || results[0] == null){ this.bend = bend; for(int i = 0; i < fingerJoints.Length; i++) { fingerJoints[i].localPosition = Vector3.Lerp(minGripPosPose[i], maxGripPosPose[i], currBendOffset+secondaryOffset); fingerJoints[i].localRotation = Quaternion.Lerp(minGripRotPose[i], maxGripRotPose[i], currBendOffset+secondaryOffset); } return true; } return false; } public void UpdateFinger() { for(int i = 0; i < fingerJoints.Length; i++) { fingerJoints[i].localPosition = Vector3.Lerp(minGripPosPose[i], maxGripPosPose[i], currBendOffset+secondaryOffset); fingerJoints[i].localRotation = Quaternion.Lerp(minGripRotPose[i], maxGripRotPose[i], currBendOffset+secondaryOffset); } } public void UpdateFinger(float bend) { this.bend = bend; for(int i = 0; i < fingerJoints.Length; i++) { fingerJoints[i].localPosition = Vector3.Lerp(minGripPosPose[i], maxGripPosPose[i], currBendOffset+secondaryOffset); fingerJoints[i].localRotation = Quaternion.Lerp(minGripRotPose[i], maxGripRotPose[i], currBendOffset+secondaryOffset); } } /// Forces the finger to a bend ignoring physics and offset /// 0 is no bend / 1 is full bend public void SetFingerBend(float bend) { this.bend = bend; for(int i = 0; i < fingerJoints.Length; i++) { fingerJoints[i].localPosition = Vector3.Lerp(minGripPosPose[i], maxGripPosPose[i], bend); fingerJoints[i].localRotation = Quaternion.Lerp(minGripRotPose[i], maxGripRotPose[i], bend); } } /// Sets the current finger to a bend without interfering with the target /// 0 is no bend / 1 is full bend public void SetCurrentFingerBend(float bend) { currBendOffset = bend; for(int i = 0; i < fingerJoints.Length; i++) { fingerJoints[i].localPosition = Vector3.Lerp(minGripPosPose[i], maxGripPosPose[i], bend); fingerJoints[i].localRotation = Quaternion.Lerp(minGripRotPose[i], maxGripRotPose[i], bend); } } //This function smooths the finger bend so you can change the grip over a frame and wont be a jump void SlowBend(){ var offsetValue = bendOffset + bend; if(currBendOffset != offsetValue) currBendOffset = Mathf.MoveTowards(currBendOffset, offsetValue, Mathf.Pow(Mathf.Abs(currBendOffset - offsetValue)*2, 0.5f) * Time.deltaTime * fingerSmoothSpeed * 6 + fingerSmoothSpeed * Time.deltaTime); } [ContextMenu("ResetBend")] public void ResetBend() { for(int i = 0; i < fingerJoints.Length; i++) { fingerJoints[i].localPosition = minGripPosPose[i]; fingerJoints[i].localRotation = minGripRotPose[i]; } } [ContextMenu("Grip")] public void Grip() { for(int i = 0; i < fingerJoints.Length; i++) { fingerJoints[i].localPosition = maxGripPosPose[i]; fingerJoints[i].localRotation = maxGripRotPose[i]; } } /// Returns the bend the finger ended with from the last BendFingerUntilHit() call public float GetLastHitBend() { return lastHitBend; } [ContextMenu("Set Open Finger Pose")] public void SetMinPose(){ int GetKidsCount(Transform obj, ref int count) { if(obj != tip){ count++; for(int k = 0; k < obj.childCount; k++) { GetKidsCount(obj.GetChild(k), ref count); } } return count; } int points = 0; GetKidsCount(transform, ref points); minGripPosPose = new Vector3[points]; minGripRotPose = new Quaternion[points]; fingerJoints = new Transform[points]; int i = 0; AssignChildrenPose(transform, ref i); void AssignChildrenPose(Transform obj, ref int index) { if(obj != tip){ AssignPoint(index, obj.localPosition, obj.localRotation, obj); index++; for(int j = 0; j < obj.childCount; j++) { AssignChildrenPose(obj.GetChild(j), ref index); } } } void AssignPoint(int point, Vector3 pos, Quaternion rot, Transform joint) { minGripPosPose[point] = pos; minGripRotPose[point] = rot; fingerJoints[point] = joint; } } [ContextMenu("Set Closed Finger Pose")] public void SetMaxPose(){ int GetKidsCount(Transform obj, ref int count) { if(obj != tip){ count++; for(int k = 0; k < obj.childCount; k++) { GetKidsCount(obj.GetChild(k), ref count); } } return count; } int points = 0; GetKidsCount(transform, ref points); maxGripPosPose = new Vector3[points]; maxGripRotPose = new Quaternion[points]; fingerJoints = new Transform[points]; int i = 0; AssignChildrenPose(transform, ref i); void AssignChildrenPose(Transform obj, ref int index){ if(obj != tip){ AssignPoint(index, obj.localPosition, obj.localRotation, obj); index++; for(int j = 0; j < obj.childCount; j++) { AssignChildrenPose(obj.GetChild(j), ref index); } } } void AssignPoint(int point, Vector3 pos, Quaternion rot, Transform joint) { maxGripPosPose[point] = pos; maxGripRotPose[point] = rot; fingerJoints[point] = joint; } } public void CopyPose(Finger finger) { maxGripPosPose = new Vector3[finger.maxGripPosPose.Length]; finger.maxGripPosPose.CopyTo(maxGripPosPose, 0); maxGripRotPose = new Quaternion[finger.maxGripRotPose.Length]; finger.maxGripRotPose.CopyTo(maxGripRotPose, 0); minGripPosPose = new Vector3[finger.minGripPosPose.Length]; finger.minGripPosPose.CopyTo(minGripPosPose, 0); minGripRotPose = new Quaternion[finger.minGripRotPose.Length]; finger.minGripRotPose.CopyTo(minGripRotPose, 0); fingerJoints = new Transform[finger.fingerJoints.Length]; finger.fingerJoints.CopyTo(fingerJoints, 0); } public bool IsMinPoseSaved() { return minGripPosPose.Length != 0; } public bool IsMaxPoseSaved() { return maxGripPosPose.Length != 0; } public float GetCurrentBend() { return currBendOffset+secondaryOffset; } private void OnDrawGizmos() { if(tip == null) return; Gizmos.color = Color.cyan; Gizmos.DrawWireSphere(tip.transform.position, tipRadius); } private void OnDrawGizmosSelected() { Gizmos.color = Color.blue; DrawSphereBetweenChild(transform); void DrawSphereBetweenChild(Transform transform){ for (int i = 0; i < transform.childCount; i++) { var childTransform = transform.GetChild(i); if (childTransform.TryGetComponent(out CapsuleCollider cap)) { Gizmos.DrawWireSphere(Vector3.Lerp(transform.position, cap.bounds.center, 0.5f), tipRadius); } DrawSphereBetweenChild(childTransform); } } } } }