Getting Started with IK

Checked with version: 2018.1

-

Difficulty: Beginner

Inverse Kinematics (IK) is the process of calculating rotations for a set of joints in order to make the final joint reach some position. For example, IK can be used to calculate the rotations for shoulders, elbows and wrist which are required for the fingers to reach a point in space.

There are a number of approaches to creating IK solutions in Unity, which can be complex and difficult to understand. However, sometimes a simple solution is all that is required to achieve a reasonable end result. This tutorial covers a simple solution for 3 or 4 jointed limbs, and shows how to support developers using the solution using some custom Unity Editor scripting.

Setting up a test scene for development.

You will need a model of an arm segment which is setup with the forward axis matching the unity forward axis (x:0,y:0,z:1) with the pivot point set to the same position the joint should rotate around. Segment.fbx is provided in the sample project which fills these requirements.

correct position and orientation of the segment model

Create an empty gameobject which will be the root of your arm. Create a second, empty child gameobject which operates as the main pivot. Then, attach three segments to create an arm. Finally, create an empty GameObject at the end of the hierarchy and name it 'Tip'. This position is the point where you want to match your target position.

Hierarchy setup

Using the position handles in the Scene view, lay out your GameObjects so that they resemble a mechanical arm, similar to the screenshot below. Notice the tip GameObject is positioned at the very end of the last segment.

correct position and orientation of hierarchy

Now is a good time to save your Scene.

Writing Some Code!

The actual IK solver

Our IK algorithm is going to use the law of cosines and a little bit of cheating to work out the final effector angle. The effector is the tip’s parent transform. The pivot is the first transform after the root. That leaves two transforms in the middle of our chain. If we manually position the pivot and the effector, we can use the law of cosines to work out the correct rotations for the two middle transforms.

The Monobehaviour class

Create a new script called SimpleIKSolver.cs. We need to add the fields required to hold our chain of transforms, and the data we need to perform the calculations.

Code snippet

public class SimpleIKSolver : MonoBehaviour
{

    public Transform pivot, upper, lower, effector, tip;
    public Vector3 target = Vector3.forward;
    public Vector3 normal = Vector3.up;




    float upperLength, lowerLength, effectorLength, pivotLength;
    Vector3 effectorTarget, tipTarget;
}

This behaviour class should be assigned to the pivot gameobject. But before you do this, we will create a method to make the component inspector setup less painful! Add the below method to your class, then add it as a component to the pivot gameobject.

Code snippet

void Reset()
{
{
    pivot = transform;
    try
    {
        upper = pivot.GetChild(0);
        lower = upper.GetChild(0);
        effector = lower.GetChild(0);
        tip = effector.GetChild(0);
    }
    catch (UnityException)
    {
        Debug.Log("Could not find required transforms, please assign manually.");
    }
}

This method automatically runs when the component is first attached to a gameobject. It tries to find the required gameobjects and assign them to the correct slots in the inspector. However, if it can't find the gameobjects, it will print out a helpful message.

We now need to collect some data for use in our trigonometry calculations. The data we need is the length of each segment. We should do this at runtime in the Awake method, so that we know it is always correct for the current setup of the hierarchy.

Code snippet

void Awake()
{
    upperLength = (lower.position - upper.position).magnitude;
    lowerLength = (effector.position - lower.position).magnitude;
    effectorLength = (tip.position - effector.position).magnitude;
    pivotLength = (upper.position - pivot.position).magnitude;
}

Next, the Update function is going to calculate the target positions and call the yet-to-be-written Solve method.

Code snippet

void Update()
{
    tipTarget = target;
    effectorTarget = target + normal * effectorLength;
    Solve();
}

This is where we start to cheat a little. We directly set the orientation of the final segment using the normal field. This field could be set to a constant value, or dynamically changed based on collision normal or some other mechanism. As we know the orientation of the final segment, we can calculate the target position of the effector transform, using our known orientation and the length of effector.

The IK Algorithm

The complete IK algorithm is listed below for your copy/paste convenience.

We know the last joint orientation, which leaves three more joints to solve. Let's cheat some more, and rotate the pivot transform so it is looking directly at the tipTarget.

Code snippet

var pivotDir = effectorTarget - pivot.position;
pivot.rotation = Quaternion.LookRotation(pivotDir);

That leaves two rotations we need to calculate, fortunately this is a well known problem which we can solve using trigonometry (law of cosines). The variables 'a', 'b' and 'c' are the lengths of the triangle formed by the transform chain.

Code snippet

var upperToTarget = (effectorTarget - upper.position);
    var a = upperLength;
    var b = lowerLength;
    var c = upperToTarget.magnitude;

We can then apply the law of cosines to calculate the internal angles of the abc triangle.

var B = Mathf.Acos((c * c + a * a - b * b) / (2 * c * a)) * Mathf.Rad2Deg; var C = Mathf.Acos((a * a + b * b - c * c) / (2 * a * b)) * Mathf.Rad2Deg;

Note, we need to check that C is a valid number, as sometime we can form an impossible to solve triangle with our joint orientations. if the C variable is ok, the next step is to convert the angles into rotations which we can apply to our segments. Also, as we are working with local rotations, we rotate around global vectors (Vector3.right).

Code snippet

if (!float.IsNaN(C))
{
{
    var upperRotation = Quaternion.AngleAxis((-B), Vector3.right);
    upper.localRotation = upperRotation;
    var lowerRotation = Quaternion.AngleAxis(180 - C, Vector3.right);
    lower.localRotation = lowerRotation;
}

Finally, we orient the last segment (effector) to point at the tipTarget.

Code snippet

var effectorRotation = Quaternion.LookRotation(tipTarget - effector.position);
effector.rotation = effectorRotation;

The Complete Method:

Code snippet

void Solve()
{
    var pivotDir = effectorTarget - pivot.position;
    pivot.rotation = Quaternion.LookRotation(pivotDir);


    var upperToTarget = (effectorTarget - upper.position);
    var a = upperLength;
    var b = lowerLength;
    var c = upperToTarget.magnitude;


    var B = Mathf.Acos((c * c + a * a - b * b) / (2 * c * a)) * Mathf.Rad2Deg;
    var C = Mathf.Acos((a * a + b * b - c * c) / (2 * a * b)) * Mathf.Rad2Deg;


    if (!float.IsNaN(C))
    {
        var upperRotation = Quaternion.AngleAxis((-B), Vector3.right);
        upper.localRotation = upperRotation;
        var lowerRotation = Quaternion.AngleAxis(180 - C, Vector3.right);
        lower.localRotation = lowerRotation;
    }
    var effectorRotation = Quaternion.LookRotation(tipTarget - effector.position);
    effector.rotation = effectorRotation;
}

At this point, the IK solve is complete and should work in your editor. But it is not very easy to debug or use at the moment, so we need to add some editor controls which let us test the system is working as expected.

Scripting the Editor

Create a new script in an Editor folder, call it SimpleIKSolverEditor.cs. Edit the script and paste in this code:

Code snippet

[CustomEditor(typeof(SimpleIKSolver))]
public class SimpleIKSolverEditor : Editor
{
}

We are now ready to start customising the editor!

Customising the Inspector

First, we will modify the inspector to warn us when any of the transforms required for our solver are missing. If any of the transforms are null, we will display an error message directly in the inspector. Then, we draw the default inspector as the default functionality is good enough for this component.

Code snippet

public override void OnInspectorGUI()
{
    var s = target as SimpleIKSolver;
    if (s.pivot == null || s.upper == null || s.lower == null | s.effector == null || s.tip == null)
        EditorGUILayout.HelpBox("Please assign Pivot, Upper, Lower, Effector and Tip transforms.", MessageType.Error);
    base.OnInspectorGUI();
}

Customised Inspector

Customising the Gizmo

Second, we are going to write a gizmo function for showing the same error information from the inspector, in the sceneview. We can also draw some helpful information, showing the distance from our target position to the tip position. The default labels in the sceneview are a bit difficult to see, so create an guistyle which we can use to make them more visible.

Code snippet

static GUIStyle errorBox;


void OnEnable()
{
    errorBox = new GUIStyle(EditorGUIUtility.GetBuiltinSkin(EditorSkin.Scene).box);
    errorBox.normal.textColor = Color.red;
}

This is the function that will draw the gizmo in the scene view. We pass in the guistyle we just created to the Handles.Label method. If any of the transforms are missing, we immediately exit the method, as none of the further drawing operations would work.

Finally, we draw a blue line showing the segments in our chain, and a dotted line showing the delta between the tip position and target position.

Code snippet

[DrawGizmo(GizmoType.Selected)]
static void OnDrawGizmosSelected(SimpleIKSolver siks, GizmoType gizmoType)
{
    Handles.color = Color.blue;
    if (siks.pivot == null)
    {
        Handles.Label(siks.transform.position, "Pivot is not assigned", errorBox);
        return;
    }
    if (siks.upper == null)
    {
        Handles.Label(siks.pivot.position, "Upper is not assigned", errorBox);
        return;
    }
    if (siks.lower == null)
    {
        Handles.Label(siks.upper.position, "Lower is not assigned", errorBox);
        return;
    }
    if (siks.effector == null)
    {
        Handles.Label(siks.lower.position, "Effector is not assigned", errorBox);
        return;
    }
    if (siks.tip == null)
    {
        Handles.Label(siks.effector.position, "Tip is not assigned", errorBox);
        return;
    }
    Handles.DrawPolyLine(siks.pivot.position, siks.upper.position, siks.lower.position, siks.effector.position, siks.tip.position);
    Handles.DrawDottedLine(siks.tip.position, siks.target, 3);
    Handles.Label(siks.upper.position, "Upper");
    Handles.Label(siks.effector.position, "Effector");
    Handles.Label(siks.lower.position, "Lower");
    Handles.Label(siks.target, "Target");
    var distanceToTarget = Vector3.Distance(siks.target, siks.tip.position);
    var midPoint = Vector3.Lerp(siks.target, siks.tip.position, 0.5f);
    Handles.Label(midPoint, string.Format("Distance to Target: {0:0.00}", distanceToTarget));
}

An error message in the scene view

Customising the Scene View

The final two functions draw a Rotation handle at each pivot point, allowing you to rotate each joint in the chain without having to deselect the main pivot transform. We also use a position handle for the target position, and a rotation handle to modify the normal field.

The rotation handles

Code snippet

public void OnSceneGUI()
{
{
    var siks = target as SimpleIKSolver;
    RotationHandle(siks.effector);
    RotationHandle(siks.lower);
    RotationHandle(siks.upper);
    siks.target = Handles.PositionHandle(siks.target, Quaternion.identity);
    var normalRotation = Quaternion.LookRotation(Vector3.forward, siks.normal);
    normalRotation = Handles.RotationHandle(normalRotation, siks.tip.position);
    siks.normal = normalRotation * Vector3.up;
}

This method will draw a rotation handle for any transform. We use it above on the upper, lower and effector transforms. It will only change the rotation value if it has been changed, this is detected by using the BeginChangeCheck and EndChangeCheck methods. If it was changed, we record the state of the transform so that the Undo/Redo system will work correctly, then assign the new rotation value to the transform.rotation property.

Code snippet

void RotationHandle(Transform transform)
{
    if (transform != null)
    {
        EditorGUI.BeginChangeCheck();
        var rotation = Handles.RotationHandle(transform.rotation, transform.position);
        if (EditorGUI.EndChangeCheck())
        {
            Undo.RecordObject(transform, "Rotate");
            transform.rotation = rotation;
        }
    }
}

We now have a simple IK system with a nice editor interface, ready to extend and use with any chain of transforms.

Complete IK solution

Scripting

  1. Scripts as Behaviour Components
  2. Variables and Functions
  3. Conventions and Syntax
  4. C# vs JS syntax
  5. IF Statements
  6. Loops
  7. Scope and Access Modifiers
  8. Awake and Start
  9. Update and FixedUpdate
  10. Vector Maths
  11. Enabling and Disabling Components
  12. Activating GameObjects
  13. Translate and Rotate
  14. Look At
  15. Linear Interpolation
  16. Destroy
  17. GetButton and GetKey
  18. GetAxis
  19. OnMouseDown
  20. GetComponent
  21. Delta Time
  22. Data Types
  23. Classes
  24. Instantiate
  25. Arrays
  26. Invoke
  27. Enumerations
  28. Switch Statements
  1. Properties
  2. Ternary Operator
  3. Statics
  4. Method Overloading
  5. Generics
  6. Inheritance
  7. Polymorphism
  8. Member Hiding
  9. Overriding
  10. Interfaces
  11. Extension Methods
  12. Namespaces
  13. Lists and Dictionaries
  14. Coroutines
  15. Quaternions
  16. Delegates
  17. Attributes
  18. Events
  1. Introduction to ECS
  2. Introduction to the Entity Component System and C# Job System
  3. ECS Overview
  4. Implementing Job System
  5. Implementing ECS
  6. Using the Burst Compiler
  1. Building a Custom Inspector
  2. The DrawDefaultInspector Function
  3. Adding Buttons to a Custom Inspector
  4. Unity Editor Extensions – Menu Items
  5. An Introduction to Editor Scripting
  6. Creating a Spline Tool
  7. Getting Started with IK
  1. Simple Clock
  2. MonoDevelop's Debugger
  3. Unity Editor Extensions – Menu Items
  4. Creating Meshes
  1. Mastering Unity Project Folder Structure - Version Control Systems
  1. Installation and Setup of Visual Studio
  2. Editing Your Game Code with Visual Studio
  3. Debugging Unity games in Visual Studio
  1. Scripting Primer and Q&A
  2. Scripting Primer and Q&A - Continued
  3. Scripting Primer and Q&A - Continued (Again)
  4. Persistence - Saving and Loading Data
  5. Object Pooling
  6. Introduction to Scriptable Objects
  7. How to communicate between Scripts and GameObjects
  8. Coding in Unity for the Absolute Beginner
  9. Sound Effects & Scripting
  10. Editor Scripting Intro
  11. Writing Plugins
  12. Property Drawers & Custom Inspectors
  13. Events: Creating a simple messaging system
  14. Ability System with Scriptable Objects
  15. Character Select System with Scriptable Objects
  16. Creating Basic Editor Tools
  1. Intro and Setup
  2. Data Classes
  3. Menu Screen
  4. Game UI
  5. Answer Button
  6. Displaying Questions
  7. Click To Answer
  8. Ending The Game and Q&A
  1. Intro To Part Two
  2. High Score with PlayerPrefs
  3. Serialization and Game Data
  4. Loading Game Data via JSON
  5. Loading and Saving via Editor Script
  6. Game Data Editor GUI
  7. Question and Answer
  1. Overview and Goals
  2. Localization Data
  3. Dictionary, JSON and Streaming Assets
  4. Localization Manager
  5. Startup Manager
  6. Localized Text Component
  7. Localized Text Editor Script
  8. Localization Q&A
  1. Introduction and Session Goals
  2. Particle Launcher
  3. Particle Collisions
  4. ParticleLauncher Script
  5. Particle Collisions and Scripting
  6. Random Particle Colors
  7. Drawing Decals with Particles
  8. Collecting Particle Information For Display
  9. Displaying Particles Via Script
  10. Droplet Decals
  11. Questions and Answers
  1. Introduction and Goals
  2. Project Architecture Overview
  3. Creating Rooms
  4. Creating Exits
  5. Text Input
  6. Reacting To String Input
  7. Input Actions And The Delegate Pattern
  8. Questions and Answers
  1. Introduction and Goals
  2. Project Architecture and Review
  3. Displaying Item Descriptions
  4. Examining Items
  5. Taking Items
  6. Displaying Inventory
  7. Action Responses
  8. Preparing The Use Item Dictionary
  9. Creating The Use Action
  10. Questions and Answers