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.
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)