Eye Movement with Bone Transforms
AUTHOR NOTE: This tutorial uses affiliate links. I only use affiliate links for assets that are either explicitly used in the article or that I have personally tested. I do not include affiliated links for paid promotional purposes.
Using Unity Version 2019.4
There are a few creative hills I’m willing to die on, no matter how basic the game is. One of those hills is eye movement.
At the very least, eye movement can direct player attention, but there’s much more to it than that. In reality, our eyes also convey focus, personality and emotion; we abandon a crucial human element if we negate that in our character design. In fact, a character staring lifelessly in one direction can hurt immersion significantly.
So for me, eye movement is a must. And fortunately, it’s quite simple to implement.
The Problem:
Your character has dull, lifeless eyes. You want them to look at points of interest or look around their surroundings, but they only stare blankly into the abyss.
My solution:
This is the first of two tutorials that will go through tackling eye movement. In this one, we’ll use eyes that are rigged to bone transforms. The second will use an offset on the texture UV for the eyes.
Obviously, there are exceptions to the below criteria, but basically, you will want to use:
Eye Bones:
Unity’s API includes an interface that will point a transform at another transform. Specifically, the animator.setlookat functionality rotates a transform’s forward direction (which should be the front of the eyeball) to “look at” another transform (the target).
If your character has separate eye meshes rigged and weighted to bones, and
The eye meshes are spherical
Uv Offsets:
This process offsets the texture rather than rotating a mesh. Based on the target position, it will offset the texture on it’s x and y axis so the eye appears to be looking at a target.
If your character has eye meshes that are not rigged to bones, or
The eyes are part of the character mesh, but have their own material, or
The eye meshes are flat or non-spherical
If you have the transforms, or plan to use blendshapes instead of UV offsets, there are several high-quality assets on the store that take care of eye movement for you. My favourite is Realistic Eye Movement, but there are many others.
However, if you want to do it yourself, keep reading.
Animator Controller Setup
I’m going to make use of the animator, mainly because I like to keep things that move the character in one place. Also, by dedicating a layer to eye movement, we can sync it with our other character animations.
Why would we do that? Well, if your character is running forward, they’re likely just looking ahead, so you don’t need to bog down your update function with eye movement calls. But when they are near a point of interest, or in a fighting stance, you want to be looking at the target. If they’re standing idle, they might just be looking at random points. And finally, if they’re in a dialogue state, their eye movements would ideally correspond to their state of mind. Thinking, shyness, anger, and so on — all would be expressed through different eye movements.
Begin by creating a new layer in your animator called “EyeMovement” or whatever you like. This layer will use an avatar mask for just the eye mesh, the head bone and the eye bones, so cancel out the entire humanoid form and import your character’s avatar to the Transform field.
The layer should override the other layers and have the IK Pass enabled.
We’ll set up states for eye movement, even though they contain no actual animation clips. (All we really want is for the states to initiate the appropriate eye behaviours.)
The first state is an empty default state that I’ve called “DefaultEye”. It will not need an animation behaviour since it’s going to serve as the resting state for our eye movement system. When we return to this state, our character will not be tracking any targets with their eyes.
However, the next state is for when the character is looking at and tracking a target with their eyes. I’ve called this state “LookAtTarget” and this is where I’ll start setting up the animation behaviour. The behaviour scripts will be different depending on the method of eye movement you choose.
Lastly, for testing purposes, go ahead and create two trigger parameters, one called DefaultEye and another called LookAtTarget. Use these triggers as the transition conditions between the two animation states.
What you need:
A character with separate eye meshes rigged to eye bones. I’ll be using my sci-fi version of Scarlett Riley.
Eye bones are simple to manage. Practically everything you need for eye movement is already built into the Animator.SetLookAt interface.
Our Eye Rig:
We’re going to make a rig that holds the reference transforms for our look system. Start by creating an empty child under the Head transform and call it EyeRig. Next, add a child called DefaultLookTarget. This will be our characters resting look target so position it as closely to a natural spot as possible.
Now create a new script called “EyeMovement” or whatever you prefer. This is where your animation behaviour will grab references to the look targets, but because most of the coding involves floats that can be cast directly through the animation behaviour, it will remain pretty bare bones. It simply needs to contain parameters for your targets and your character. I’ve also added a function that takes a Transform parameter to set up new targets to look at.
This is my EyeMovement code so far:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EyeMovement : MonoBehaviour
{
public GameObject character;
public Transform activeLookTarget;
public Transform defaultLookTarget;
private Animator anim;
// Start is called before the first frame update
void Start()
{
anim = character.GetComponent();
}
public void SetNewTarget(Transform _newTarget)
{
activeLookTarget = _newTarget;
}
}
Go back to your scene and populate the fields in your script. If you run the code now, you’ll notice that (drumroll please)…it won’t do anything. We have some work to do with our Animator first.
We’ve already created our DefaultEye and LookAtTarget states. Leave the Default state alone and select the LookAtTarget state. Add a new behaviour script to the state. I’ve called mine “LookBehaviour”.
The parameters we need to make the system work properly will include a reference to our eye movement system and floats for the lookat weights.
So with that in mind, let’s expose the OnStateEnter function so we can grab a reference to our eye movement script when we enter the LookAtTarget state. We’ll also expose the OnStateIK function to enable the actual magic behind the looking system. All we have to do is use the Animator.SetLookAt interface to lock onto the target referenced in our EyeMovement script. You can then play with the weights we’ve left exposed on the behaviour to get the look you like.
Lastly, set the look target back to the default position in OnStateExit.
Here’s the full LookBehaviour script.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LookBehaviour : StateMachineBehaviour
{
[HideInInspector]
public EyeMovement eyeMovement;
//Weight Values
[Header("Look At Weights")]
public float lookWeight = 1f;
public float eyeWeight = .5f;
public float headWeight = 0f;
public float bodyWeight = 0f;
public float clampWeight = 0.5f;
// OnStateEnter is called when a transition starts and the state machine starts to evaluate this state
override public void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
eyeMovement = animator.GetComponent();
}
// OnStateUpdate is called on each Update frame between OnStateEnter and OnStateExit callbacks
//override public void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
//
//}
// OnStateExit is called when a transition ends and the state machine finishes evaluating this state
override public void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
eyeMovement.activeLookTarget = eyeMovement.defaultLookTarget;
}
// OnStateMove is called right after Animator.OnAnimatorMove()
//override public void OnStateMove(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
//{
// // Implement code that processes and affects root motion
//}
// OnStateIK is called right after Animator.OnAnimatorIK()
override public void OnStateIK(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
//We want the eyes locked on the look target no matter the targeting state
animator.SetLookAtPosition(eyeMovement.activeLookTarget.position);
animator.SetLookAtWeight(lookWeight, bodyWeight, headWeight, eyeWeight, clampWeight);
}
}
Now, either set the LookAtTarget state to be default, or trigger the animation state with a menu button to test it out. You can see that the characters eyes will follow the target. If we go back to the default state, they will revert to their original rotation and stop tracking the target. It’s pretty simple.
NOTE: The limit on the eye range can be controlled by the weight and clamp values of animator.SetLookAtWeight. You’ll want to play around with it to see where it fits best, which is why we left the parameters exposed. The great thing about this is that you can include subtle head and body weights in addition to eyes.
Happy Look Targeting!
#Unity3D #Character #CodingTutorial #Animation



