2

I am trying to write a method, MouseLookFirstPerson(), to control camera orientation with mouse axis input. From what I can gather, the most common basic way of preventing the camera from inverting when looking very far up or down is with Mathf.Clamp(), clamping the camera's rotation about the X axis to within an appropriate range. I have implemented this in a MouseLookFirstPerson() method (inside a PlayerCamera script attached to the player capsule, of which the camera is a child):

void MouseLookFirstPerson()
    {
        float sensNormalized = sensMouseLook*Time.deltaTime;                                 //Cache product of sensitivity and time passed last frame

        float deltaY = sensNormalized*Input.GetAxis("Mouse Y");                              //Cache requested camera rotation about Y-axis
        float deltaX = sensNormalized*Input.GetAxis("Mouse X");                              //Cache requested player rotation about X-axis

        Vector3 deltaCamera = new Vector3(deltaY, 0, 0);                                     //Cache requested camera orientation change
        Vector3 deltaPlayer = new Vector3(0, deltaX, 0);                                     //Cache requested player orientation change

        Vector3 newOrientationCamera = Camera.main.transform.localEulerAngles - deltaCamera; //Cache new camera orientation by subtracting requested camera rotation from original camera orientation
        Vector3 newOrientationPlayer = transform.eulerAngles                  + deltaPlayer; //Cache new player orientation by summing requested player rotation and original player orientation
        
        Mathf.Clamp(newOrientationCamera.x, 89, -89);                                        //Clamp new camera orientation to allowed boundaries

        transform.eulerAngles = newOrientationPlayer;                                        //Set player orientation to new player orientation
        Camera.main.transform.localEulerAngles = newOrientationCamera;                       //Set camera orientation to new camera orientation
    }

The method is called here (the other two methods are in varying states of functionality, but beyond the scope of this question):

void Update()
    {
        ...
        switch (cameraMode)
        {
            case CameraMode.First:
                MouseLookFirstPerson();
                break;
            case CameraMode.Third:
                MouseLookThirdPerson();
                break;
            case CameraMode.Angled:
                MouseLookAngled();
                break;
        }
    }

This Update() method is in that same PlayerCamera script.

What I expect is that the camera does not rotate beyond +89° or -89° under any circumstances. However, if I move my mouse very quickly, or increase the DPI, and push against the upper or lower boundary, the camera will jitter, and occasionally inverts. This is prohibitive to play, and should not happen under any circumstances.

I have tried having the script check if the current local orientation of the camera is within bounds and making no change if not (using the ternary conditional operator):

Vector3 newOrientationCamera = Camera.main.transform.localEulerAngles - new Vector3(Camera.main.transform.localRotation.y < 89 && Camera.main.transform.localRotation.y > -89? -deltaY : 0, 0, 0);

This had no discernible effect on the behaviour of the camera.

Is there a more robust way to limit mouselook Y-axis camera rotation, or will I need to implement a 'net' of some kind to catch when the camera inverts and reset its position? Preferably, the 'jittering' would not occur either.

1 Answer 1

3

Mathf.Clamp returns the clamped value, it doesn't modify it, so you have to reassign the value it returns:

newOrientationCamera.x = Mathf.Clamp(newOrientationCamera.x, -89, 89);

Additionally, Camera.main.transform.localEulerAngles will return unsigned angles, (such as 340 instead of -20) which will break your negative bounds check. Convert the angle to a signed range first, before your clamp:

// Convert to signed angle
newOrientationCamera.x = ((newOrientationCamera.x + 180) % 360) - 180;

// Clamp
newOrientationCamera.x = Mathf.Clamp(newOrientationCamera.x, -89, 89);

No need to worry about changing it back, since the setter for localEulerAngles can handle both signed/unsigned.


As an aside, you shouldn't multiply the mouse inputs by Time.deltaTime, since they're deltas relative to a single frame. If you multiply them by Time.deltaTime, it actually ties your camera control back to the framerate (inversely)

Sign up to request clarification or add additional context in comments.

7 Comments

Why does Mathf.Clamp() have any effect if it only returns the clamped value? Also, the line you provided causes the camera's rotation about the X-axis to lock to straight up. It locks regardless of the local rotation of the camera at time of switching to CameraMode.First from CameraMode.Third (the default at start of play), though the angle to which it locks depends on the rotation at time of switch: it locks to -89° if the rotation is between +90° and -90°, and locks to -91° (which I assume is equivalent to +89°) otherwise. Re: aside, thanks for the tip :)
Why does Mathf.Clamp() have any effect if it only returns the clamped value? It definitely shouldn't, all it does is check min < x < max and return min/max/x depending on the checks. Also, the min is first, so change the second/third parameters to -89, 89. I didn't notice this in your original code, edited.
"positive rotation here is 'looking down'" sounds like an issue with negatives, then? Perhaps Camera.main.transform.localEulerAngles is returning a 0-360 range, messing with the clamping. I can't check this right now, but you can with a Debug.Log. If it is returning 0-360, then do something like ((angle + 180) % 360) - 180; to convert from 0-360 to (-180)-180
This was the problem. localEulerAngles was outputting values like +350° when the camera rotated upward, above the horizon, rather than the output I was expecting, 0°. This meant the value was being clamped to the positive boundary at both the horizon and 'down' orientations. The final code replaces Mathf.Clamp(newOrientationCamera.x, 89, -89); with newOrientationCamera.x = Mathf.Clamp((newOrientationCamera.x + 180)%360 - 180, -89, 89);, and behaves as expected. There is no jittering at the boundaries, and they are never exceeded, even with a comically high DPI. Thanks for the help!
In general don't go through eulerAngles .. rather store the individual float components locally and operate on these .. reason: eulerAngles are only ad-hoc returned human readable values of a quaternion .. there are multiple Euler representations for the same quaternion so the returned value might be completely different than the assigned one .. read more in eulerAngles
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.