I am trying to write a first person camera aim script that takes mouse input to rotate the camera. I know there are plenty of examples around of this and indeed I started with one, but what I’m finding is that all the examples I’ve found so far are making a very fundamental assumption: that the camera’s up is always the same as world up. For example look at this script:
private void Update()
{
float turnSpeed = 4.0f;
float minTurnAngle = -90.0f;
float maxTurnAngle = 90.0f;
// get the mouse inputs
float y = Input.GetAxis("Mouse X") * turnSpeed;
rotX += Input.GetAxis("Mouse Y") * turnSpeed;
// clamp the vertical rotation
rotX = Mathf.Clamp(rotX, minTurnAngle, maxTurnAngle);
// rotate the camera
transform.eulerAngles = new Vector3(-rotX, transform.eulerAngles.y + y, 0);
}
As you can see, the assumption is being made that X input is always perpendicular to world up while Y input is always parallel to world up. The clamping is also making this assumption.
I’ve been trying for hours and hours to work out how to “convert” (for lack of a better term) the input values to make sense against the current camera up vector, but to no avail. I know I’m missing something simple, but at the moment the right function or operator is eluding me.
Well, rotating the camera based on the camera’s up vector probably doesn’t make much sense, because the up vector will change as a result of the very rotation you’re performing. Clamping based on that definitely doesn’t make sense; you’d be saying that the camera can’t be more than (some number) off from itself.
If the camera is a child of some sort of rotated reference-frame object, and you want to do everything relative to that parent, then you can probably just replace all references to “transform.eulerAngles” with “transform.localEulerAngles”.
If that’s not what you mean, you’ll need to describe what you want with more mathematical detail.
Sorry, what I meant to say was the up vector of the character which doesn’t change as the camera rotates.
I did try localEulerAngles and it made no difference.
And also to answer your other question, I have a scene where gravity changes as the player selects on which surface they want to stand. So if they go to another surface that has different gravity to the normal world down vector they are rotated to stand on the the surface and their up changes. So in a first person view, it needs to appear to the player like everything is normal and that means that the mouse input behaves exactly the same way as it did when gravity was world down.
Is the camera a descendant of the player in the game hierarchy? If not, then it certainly would make no difference.
Even if it’s impractical to make the camera a child of the player, making the camera a child of something that rotates to align with gravity is probably still the easiest way to organize things. Even if the parent is just an empty game object that exists solely for this purpose.
But if you want to do it in math rather than with object hierarchy, then I believe you just need to calculate the rotation necessary to get from default orientation (gravity in negative y) into the active orientation and compound that with whatever rotation your current UI logic would give you. This will require converting your rotations into Quaternions instead of Euler angles, but you can do that at the last step (the static function Quaternion.Euler can convert Euler angles into a Quaternion). Then your final step looks something like transform.rotation = referenceFrameRotation * Quaternion.Euler(-rotX, rotY, 0);
I suppose you’d also need to start keeping rotY in a persistent variable like you’re doing for rotX, rather than relying on the value of transform.eulerAngles.y
Thanks for the reply, even though I didn’t follow it exactly it did unlock another pathway in my brain (which is difficult at the best of times I’ll admit)! I’ve decided to go with a slightly different approach which seems to be working. I’m taking advantage of the cinemachine camera I’m using which has a built in Look At setting so that it is always focused on a target object. So instead of trying to do the rotations I’m translating that focus object (which is just an empty) according to the coordinates I get from ScreenToWorldPoint which are world rotation agnostic. With the appropriate lerping / smoothing applied of course. Seems to be doing the trick!