What Are Quaternions?
If you have ever opened the Inspector in Unity and clicked on a GameObject's Transform component, you saw three numbers labelled Rotation: X, Y, and Z. Those are Euler angles — the intuitive pitch, yaw, and roll representation most of us learned first. But internally, Unity does not store or compute rotations as Euler angles. It uses Quaternions.
A Quaternion is a four-dimensional mathematical object, written as (x, y, z, w), that encodes an arbitrary 3D rotation. You almost never manipulate the four raw components directly; instead, you use the rich API that Unity provides. What you do need to understand is why Quaternions exist and what they solve.
The Gimbal Lock Problem
Euler angles represent a rotation as three successive rotations around fixed axes: first X, then Y, then Z (or some other convention). This chained approach breaks down in a specific situation called Gimbal Lock: when two of the three rotation axes become aligned, the system loses one degree of freedom. In practice, the object can no longer rotate freely in all directions — certain orientations become unreachable, and motion becomes erratic or "snapping".
The classic example is a camera or an aircraft. Pitch the nose 90° straight up, and now the roll and yaw axes overlap. You have effectively lost one axis of control. Gimbal lock caused real problems in early aerospace (it famously troubled the Apollo 11 mission's guidance computer) and it causes very visible bugs in 3D game engines that rely on Euler angles for interpolation.
Quaternions sidestep this entirely. Because they live in four-dimensional space, two rotation axes cannot become parallel within the Quaternion's own representation. You can freely interpolate between any two orientations without artifacts.
Where Quaternions Appear in Unity
Two Transform properties are Quaternions:
transform.rotation— the world-space rotationtransform.localRotation— the rotation relative to the parent
The Inspector still shows Euler angles because they are human-readable, but every time you set a rotation in Unity — even via the Inspector — the value is converted to and stored as a Quaternion. The convenient transform.eulerAngles property is a computed alias that converts on the fly.
Additional advantages of Quaternions: they are compact (four floats vs. a 3×3 rotation matrix), numerically stable, and can be smoothly interpolated using Slerp (Spherical Linear Interpolation) without ever encountering gimbal lock.
The Euler() Method
Quaternion.Euler(x, y, z) is the simplest way to create a Quaternion from familiar angle values. The parameters are degrees of rotation around the X, Y, and Z axes respectively.
// Rotate the object 180 degrees on the world Y axis (absolute rotation)
transform.rotation = Quaternion.Euler(0f, 180f, 0f);
// Same rotation, but relative to the parent
transform.localRotation = Quaternion.Euler(0f, 180f, 0f);
An important distinction: Quaternion.Euler() sets an absolute rotation, replacing whatever rotation the object had before. This is different from transform.Rotate(), which adds rotation on top of the current one. If you call Quaternion.Euler(0, 30, 0) every frame, the object will always be at 30° — it will not spin. To spin, you need cumulative rotation, covered in the next section.
// This sets the object to exactly 30 degrees — it does NOT spin
void Update()
{
transform.rotation = Quaternion.Euler(0f, 30f, 0f);
}
// This DOES spin — transform.Rotate() is cumulative
void Update()
{
transform.Rotate(0f, 30f * Time.deltaTime, 0f);
}
Cumulative Rotations with the * Operator
Quaternions can be combined using the * (multiplication) operator. Multiplying two Quaternions produces a new Quaternion that represents applying both rotations in sequence.
// Two 30-degree rotations combined = one 60-degree rotation
Quaternion combined = Quaternion.Euler(0f, 30f, 0f) * Quaternion.Euler(0f, 30f, 0f);
transform.rotation = combined; // object is now at 60 degrees
To make an object spin by adding an incremental rotation each frame, use the *= operator:
public float rotationSpeed = 90f; // degrees per second
void Update()
{
// Multiply the current rotation by a small incremental rotation this frame
transform.rotation *= Quaternion.Euler(0f, rotationSpeed * Time.deltaTime, 0f);
}
Order Matters
Quaternion multiplication is not commutative. A * B is generally not the same as B * A. The left operand is applied first in Unity's convention. Think of it as: "take the current rotation, then apply this additional rotation on top of it." Reversing the order changes the result, often dramatically. Always test which order gives you the intended behavior.
Quaternion a = Quaternion.Euler(45f, 0f, 0f);
Quaternion b = Quaternion.Euler(0f, 90f, 0f);
// These produce different orientations:
transform.rotation = a * b; // tilt then yaw
transform.rotation = b * a; // yaw then tilt
The Angle() Method
Quaternion.Angle(from, to) returns the angle in degrees between two rotations. It is useful whenever you need to measure how far apart two orientations are — for example, checking whether a turret is aligned with its target before firing.
public Transform turret;
public Transform target;
public float firingThreshold = 5f; // degrees
void Update()
{
Quaternion targetRotation = Quaternion.LookRotation(
target.position - turret.position
);
float angleDiff = Quaternion.Angle(turret.rotation, targetRotation);
if (angleDiff < firingThreshold)
{
Fire();
}
}
Note that Quaternion.Angle always returns a positive value between 0 and 180 degrees. It does not tell you which way the difference goes, only the magnitude of the angular difference.
The AngleAxis() Method
Quaternion.AngleAxis(angle, axis) creates a Quaternion that represents a rotation of angle degrees around the given axis vector. This is the most explicit and unambiguous way to specify a rotation when you know exactly which axis you want to rotate around.
// 45 degrees around the world Y axis
Quaternion spinY = Quaternion.AngleAxis(45f, Vector3.up);
// 90 degrees around the world X axis
Quaternion tiltX = Quaternion.AngleAxis(90f, Vector3.right);
// 30 degrees around a custom axis
Vector3 customAxis = new Vector3(1f, 1f, 0f).normalized;
Quaternion customRot = Quaternion.AngleAxis(30f, customAxis);
transform.rotation = spinY;
AngleAxis is particularly useful when working with physics or procedural animation, where you know the axis of rotation from context (a joint axis, a normal vector, etc.) but need to express it as a Quaternion.
The FromToRotation() Method
Quaternion.FromToRotation(fromDirection, toDirection) computes the rotation that would rotate the vector fromDirection so it points in the direction of toDirection. It answers the question: "how do I rotate vector A so it aligns with vector B?"
// Make the object's local UP axis point toward a surface normal
Vector3 surfaceNormal = hit.normal;
Quaternion alignToSurface = Quaternion.FromToRotation(Vector3.up, surfaceNormal);
transform.rotation = alignToSurface;
// Align the object's FORWARD axis to a direction
Vector3 direction = (target.position - transform.position).normalized;
Quaternion faceDirection = Quaternion.FromToRotation(Vector3.forward, direction);
transform.rotation = faceDirection;
A common use case is making a character's feet or a vehicle's wheels conform to uneven terrain: cast a ray downward to get the surface normal, then use FromToRotation to tilt the object to match.
The Inverse() Method
Quaternion.Inverse(rotation) returns the opposite rotation — the one that undoes rotation. Combining a rotation with its inverse gives you the identity (no rotation).
Quaternion original = transform.rotation;
Quaternion undone = Quaternion.Inverse(original);
// This results in identity (no rotation):
Quaternion identity = original * Quaternion.Inverse(original);
// Practical use: compute the relative rotation between two objects
Quaternion relativeRotation = Quaternion.Inverse(objA.rotation) * objB.rotation;
The relative rotation pattern is very useful. If you want to know how objB is rotated relative to objA, multiply the inverse of A's rotation by B's rotation. This gives you the rotation you would need to apply to A to make it match B's orientation.
The RotateTowards() Method
Quaternion.RotateTowards(current, target, maxDegreesDelta) rotates from current toward target, but by no more than maxDegreesDelta degrees. It will never overshoot. When current equals target, the function returns target unchanged.
public Transform target;
public float rotationSpeed = 90f; // degrees per second
void Update()
{
Vector3 direction = target.position - transform.position;
Quaternion targetRotation = Quaternion.LookRotation(direction);
transform.rotation = Quaternion.RotateTowards(
transform.rotation,
targetRotation,
rotationSpeed * Time.deltaTime
);
}
This is the right tool for turrets, enemy characters, or any object that needs to smoothly track a target at a controlled speed. Unlike Quaternion.Lerp which slows down as it approaches the target, RotateTowards moves at a constant angular speed — it behaves like a physical servo motor.
Comparing RotateTowards vs. Slerp
RotateTowards: constant angular speed, never overshoots — best for gameplay turrets, character turningQuaternion.Slerp: eases in and out, follows a fraction of remaining angle each frame — best for cinematic camera transitionsQuaternion.Lerp: similar to Slerp but slightly cheaper (normalizes instead of spherical interpolation) — good for UI or non-critical smooth rotation
The LookRotation() Method
Quaternion.LookRotation(forward) creates a rotation whose forward axis (Vector3.forward, i.e. +Z) points in the direction of forward. An optional second parameter up specifies which direction is "up" for the resulting rotation; it defaults to Vector3.up if omitted.
// Make an object face a direction
Vector3 direction = target.position - transform.position;
transform.rotation = Quaternion.LookRotation(direction);
// Keep the object upright (default up = Vector3.up)
// This is the most common usage for characters and vehicles
// Override the up vector for special cases (e.g., a spacecraft with no "gravity" up)
Vector3 customUp = transform.up; // maintain current up
transform.rotation = Quaternion.LookRotation(direction, customUp);
LookRotation requires that forward is not zero. If the direction vector could be zero (e.g., the object is at the exact same position as the target), guard against it:
Vector3 direction = target.position - transform.position;
if (direction.sqrMagnitude > 0.001f)
{
transform.rotation = Quaternion.LookRotation(direction);
}
How to Make an Object "Look" at a Target
Making an object face another object is one of the most common rotation tasks in games. Unity provides several approaches, each with different trade-offs.
Method 1: transform.LookAt()
The simplest solution is the built-in transform.LookAt() method:
void Update()
{
// Make this object's forward axis point at the target
transform.LookAt(target.transform);
// Or pass a world position directly
transform.LookAt(target.position);
// Optional second parameter overrides the world "up" direction
transform.LookAt(target.position, Vector3.up);
}
This is fast to write and works well for many situations. However, it locks all three rotation axes simultaneously — the object will tilt up/down as well as yaw, which is not always desirable. A billboard, for example, should face the camera but stay upright.
Method 2: LookRotation() with Direction Vector
Building the direction vector manually gives you full control. You can modify the vector before applying the rotation:
void Update()
{
Vector3 dir = target.position - transform.position;
// Option A: Allow full 3D look (identical to LookAt)
transform.rotation = Quaternion.LookRotation(dir);
// Option B: Flatten Y to keep the object upright (e.g., a tank turret)
dir.y = 0f;
if (dir.sqrMagnitude > 0.001f)
{
transform.rotation = Quaternion.LookRotation(dir);
}
}
Setting dir.y = 0 before calling LookRotation projects the direction onto the horizontal plane. The object will turn left and right to face the target but will never tilt up or down. This is the standard technique for character and vehicle rotation.
Method 3: Smooth Look with Slerp
For cinematic-quality smooth tracking, combine LookRotation with Slerp:
public float smoothSpeed = 5f;
void Update()
{
Vector3 dir = target.position - transform.position;
dir.y = 0f;
if (dir.sqrMagnitude > 0.001f)
{
Quaternion targetRot = Quaternion.LookRotation(dir);
transform.rotation = Quaternion.Slerp(
transform.rotation,
targetRot,
smoothSpeed * Time.deltaTime
);
}
}
Exercise: The Pedal (Pédalier)
This exercise models a bicycle crank system. The goal is to simulate the bottom bracket (the central axle), a crank arm, and a pedal that stays horizontal as the crank rotates — just like a real bicycle.
Hierarchy
- BottomBracket — the pivot point (empty GameObject at crank center)
- CrankArm — child of BottomBracket
- Pedal — child of CrankArm (offset from CrankArm pivot)
Solution: CrankController.cs
using UnityEngine;
public class CrankController : MonoBehaviour
{
[Header("References")]
public Transform crankArm; // child of this BottomBracket
public Transform pedal; // child of crankArm
[Header("Settings")]
public float crankSpeed = 180f; // degrees per second
// Track accumulated angle for the pedal counter-rotation
private float _currentAngle = 0f;
void Update()
{
float delta = crankSpeed * Time.deltaTime;
_currentAngle += delta;
// Rotate the crank arm around the bottom bracket's Z axis
crankArm.localRotation = Quaternion.Euler(0f, 0f, _currentAngle);
// Counter-rotate the pedal so it stays level (horizontal)
// The pedal rotates opposite to the crank at the same speed
pedal.localRotation = Quaternion.Euler(0f, 0f, -_currentAngle);
}
}
The key insight is the counter-rotation on the pedal. Because the pedal is a child of the crank arm, it inherits the crank's rotation. By rotating it equally in the opposite direction in local space, the pedal's world-space orientation remains constant — exactly as a real pedal stays horizontal under the rider's foot.
The RotateAround() Method
transform.RotateAround(point, axis, angle) is a convenience method that rotates the object around a point in world space. Internally it moves the object's position as well as its rotation, keeping it at the correct distance from the pivot point.
public Transform sun;
public float orbitSpeed = 45f; // degrees per second
void Update()
{
// Planet orbiting a star around the world Y axis
transform.RotateAround(sun.position, Vector3.up, orbitSpeed * Time.deltaTime);
}
Note that RotateAround changes both transform.position and transform.rotation. If you want the orbiting object to always face the center (like a tidally locked moon), you can add a LookAt call after RotateAround. If you want the object to maintain its own independent orientation, you may need to store the original rotation and reapply it.
// Orbit while maintaining a fixed world-space orientation
void Update()
{
Quaternion originalRotation = transform.rotation; // save
transform.RotateAround(pivot.position, Vector3.up, speed * Time.deltaTime);
transform.rotation = originalRotation; // restore
}
Quaternion * Vector3: Rotating a Direction
You can apply a Quaternion rotation to a Vector3 direction vector using the * operator. This is different from composing two Quaternions — here the right operand is a vector, and the result is a rotated vector.
// Where does "forward" point after a 90-degree yaw?
Quaternion yaw90 = Quaternion.Euler(0f, 90f, 0f);
Vector3 newForward = yaw90 * Vector3.forward;
// newForward is now Vector3.right (pointing right)
// Find the "right" direction relative to an object's current rotation
Vector3 objectRight = transform.rotation * Vector3.right;
// Shoot a projectile in the direction the gun barrel points
Vector3 barrelDirection = gunBarrel.rotation * Vector3.forward;
Rigidbody rb = projectile.GetComponent<Rigidbody>();
rb.velocity = barrelDirection * projectileSpeed;
This operation is extremely common in procedural movement and physics. Any time you need a direction vector that depends on an object's orientation, use rotation * baseDirection.
Exercise: Solar System
Build a simplified solar system where planets orbit the sun and moons orbit their respective planets. Use RotateAround and the parent-child hierarchy to simplify the math.
Approach
Each celestial body is a script that references its own pivot (the object it orbits). Planets reference the sun; moons reference their planet. Each body also spins on its own axis.
Solution: OrbitingBody.cs
using UnityEngine;
public class OrbitingBody : MonoBehaviour
{
[Header("Orbit Settings")]
public Transform orbitCenter; // Sun for planets, Planet for moons
public float orbitSpeed = 30f; // degrees per second
public Vector3 orbitAxis = Vector3.up; // orbit plane normal
[Header("Self Rotation")]
public float selfRotationSpeed = 120f; // degrees per second
public Vector3 selfRotationAxis = Vector3.up;
void Update()
{
// Orbit around the center
if (orbitCenter != null)
{
transform.RotateAround(
orbitCenter.position,
orbitAxis,
orbitSpeed * Time.deltaTime
);
}
// Spin on own axis
transform.Rotate(selfRotationAxis, selfRotationSpeed * Time.deltaTime, Space.World);
}
}
Setup
// Scene Hierarchy:
// Sun (no OrbitingBody script)
// Earth (OrbitingBody: orbitCenter = Sun, orbitSpeed = 30)
// Moon (OrbitingBody: orbitCenter = Earth, orbitSpeed = 120)
// Mars (OrbitingBody: orbitCenter = Sun, orbitSpeed = 15)
Because moons are not children of their planets in the hierarchy (they have their own OrbitingBody script that references the planet as the orbit center), each object moves independently. This is cleaner than using parent-child transforms when bodies have complex independent behaviors.
Exercise: 3D Object Visualizer with Mouse Drag
A product visualizer lets users drag to rotate a 3D model. This is a classic interaction pattern for e-commerce and portfolio sites. The technique maps mouse movement (a 2D delta) to world-space rotation axes.
Concept
When the user drags horizontally (X axis on screen), the object rotates around the world Y axis (yaw). When the user drags vertically (Y axis on screen), the object rotates around the world X axis (pitch). The mouse delta each frame drives the rotation amount.
Solution: MouseOrbitVisualizer.cs
using UnityEngine;
public class MouseOrbitVisualizer : MonoBehaviour
{
[Header("Sensitivity")]
public float horizontalSensitivity = 0.5f;
public float verticalSensitivity = 0.5f;
[Header("Clamp Vertical Rotation")]
public float minVerticalAngle = -80f;
public float maxVerticalAngle = 80f;
private float _yaw = 0f; // horizontal rotation (around Y axis)
private float _pitch = 0f; // vertical rotation (around X axis)
private bool _isDragging = false;
void Update()
{
// Detect drag start
if (Input.GetMouseButtonDown(0))
_isDragging = true;
if (Input.GetMouseButtonUp(0))
_isDragging = false;
if (_isDragging)
{
float mouseX = Input.GetAxis("Mouse X");
float mouseY = Input.GetAxis("Mouse Y");
// Accumulate rotation angles
_yaw += mouseX * horizontalSensitivity * 100f * Time.deltaTime;
_pitch -= mouseY * verticalSensitivity * 100f * Time.deltaTime;
// Clamp pitch to avoid flipping
_pitch = Mathf.Clamp(_pitch, minVerticalAngle, maxVerticalAngle);
// Build the rotation from accumulated angles
transform.rotation = Quaternion.Euler(_pitch, _yaw, 0f);
}
}
}
Momentum Variant
For a more polished feel, add momentum so the object continues spinning after the user releases the mouse:
using UnityEngine;
public class MouseOrbitWithMomentum : MonoBehaviour
{
public float sensitivity = 0.3f;
public float damping = 5f; // how quickly momentum decays
private float _yaw = 0f;
private float _pitch = 0f;
private Vector2 _velocity = Vector2.zero;
private bool _isDragging = false;
void Update()
{
if (Input.GetMouseButtonDown(0)) _isDragging = true;
if (Input.GetMouseButtonUp(0)) _isDragging = false;
if (_isDragging)
{
_velocity.x = Input.GetAxis("Mouse X") * sensitivity * 100f;
_velocity.y = Input.GetAxis("Mouse Y") * sensitivity * 100f;
}
else
{
// Decay velocity over time (momentum)
_velocity = Vector2.Lerp(_velocity, Vector2.zero, damping * Time.deltaTime);
}
_yaw += _velocity.x * Time.deltaTime;
_pitch -= _velocity.y * Time.deltaTime;
_pitch = Mathf.Clamp(_pitch, -80f, 80f);
transform.rotation = Quaternion.Euler(_pitch, _yaw, 0f);
}
}
Summary
Quaternions are Unity's native rotation representation. You rarely need to understand the underlying four-dimensional math — what matters is knowing which API method to reach for in each situation:
- Euler() — set a rotation from familiar degree values
- * operator — compose or accumulate rotations
- Angle() — measure angular difference between two orientations
- AngleAxis() — rotate by a specific number of degrees around a specific axis
- FromToRotation() — align one direction vector to another
- Inverse() — reverse a rotation or compute relative orientation
- RotateTowards() — constant-speed tracking without overshoot
- LookRotation() — face a direction (combine with direction arithmetic for precise control)
- RotateAround() — orbit an object around a world point
- rotation * Vector3 — rotate a direction vector by a Quaternion
Mastering these ten tools covers virtually every rotation scenario you will encounter in Unity3D game development, from simple character turning to complex procedural animation systems.
Need help with Unity3D development?
I'm a senior developer with 16+ years experience, including AAA projects at Ubisoft. Let's discuss how I can help with your game or interactive project.
Start a Conversation