Character or camera "roll" when rotating on XY axis

I have a script for a MouseLook Third Person camera (think of it like a spaceship), while I’ve avoided gimbal lock I have this slight problem where the character or camera (its hard to tell which) are rotating along its “side” or rolling slightly after some combination of XY axis rotations.

Script:

//void lateupdate


    Transform myTarget;

    Screen.lockCursor = lockCursor;
    float horizontal = Input.GetAxis("Mouse X");
    float vertical = Input.GetAxis("Mouse Y");

    horizontal *= rotateSpeed;
    vertical *= rotateSpeed;

    target.transform.rotation *= Quaternion.Euler(-vertical, horizontal, 0);

    transform.position = target.transform.position - (target.transform.rotation * offset);
    myTarget = target.transform;

    var relUp = myTarget.TransformDirection(Vector3.forward);
    var relPos = myTarget.position - transform.position;

    transform.rotation = Quaternion.LookRotation(relPos, relUp);

It would probably be fine perhaps to just reset the Z rotation to 0 maybe or is there a better way?

Hi, for this kind of camera code, it’s sometimes easier to have a hierarchy of 2 objects/Transform: each one assigned to the rotation around 1 axis only. The engine will then combine the rotations when computing Transforms.

Example: you have an object “Target” that you move (only position) as your camera target (= on the character or spaceship). Also, this “Target” will manage rotation around Y. So you can change it’s transform.rotation using Quaternion.Euler( 0, angle, 0 ).

You define a child object of “Target”, let’s say “Elevation”, that is positioned (0, 0, 0) relatively to it’s parent “Target”. Then, you can rotate this object on the X axis with Quaternion.Euler(angle, 0, 0) on transform.localRotation (note: local).

After rotating it, you can use Elevation.transform.back to get a transformed vector that combines the 2 rotations. Use Evelation.transform.back * offset to get the position of a camera. To get the angle of the camera, just use Elevation.transform.rotation (not local this time, it will “look at” Target). I guess there will be no gimbal lock here either but I didn’t tested it.

Edit: I didn’t try to code this, it might be more convenient to rotate the “Evelation” object around Z and use transform.right or .left to position the camera, depending on the init/0 values you want.