Part of these game systems:
intermediate Touch & Mobile

Gyroscope Camera Controller

Controls camera rotation using the device gyroscope sensor with smoothing and sensitivity settings. Includes calibration, axis locking, and fallback for devices without a gyroscope.

Unity 2022.3+ · 3.5 KB · GyroscopeCameraController.cs

How to Use

1

Attach the GyroscopeCameraController script to your Main Camera

2

Adjust sensitivity and smoothSpeed in the inspector

3

Lock any axes you do not want the gyroscope to control

4

Enable useTouchFallback so the camera still works in the editor or on devices without a gyroscope

5

Call Calibrate() at runtime to reset the neutral orientation

Features

  • Device gyroscope-based camera rotation
  • Smoothing and sensitivity settings
  • Calibration/reset function
  • Optional axis locking
  • Fallback for devices without gyroscope

When to Use This

Ideal for mobile AR-like experiences, 360-degree viewers, VR-lite games, and any mobile 3D game that wants device tilt to control the camera. Use for immersive exploration games, panoramic photo viewers, or as an alternative look control in mobile FPS games.

Common Mistakes

The gyroscope coordinate system differs from Unity's — the script handles this conversion, but if you modify the rotation math, test on both iOS and Android as gyro behavior varies. Always call Calibrate() when the player starts a level or the neutral position will be wherever the device was at app launch. The touch fallback sensitivity needs separate tuning from gyro sensitivity since they use completely different input ranges.

Source Code

GyroscopeCameraController.cs
C#
using UnityEngine;

public class GyroscopeCameraController : MonoBehaviour
{
    [Header("Gyroscope Settings")]
    [SerializeField] private float sensitivity = 1f;
    [SerializeField] private float smoothSpeed = 5f;

    [Header("Axis Locking")]
    [SerializeField] private bool lockX;
    [SerializeField] private bool lockY;
    [SerializeField] private bool lockZ;

    [Header("Fallback")]
    [SerializeField] private bool useTouchFallback = true;
    [SerializeField] private float touchSensitivity = 0.2f;

    public bool GyroAvailable { get; private set; }
    public bool IsCalibrated { get; private set; }

    private Gyroscope gyro;
    private Quaternion calibrationOffset = Quaternion.identity;
    private Quaternion targetRotation;
    private Quaternion initialRotation;

    // Conversion from gyro space to Unity space
    private static readonly Quaternion gyroFix = new Quaternion(0f, 0f, 1f, 0f);

    private Vector2 touchDelta;
    private float fallbackYaw;
    private float fallbackPitch;

    private void Start()
    {
        initialRotation = transform.rotation;

        if (SystemInfo.supportsGyroscope)
        {
            gyro = Input.gyro;
            gyro.enabled = true;
            GyroAvailable = true;
            Calibrate();
        }
        else
        {
            GyroAvailable = false;
            Debug.Log("Gyroscope not available. Using touch fallback.");
        }
    }

    private void Update()
    {
        if (GyroAvailable)
        {
            UpdateGyro();
        }
        else if (useTouchFallback)
        {
            UpdateTouchFallback();
        }
    }

    private void UpdateGyro()
    {
        Quaternion rawGyro = gyro.attitude;
        Quaternion convertedRotation = ConvertGyroRotation(rawGyro);
        Quaternion calibrated = calibrationOffset * convertedRotation;

        targetRotation = calibrated;
        ApplyRotation(targetRotation);
    }

    private Quaternion ConvertGyroRotation(Quaternion gyroAttitude)
    {
        return Quaternion.Euler(90f, 0f, 0f) * new Quaternion(
            gyroAttitude.x, gyroAttitude.y,
            -gyroAttitude.z, -gyroAttitude.w);
    }

    private void UpdateTouchFallback()
    {
        if (Input.touchCount == 1)
        {
            Touch touch = Input.GetTouch(0);
            if (touch.phase == TouchPhase.Moved)
            {
                touchDelta = touch.deltaPosition;
                fallbackYaw += touchDelta.x * touchSensitivity;
                fallbackPitch -= touchDelta.y * touchSensitivity;
                fallbackPitch = Mathf.Clamp(fallbackPitch, -80f, 80f);
            }
        }

        // Mouse fallback for editor
        if (Input.GetMouseButton(1))
        {
            fallbackYaw += Input.GetAxis("Mouse X") * touchSensitivity * 10f;
            fallbackPitch -= Input.GetAxis("Mouse Y") * touchSensitivity * 10f;
            fallbackPitch = Mathf.Clamp(fallbackPitch, -80f, 80f);
        }

        targetRotation = Quaternion.Euler(fallbackPitch, fallbackYaw, 0f);
        ApplyRotation(targetRotation);
    }

    private void ApplyRotation(Quaternion rotation)
    {
        Vector3 euler = rotation.eulerAngles;

        if (lockX) euler.x = initialRotation.eulerAngles.x;
        if (lockY) euler.y = initialRotation.eulerAngles.y;
        if (lockZ) euler.z = initialRotation.eulerAngles.z;

        Quaternion final_rot = Quaternion.Euler(euler);

        if (sensitivity != 1f)
        {
            final_rot = Quaternion.Slerp(initialRotation, final_rot, sensitivity);
        }

        transform.rotation = Quaternion.Slerp(
            transform.rotation, final_rot, Time.deltaTime * smoothSpeed);
    }

    public void Calibrate()
    {
        if (!GyroAvailable) return;

        Quaternion currentGyro = ConvertGyroRotation(gyro.attitude);
        calibrationOffset = Quaternion.Inverse(currentGyro) * initialRotation;
        IsCalibrated = true;
    }

    public void SetSensitivity(float value)
    {
        sensitivity = Mathf.Clamp(value, 0.1f, 3f);
    }

    public void SetSmoothSpeed(float value)
    {
        smoothSpeed = Mathf.Clamp(value, 1f, 20f);
    }

    private void OnDisable()
    {
        if (gyro != null)
            gyro.enabled = false;
    }

    private void OnEnable()
    {
        if (gyro != null)
            gyro.enabled = true;
    }
}
Ready for more? Endless Runner Controller Complete endless runner character controller with auto-forward movement, lane switching, jump and slide mechanics.