Trying to Keep FPS Look Direction Consistent While Adjusting Player Body Orientation

Hello, I’ve been wanting to prototype some FPS mechanics for a while, so I decided to give Unity a shot. At the most basic, I want the player to operate in a zero-gravity environment but keep the controls feeling very simple and traditional otherwise.

Put simply, the player essentially has magnet boots and otherwise moves along any flat “ground” surface as they would in a regular FPS game. Implementing the basics of this movement behavior started off surprisingly well. The player generally transitions to new surface orientations smoothly, and mouselook works in all orientations (no sudden camera flipping).

My only real issue with the feel of this system is, the direction of the player’s aim moves along with the rotation of the body. This can mean the aim suddenly jumps quite a bit, and if at all possible, it seems preferable to keep the player looking at the same direction even when rotation of the camera and the body changes (within limits, like only being able to look +/-90 degrees up or down).

Over the past week or so I’ve tried to implement this in multiple different ways but I am continually falling short.
With varying combinations of vectors, quaternions, and Euler angles I’ve generally been following this process:

  • Record the desired look direction before any body rotation (or keep it independent of body rotation, i.e. not a child of the body).

  • Rotate the player’s body without concern for look direction

  • Rotate the player’s body again such that it remains on the same up/down axis (usually transform.up), and also minimizes the angle between its forward direction (transform.forward) and the desired look direction. This seems to be the hardest step.

  • Rotate the look direction back to the original look direction (parent/child relationship) OR align the position of the camera back to the correct spot on the player’s body, if needed.

  • Re-constrain or “fix” up/down look (+/-90 degrees, as mentioned above). This can also been tricky.

I’ve seemingly gotten very close with 4-5 separate implementations of this, but they all seem to drift or otherwise not work as expected. I also assume a more knowledgeable person could probably come up with a more elegant solution.

The most important piece I can provide up front is probably my method of rotating the player’s body to a new surface smoothly. It’s not finalized, but it seems to work well.

if (Physics.SphereCast(transform.position, 0.45f, -transform.up, out magHit, playerHeight / 2, groundMask))
        {
            rb.MoveRotation(Quaternion.Slerp(rb.rotation, Quaternion.FromToRotation(transform.up, magHit.normal) * rb.rotation, 0.05f));
        }

Thanks for any guidance in advance.

The Hard Part™ here is not really the player controller… we can trivially morph control inputs into any orientation by multiplying with rotations.

More likely the Hard Part will be code to analyze the level colliders around the player and arrive at a good and stable deduction of which way to face / rotate.

For instance, As you approach a vertical wall, one would want the transition to be smooth so you would probably need a multi-sample approach to decide what your local “up” is at any point.

You would also need to consider what to do when wedging under a sloping descending roof… at some point a player walking underneath a descending set of stairs would need to flip onto his back and start walking up the underside of the stairs. Coming up with a stable analysis of arbitrary geometry is gonna be the hard part.

Don’t conflate these two problems. Get a stable analysis of your level geometry, which might involve some kind of spherical check, or perhaps cardinal raycasts, and satisfy yourself 100% with the chosen “up” before you try feeding that smoothly into the player controller.

Hey, thanks for taking a look!

Sorry if I’m misunderstanding something, but I don’t think we’re quite on the same page yet. Rather than trying to explain through just text again, hopefully I can illustrate the problem better:

9189431--1280633--Current Behavior.gif

You should be able to see the player’s body orienting itself to new surfaces smoothly. There is some tuning to be done, edge cases to figure out, and additional functionality to be added on to this foundation (some of which you’ve already picked up on, and some of which I’m already thinking about), but this has worked surprisingly well so far.

The one big thing I’m trying to fix here is the player’s look direction being forcefully yanked along with the body. Instead, I’d like it to work more like a camera gimbal/stabilizer. The player’s look direction should remain constant while the body orientation changes, unless a fairly standard FPS constraint is reached (the player not being able to look more than 90 degrees up/down, which is the threshold where they’d start looking backwards).

To further illustrate the current and desired behaviors, starting with this orientation and walking forward on to the ramp:
9189431--1280642--Before.png
(blue line represents the forward direction of the body, green line represents the current look direction)

Currently results in this change to the player look direction:
9189431--1280651--Current Behavior.png

Whereas I would strongly prefer the player to remain looking in the same direction (given a constraint is not met) as the body orientation changes:
9189431--1280648--Desired Behavior.png

And importantly, after this correction, player movement should continue working exactly as expected. Forward for the player’s movement and the player’s look should remained aligned, unlike this:
9189431--1280657--Broken Orientation.png
(with the red line representing movement direction while pressing forward)

The behavior seems completely possible. My current understanding of the math and Unity is just too limited to implement it in code or conclude otherwise. I have heard Cinemachine is a good tool for creating complex camera behaviors in Unity, so I will explore that for a possible solution once I get a chance.

Thanks again!

It is. Steps to success:

  • stop adjusting the camera rotation at all based on player orientation

  • only adjust the camera rotation based on mouse input

  • only move camera position to match the player position

Done.