You might have wondered how most of the games accurately manage to achieve accurate foot placement for their character on not so smooth ground. We know that grounds in games are never a smooth plane just like in the real world where we see pits and bumps everywhere. So how do other games we see manage to properly align their character on bumpy roads despite having static animation? Well, you guessed it, it's "Inverse Kinematic" IK in short. IK has been used by our hand naturally, eg. when we want to grab a cup of coffee laying on the table that is close to us, we naturally bend our arm just right so that we can accurately reach the cup. IK has been in use in robotics for a long time now. So how can we use it? It is pretty easy to achieve in Unity since all the calculation for IK is already done there. I will just guide you on how to achieve that goal using Unity. It's easy.

Initial Setup
1) We need a character whose rig is set to humanoid.

2) Set IK Pass to True in animator component

What do we want?

  • We need a ground position for our character foot to be placed. This is the position where the foot will try to reach.
  • We need to calculate rotation according to slope normal so that we can properly rotate our characters' feet to properly align with the surface.

Let's see how we can implement all these with the following code:

float ikWeight = 1 f; //ik will completly override animation
private Animator _animator;

private void Awake() {
  _animator = GetComponent < Animator > ();
}

private void OnAnimatorIK(int layerIndex) {
  Debug.Log("Trying to adjust pos");
  if (_animator) {
    /* this is used to control the blend between animation and foot Ik placement , 0 = no ik and 1 means ik completely takes over, 
    value between 0 and 1 is blend between 2 */
    _animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, ikWeight);
    _animator.SetIKRotationWeight(AvatarIKGoal.LeftFoot, ikWeight);

    _animator.SetIKPositionWeight(AvatarIKGoal.RightFoot, ikWeight);
    _animator.SetIKRotationWeight(AvatarIKGoal.RightFoot, ikWeight);

    Vector3 rayDir = Vector3.down;
    // for left foot
    AdjustFootIK(AvatarIKGoal.LeftFoot, rayDir);
    //for the right foot
    AdjustFootIK(AvatarIKGoal.RightFoot, rayDir);
  }
}

private void AdjustFootIK(AvatarIKGoal ikGoal, Vector3 rayDir) {
  Vector3 rayStartPos = _animator.GetIKPosition(ikGoal) + Vector3.up;
  // raycast origin starts from the foot location + offset of 1 unit in up dir   
  bool isGround = Physics.Raycast(rayStartPos, rayDir, out RaycastHit hitInfo, 2 f, groundMask);
  // check for ground detection   
  if (isGround) // touching ground   
  {
    // point where the raycast hit
    Vector3 hitPos = hitInfo.point;
    // offset foot by certain value along normal direction  
    hitPos += hitInfo.normal * footPlacementOffset;
    // set the ik position for foot
    _animator.SetIKPosition(ikGoal, hitPos);
    // calculate new rotation for foot according to change in normal   
    Quaternion lookDir = Quaternion.LookRotation(transform.forward, hitInfo.normal);
    // set new foot pos 
    _animator.SetIKRotation(ikGoal, lookDir);
  }
}
Full Code
Final Result

Following the steps above will help you set up proper Foot placement in unity. If you want to know more about Inverse Kinematic you can check out official docs.