One of the hardest things to get correct in a game is the placement of a character's hands. In this article, I will use Inverse Kinematics (IK) to give that extra polish to my game when ledge grabbing. This concept can be used to add that extra polish to any animation that requires the correct placement of hands or feet.

0:00
/
Normal Ledge Grabbing
0:00
/
Ledge Grabbing using inverse kinematics.

Enabling IK on the Animator.

The first step is to enable an IK Pass on a layer in the Animator.

  • Open the Animator and Edit the Animator Controller for your character.
  • In the Layers tab click the Settings cog for the Layer that has the Animation that you want to use IK.
  • Check the IK Pass checkbox.
Enabling IK on the Animator
⚠️
IK will not work without this step

The Code

Now that we have made sure that our character will be able to use IK, it is time to write some code. There are a couple of different places where Unity provides us the ability to use IK.

  1. Monobehaviour.OnAnimatorIK(int)
  2. StateMachineBehavior.OnStateExit(Animator animator, AnimatorStateInfo animatorStateInfo, int layerIndex)

There are 2 methods on the Animator that we will need to use IK.

  1. Animator.SetIKPositionWeight- "Sets the weight of an IK goal" (LeftFoot, RightFoot, LeftHand, RightHand) (0 = at the original animation before IK, 1 = at the goal).
  2. Animator.SetIKPosition- "Sets the position of an IK goal."

I already have a MonoBehaviour that uses the OnTriggerEnter(Collider other) callback that triggers an event to notify my character controller that it should be ledge-grabbing.

These scripts come from the Unity 3rd Person Combat & Traversal course on Game Dev TV.

Unity 3rd Person Combat System Complete Course!
Make a complete 3rd Person Combat System for and RPG In Unity!
Section Intro - Advanced Movement
Make a complete 3rd Person Combat System for and RPG In Unity!
⚠️
The OnAnimaterIK callback must be on a script that is on the same GameObject that the Animator is on in order for it to get called.

State Machine script despite its name is actually a MonoBehaviour and should not be confused with the Unity StateMachineBehavior.

namespace LegendOfTheHero.StateMachines
{
    public class CombatAndTraversalStateMachine :
        StateMachine<CombatAndTraversalBaseState, CombatAndTraversalStateFactory, CombatAndTraversalStateMachine>
    {        
        [field: SerializeField] public LedgeDetector LedgeDetector { get; private set; }

        #region Unity Methods

        #region Init

        protected override void OnEnable()
        {   
            if (LedgeDetector) LedgeDetector.OnLedgeDetect += LedgeDetectorOnOnLedgeDetect;
        }

        protected override void OnDisable()
        {
            base.OnDisable();
            
            if (LedgeDetector) LedgeDetector.OnLedgeDetect -= LedgeDetectorOnOnLedgeDetect;
        }

        #endregion

        #endregion

        private void LedgeDetectorOnOnLedgeDetect(Vector3 ledgeForward, Vector3 closestPoint)
        {
            ((CombatHangingState)Factory.Hanging).SetPositions(ledgeForward, closestPoint);
        }
    }
MonoBehavior that is on the same level as the Animator
namespace LegendOfTheHero.Ledges
{
    [RequireComponent(typeof(Collider), typeof(Rigidbody))]
    public class LedgeDetector : MonoBehaviour
    {
        public event Action<Vector3, Vector3> OnLedgeDetect;

        private void OnTriggerEnter(Collider other)
        {
            OnLedgeDetect?.Invoke(other.transform.forward, other.ClosestPointOnBounds(transform.position));
        }
    }
}
Ledge Detector Script

Adding the OnAnimatorIK Unity callback.

Since the code that is controlling my character is so long I will be putting all of the logic for the IK in my Ledge Detection script. I will add a method called OnLedgeAnimatorIK and call that from within my character-controlling script OnAnimatorIK Unity callback.

namespace LegendOfTheHero.StateMachines
{
    public class CombatAndTraversalStateMachine :
        StateMachine<CombatAndTraversalBaseState, CombatAndTraversalStateFactory, CombatAndTraversalStateMachine>
    {        
        #region Unity Methods

        #region Init /*...*/ #endregion

        #endregion

        private void OnAnimatorIK(int layerIndex)
        {
            if (LedgeDetector) LedgeDetector.OnLedgeAnimatorIK(Animator);
        }

        private void LedgeDetectorOnOnLedgeDetect(Vector3 ledgeForward, Vector3 closestPoint) { /*...*/ }
    }
}

namespace LegendOfTheHero.Ledges
{
    [RequireComponent(typeof(Collider), typeof(Rigidbody))]
    public class LedgeDetector : MonoBehaviour
    {
        public event Action<Vector3, Vector3> OnLedgeDetect;

        private void OnTriggerEnter(Collider other)
        {
            OnLedgeDetect?.Invoke(other.transform.forward, other.ClosestPointOnBounds(transform.position));
        }

        public void OnLedgeAnimatorIK([NotNull] Animator animator)
        {
            
        }
    }
}

Setting IK weights and Positions

To set the weight we use public void SetIKPositionWeight(AvatarIKGoalgoal, float value);

This function sets a weight value in the range 0..1 to determine how far between the start and goal positions the IK will aim. The position itself is set separately using SetIKPosition.

To set the position we use public void SetIKPosition(AvatarIKGoalgoal, Vector3goalPosition);

An IK goal is a target position and rotation for a specific body part. Unity can calculate how to move the part toward the target from the starting point (ie, the current position and rotation obtained from the animation).

This function sets the position of the ultimate goal in world space; the actual point in space where the body part ends up is also influenced by a weight parameter that specifies how far between the start and the goal the IK should aim (a value in the range 0..

To set the weight and position we need to know 3 things.

  1. The position to use. ( We will use the position of a Transform object)
  2. The Weight of to use. (OnAnimatorIK will run on all animations in a Layer with IK Pass set to true. We will set this to 1 when we are hanging and 0 when we are not.)
  3. The AvatarIKGoal that we want to set. (We will be setting both hands)
namespace LegendOfTheHero.Ledges
{
    [RequireComponent(typeof(Collider), typeof(Rigidbody))]
    public class LedgeDetector : MonoBehaviour
    {
        [SerializeField] private Transform leftHandPosition;
        [SerializeField] private Transform rightHandPosition;
        
        private bool _hasLeftHandPosition;
        private bool _hasRightHandPosition;
        private bool _useIK;
        
        /// <summary>
        /// Use to set whether or not we are in a state that needs the IK.
        // If you are not useing the scripts from the Game Dev TV you can set this in OnTriggerEnter and OnTriggerExit.
        /// Originally used OnTrigger Enter and Exit to set this, but due to
        /// the character moving it's position OnTriggerExit was triggered to early. 
        /// </summary>
        public bool UseIK
        {
            set => _useIK = value;
        }

        public event Action<Vector3, Vector3> OnLedgeDetect;

        private void Awake()
        {
            _hasRightHandPosition = rightHandPosition;
            _hasLeftHandPosition = leftHandPosition;
        }

        /*...*/
    }
}
Adding the Need Variables

We also need to make sure that the use IK gets set to true when we start hanging and false when we stop hanging.

namespace LegendOfTheHero.StateMachines
{
    public class CombatHangingState: CombatAndTraversalBaseState
    {
        /*...*/

        #region State Implementation

        public override void EnterState()
        {
            /*...*/

            context.EnableController = false;
            Transform transform;
            (transform = context.transform).rotation = Quaternion.LookRotation(_ledgeForward, Vector3.up);
            transform.position =
                _closestPoint - (context.LedgeDetector.transform.position - transform.position);
            context.EnableController = true;

            context.Animator.CrossFadeInFixedTime(HangHash, AnimationDampTime);
            context.AppliedMovement = Vector3.zero;
            
            // Make sure we let the Ledge Detector know to us IK
            context.LedgeDetector.UseIK = true;
        }

        /*...*/

        protected override void ExitState()
        {
            // Make sure we let the Ledge Detector know to us IK
            context.LedgeDetector.UseIK = false;
            /*...*/
        }

        protected override void InitializeSubState() {}

        #endregion

        /*...*/

        public void SetPositions(Vector3 ledgeForward, Vector3 closestPoint)
        {
            _ledgeForward = ledgeForward;
            _closestPoint = closestPoint;
        }
    }
}
Notifying Ledge Detector when to Use IK

Back in Ledge Detector let's set the IK weights and position.

namespace LegendOfTheHero.Ledges
{
    [RequireComponent(typeof(Collider), typeof(Rigidbody))]
    public class LedgeDetector : MonoBehaviour
    {
        /*...*/

        public void OnLedgeAnimatorIK([NotNull] Animator animator)
        {
            // Set the weight to 1 if we are using ik else set it to 0 
            float weight = _useIK ? 1 : 0; 
            // Set the Left Hand Weight if we have a position for it
            animator.SetIKPositionWeight(AvatarIKGoal.LeftHand, _hasLeftHandPosition ? weight : 0);
            // Set the Right Hand Weight if we have a position for it
            animator.SetIKPositionWeight(AvatarIKGoal.RightHand, _hasRightHandPosition ? weight : 0);


            // Set the Left Hand Position if we have a position for it
            if (_hasLeftHandPosition) 
                animator.SetIKPosition(AvatarIKGoal.LeftHand, leftHandPosition.position);
            // Set the Right Hand Position if we have a position for it
            if (_hasRightHandPosition) 
                animator.SetIKPosition(AvatarIKGoal.RightHand, rightHandPosition.position);
        }
    }
}

Controlling the placement of the hands in Unity.

Now all that is needed is to have a position that we can set our hands to.

Add a Game Object to your character model that can be used for the left and right hands.

Setting the Game Objects in Unity

To get the placement to look good, place the hand position objects in front of the hands while hanging.

Fine Tune the placement.

Now enter play mode and go grab a ledge. Once you are hanging you can move the position of the hands to get them just right.

Fine-tuning the hand placement
⚠️
Since these changes are being made at runtime they will be lost when exiting play mode. Make sure to take note of the values so you can set them.

Conclusion

IK is a very useful tool for getting a more precise placement of hands and feet in animation.  Using this system you can make sure that your character animations look good in your game.

Unity Includes a simple way to use IK out of the box. If you want a more advanced way of dealing with IK check out Unity's Animation Rigging package.

Animation Rigging | Animation Rigging | 1.0.3

You can even use IK to create procedural animations in your game, see https://learn.unity.com/tutorial/using-animation-rigging-damped-transform# for more details.