Skip to content
DasPeTe Tutorials

Smooth LookAt in Unity

Turn an object smoothly towards a target instead of snapping like transform.LookAt(), with smoothing that feels the same at every frame rate.

Beginner8 min

Quest objectives

  • Turn towards a target with Quaternion.LookRotation and Slerp
  • Make the smoothing independent of the frame rate
  • Keep characters upright by turning around the Y axis only
  • Turn at a constant speed with RotateTowards

transform.LookAt(target) turns an object to face a target in one go. For a turret, a character or a camera that is usually too abrupt: it should turn towards the target over a few frames instead. This component does that. Add it to the object that should turn.

The script

SmoothLookAt.cs
using UnityEngine;

public class SmoothLookAt : MonoBehaviour {
    [SerializeField] Transform target;

    [Tooltip("How quickly the rotation catches up. Higher is snappier.")]
    [SerializeField] float sharpness = 12f;

    [Tooltip("Turn around the Y axis only, so the object stays upright.")]
    [SerializeField] bool yawOnly = true;

    public Transform Target {
        get => target;
        set => target = value;
    }

    void LateUpdate() {
        if (target == null) {
            return;
        }

        Vector3 direction = target.position - transform.position;

        if (yawOnly) {
            direction.y = 0f;
        }

        // Nothing to look at when the target is right here
        if (direction.sqrMagnitude < 0.0001f) {
            return;
        }

        Quaternion lookRotation = Quaternion.LookRotation(direction);

        // The same smoothing at every frame rate
        float t = 1f - Mathf.Exp(-sharpness * Time.deltaTime);

        transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, t);
    }
}

Every frame it works out where the target is and turns part of the way there:

  1. The direction is the target's position minus its own. With Yaw Only on, its height is set to zero, so the object only turns left and right. That is what you want for a character or the base of a turret, which would otherwise lean towards targets above or below them.

  2. If the target is right where the object is, there is no direction to look in, and the script leaves the rotation alone. Quaternion.LookRotation would only complain about a zero vector in the Console.

  3. Quaternion.LookRotation makes the rotation that looks along the direction, and Quaternion.Slerp turns the current rotation part of the way towards it.

The script runs in LateUpdate, after every Update, so it sees where a moving target ended up in this frame. The target is a private field that you set in the Inspector, and other scripts change it through Target.

The same at every frame rate

The usual way to write the last step is this:

csharp
// Don't: this turns at a different speed at every frame rate
transform.rotation = Quaternion.Slerp(
    transform.rotation,
    lookRotation,
    sharpness * Time.deltaTime
);

Thanks to Time.deltaTime it looks independent of the frame rate, but it isn't. At 60 frames per second every frame turns a fifth of the remaining way, at 20 frames more than half. The lower the frame rate, the faster the object turns, and below 12 frames per second the value is over 1 and the object snaps to the target at once.

1f - Mathf.Exp(-sharpness * Time.deltaTime) fixes that: the part of the angle that is left shrinks by the same factor every second, however many frames that second has. sharpness reads as "how quickly": at 12 the object has turned about 70% of the way after a tenth of a second and 95% after a quarter.

Turning at a constant speed

Smoothing slows down towards the end, which feels natural for heads and cameras. A turret or a mechanical part turns at a constant speed instead. Quaternion.RotateTowards turns by at most the given number of degrees:

csharp
[SerializeField] float degreesPerSecond = 180f;

// ...and in LateUpdate(), instead of the last two lines:
transform.rotation = Quaternion.RotateTowards(
    transform.rotation,
    lookRotation,
    degreesPerSecond * Time.deltaTime
);

Trying it out

Add SmoothLookAt to a cube, drag a second object into Target and press Play. Move the target around in the Scene view, and the cube turns its front, the blue Z axis, after it. Change Sharpness while the game runs to find the value that suits your object.

Comments

The comments are GitHub Discussions: sign in with GitHub to write one.

The comments come from GitHub, through giscus. Loading them sends your IP address to giscus.app and GitHub.