C# 360 Orbital Camera Controller (Gimbal Lock Issue)

I have a stationary cube in my scene that I’m orbiting a camera around. I have my MainCamera nested under a GameObject that I’m calling ‘OrbitalCamera’.

I setup the script so that a click (or tap) and drag will rotate the camera around the object in space so it feels like you’re rotating the cube (ie: if I click the top of a face on the cube, and pull down, I’m rotating the X value) but you’ll actually be rotating the camera.

For the most part, my script works. However, after rotating the Y so much, the camera is upside down and the X gets inverted. Here’s my (updated) script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class OrbitalCamera : MonoBehaviour {
    public bool cameraEnabled;

    [SerializeField] private float touchSensitivity = 5f;
    [SerializeField] private float orbitDampening = 10f;

    protected Transform xFormCamera;
    protected Transform xFormParent;
    protected Vector3 localRotation;
    protected float cameraDistance;

    void Start () {
        cameraEnabled = true;
        xFormCamera = transform;
        xFormParent = transform.parent;
        cameraDistance = transform.position.z * -1;
    }
	
	void LateUpdate () {
		if (cameraEnabled) {
            if (Input.GetMouseButton(0)) {
                if (Input.GetAxis("Mouse X") != 0 || Input.GetAxis("Mouse Y") != 0) {
                    localRotation.x += Input.GetAxis("Mouse X") * touchSensitivity;
                    localRotation.y -= Input.GetAxis("Mouse Y") * touchSensitivity;
                }
            }
        }

        // TODO:: FIX CAMERA ROTATION / GIMBAL LOCK ISSUE
        
        Quaternion yaw = Quaternion.AngleAxis(localRotation.x, transform.up);
        Quaternion pitch = Quaternion.AngleAxis(localRotation.y, transform.right);
        //Quaternion qt = Quaternion.Euler(localRotation.y, localRotation.x, 0);

        // NOTE:: ROTATING PITCH AND YAW SEPARATELY WORK FLAWLESSLY, BUT ROTATION SHOULD HAPPEN TOGETHER
        //xFormParent.rotation = Quaternion.Slerp(xFormParent.rotation, pitch, Time.deltaTime * orbitDampening);
        //xFormParent.rotation = Quaternion.Slerp(xFormParent.rotation, yaw, Time.deltaTime * orbitDampening);

        // NOTE:: MULTIPLYING PITCH AND YAW STILL PRODUCES GIMBAL LOCK AND CAMERA SPINS INFINITELY AT CERTAIN ANGLES
        xFormParent.rotation = Quaternion.Slerp(xFormParent.rotation, pitch * yaw, Time.deltaTime * orbitDampening);

        // NOTE:: USING QUATERNION EULERS GENERATES A GIMBAL LOCK AND CAMERA DOES NOT SPIN 
        //xFormParent.rotation = Quaternion.Lerp(xFormParent.rotation, qt, Time.deltaTime * orbitDampening);
    }

}

Is there a good method to achieve this type of 360 camera? I’d like dragging from right to left to always move the camera left and dragging left to right to always move the camera right – no matter how the camera is oriented.

If you want completely free rotation you need to eliminate Quaternion.Euler() and related functions from your code (because Euler angles can’t properly represent all rotations without gimbal lock). Instead you could use Quaternion.AngleAxis() for the separate axes.

Also, Quaternion.Slerp() is a lot more useful than Lerp(), because it correctly “wraps around”.