Search Unity

Question Feet IK smooth positioning error

Discussion in 'Animation' started by Jankaro, Dec 18, 2023.

  1. Jankaro

    Jankaro

    Joined:
    Jan 5, 2019
    Posts:
    4
    Hello!
    I made a script to control IK feet positoninng and body ajustment. Feet positioning is sometimes a bit snapy so I would like to smooth it. But everytime i want to smooth it the IK system fails and feet always position at the same height. So I dont know how to approach it.
    I'm sharing the actual IK script.
    Code (CSharp):
    1. public class BetterIKFootPlacement : MonoBehaviour
    2. {
    3.     [SerializeField] LayerMask walkableLayers;
    4.     [SerializeField] Transform leftFoot;
    5.     [SerializeField] Transform rightFoot;
    6.     [Header("Feet Settings")]
    7.     [Tooltip("Maximum distance can be between feets")]
    8.     [SerializeField] float maxFootDistance = 0.5f;
    9.     [Tooltip("Distance from where the foot transform is to the lowest possible position of the foot.")]
    10.     [Range(0, 1f)]
    11.     [SerializeField] float distanceToGround = 0.1f;
    12.     [SerializeField] float footSmoothTime = 0.2f;
    13.     [Header("Body Settings")]
    14.     [Tooltip("Maximum local altitude the body can be")]
    15.     [SerializeField] float maxBodyHeight = -0.1f;
    16.     [SerializeField] float bodySmoothTime = 0.5f;
    17.  
    18.     private Animator anim;
    19.     private float bodyCV = 0;
    20.  
    21.     private float leftFootRefVel = 0;
    22.     private float rightFootRefVel = 0;
    23.     private Vector3 leftDesiredPos = Vector3.zero;
    24.     private Vector3 rightDesiredPos = Vector3.zero;
    25.  
    26.     void Awake()
    27.     {
    28.         anim = GetComponent<Animator>();
    29.     }
    30.  
    31.     void OnAnimatorIK(int layerIndex)
    32.     {
    33.         if (anim == null) return;
    34.  
    35.         UpdateFootIK(AvatarIKGoal.LeftFoot, leftFoot, ref leftDesiredPos, ref leftFootRefVel);
    36.         UpdateFootIK(AvatarIKGoal.RightFoot, rightFoot, ref rightDesiredPos, ref rightFootRefVel);
    37.     }
    38.  
    39.     void Update()
    40.     {
    41.         // Convert maxBodyHeight to world space
    42.         float worldMaxBodyHeight = transform.root.position.y + maxBodyHeight;
    43.  
    44.         // Determine the target body height based on the lowest foot desired position
    45.         float lowestFootY = Mathf.Min(leftDesiredPos.y, rightDesiredPos.y);
    46.         //Debug.Log("Lowest Foot Y: " + lowestFootY);
    47.  
    48.         bool isInRange = IsFootPositionInRange();
    49.         //Debug.Log("Is In Range: " + isInRange);
    50.  
    51.         // Check if the lowest foot position is within the range
    52.         float targetBodyHeight;
    53.         if (isInRange)
    54.         {
    55.             targetBodyHeight = lowestFootY;
    56.         }
    57.         else
    58.         {
    59.             targetBodyHeight = worldMaxBodyHeight;
    60.         }
    61.         //Debug.Log("Target Body Height: " + targetBodyHeight);
    62.  
    63.         // Smoothly adjust the current body height towards the target body height
    64.         float smoothHeight =
    65.             Mathf.SmoothDamp(transform.position.y, targetBodyHeight, ref bodyCV, bodySmoothTime);
    66.         transform.position = new Vector3(transform.position.x, smoothHeight, transform.position.z);
    67.  
    68.         // Ensure local position is based on the foot positions
    69.         Vector3 localPos = transform.localPosition;
    70.         localPos.y = Mathf.Clamp(localPos.y, maxBodyHeight - maxFootDistance, maxBodyHeight);
    71.         transform.localPosition = localPos;
    72.     }
    73.  
    74.     private void UpdateFootIK(AvatarIKGoal goal, Transform foot, ref Vector3 desiredPos, ref float footVel)
    75.     {
    76.         float footWeight = anim.GetFloat($"IK{goal}Weight");
    77.         RaycastHit hit;
    78.  
    79.         if (Physics.SphereCast(foot.position + Vector3.up * maxFootDistance, distanceToGround, Vector3.down, out hit, maxFootDistance + distanceToGround * 2, walkableLayers) &&
    80.             (foot.position.y < hit.point.y || Mathf.Abs(foot.position.y - hit.point.y) <= distanceToGround))
    81.         {
    82.             anim.SetIKPositionWeight(goal, 1);
    83.             anim.SetIKRotationWeight(goal, 1);
    84.         }
    85.         else
    86.         {
    87.             anim.SetIKPositionWeight(goal, footWeight);
    88.             anim.SetIKRotationWeight(goal, footWeight);
    89.         }
    90.  
    91.         Ray ray = new Ray(anim.GetIKPosition(goal) + Vector3.up * maxFootDistance, Vector3.down);
    92.         Debug.DrawRay(ray.origin, ray.direction, Color.yellow);
    93.  
    94.         if (Physics.Raycast(ray, out hit, Mathf.Infinity, walkableLayers))
    95.         {
    96.             desiredPos = hit.point + Vector3.up * distanceToGround;
    97.             Vector3 footSmoothPos = desiredPos;          
    98.             footSmoothPos.y = Mathf.SmoothDamp(foot.position.y, desiredPos.y, ref footVel, footSmoothTime);
    99.             anim.SetIKPosition(goal, desiredPos);
    100.             //Debug.Log($"{goal} Smooth Pos: " + footSmoothPos);
    101.  
    102.             Vector3 forward = Vector3.ProjectOnPlane(transform.forward, hit.normal);
    103.             anim.SetIKRotation(goal, Quaternion.LookRotation(forward, hit.normal));
    104.         }
    105.     }
    106.  
    107.     private bool IsFootPositionInRange()
    108.     {
    109.         // Convert maxBodyHeight to world space
    110.         float worldMaxBodyHeight = transform.root.position.y + maxBodyHeight;
    111.  
    112.         float worldMinBodyHeight = worldMaxBodyHeight - maxFootDistance;
    113.  
    114.         // Determine the lowest foot's world y-coordinate
    115.         float lowestFootY = Mathf.Min(leftDesiredPos.y, rightDesiredPos.y);
    116.  
    117.         // Check if the lowest foot position is within the body's height range
    118.         return lowestFootY >= worldMinBodyHeight && lowestFootY <= worldMaxBodyHeight;
    119.     }
    120.  
    121.     void OnDrawGizmosSelected()
    122.     {
    123.         if (!Application.isPlaying)
    124.             return;
    125.  
    126.         // Convert maxBodyHeight and minBodyHeight (maxBodyHeight - maxFootDistance) to world space
    127.         float worldMaxBodyHeight = transform.root.position.y + maxBodyHeight;
    128.         float worldMinBodyHeight = worldMaxBodyHeight - maxFootDistance;
    129.  
    130.         // Start and end points for the line in world space
    131.         Vector3 lineStart = new Vector3(transform.position.x, worldMaxBodyHeight, transform.position.z);
    132.         Vector3 lineEnd = new Vector3(transform.position.x, worldMinBodyHeight, transform.position.z);
    133.  
    134.         // Set the color of the Gizmos line
    135.         Gizmos.color = Color.green;
    136.  
    137.         // Draw the line
    138.         Gizmos.DrawLine(lineStart, lineEnd);
    139.  
    140.         // Optionally, draw small spheres at the start and end points for better visibility
    141.         Gizmos.DrawSphere(lineStart, 0.01f); // Size of the sphere can be adjusted
    142.         Gizmos.DrawSphere(lineEnd, 0.01f);
    143.  
    144.         Gizmos.color = Color.red;
    145.         Gizmos.DrawSphere(leftDesiredPos, 0.05f);
    146.         Gizmos.DrawSphere(rightDesiredPos, 0.05f);
    147.     }
    148. }
    149.  
    If you need more context just tell me and I will share whatever thing you need.
    Thanks in advance!
     
    Last edited: Dec 19, 2023