sk en

Unity FPS Player Controller bug
game-dev; 8/9/2023

I've come across many tutorials about how to create FPS controllers in Unity. However each one of them introduced a bug. When you strafe (move to side) and also try to rotate camera to focus on one object, you will notice weird lag. This lag is caused by the rigidbody physics being updated at fixed timestep. Some tutorials even suggests to solve this issue by changing the timestep of physics. However this is not a problem of timestep. It's a problem of dataflow.

When you look at the problem what you really need is to create rotation from euler angles that conists of (-acumulatedMouse.y, acumulatedMouse.x, 0) for the camera and (0, acumulatedMouse.x, 0) for the movement. The problem with the solution in most tutorials is that they use rigidbody for the rotation. The Y rotation part is applied to the whole rigidbody and then only X is applied to the camera.

My solution is to never rotate the rigidbody - you don't need it. Do not nest player camera under the rigidbody. Only rotate the input vector around the Y axis (and potentially align it to the terrain below). This way, the camera is never reading state from the rigidbody which in turn allows for smooth camera controls.

Following snippet is just for demonstration.

c#
Rigidbody rigidbody;
Transform camera;

// movementVector (x = left,right; y = up,down), read from keyboard/gamepad...
Vector2 movementVector;

// cameraVector (x = left,right; y = up,down), read from keyboard/gamepad... (mouse delta or gamepad axis * deltaTime)
Vector2 cameraVector;

// Input changes only theese
float xRotation = 0;
float yRotation = 0;

void Update() {
    // Out of scope of this tutorial 
    movementVector = PlayerInput.GetMovementVector();
    cameraVector = PlayerInput.GetCameraVector();

    // This axis flip needs to happend either here or in the input, if the cameraVector
    // contains direct mapping to mouse X,Y then this needs to be here.
    xRotation += -cameraVector.y;
    yRotation += cameraVector.x;

    // Because camera is not nested in the rigidbody, we have to update it's transform.
    camera.rotation = Quaternion.EulerAngles(xRotation, yRotation, 0);
    camera.position = rigidbody.position;
}

void FixedUpdate() {
    var movementDirection = Quaternion.EulerAngles(0, yRotation, 0);
    var movement = movementDirection * new Vector3(movementVector.x, 0, movementVector.y);

    // The rigidbody is not rotating at all
    rigidbody.AddForce(movement * 100, ForceMode.VelocityChange);
}

Hopefully this will help someone to make non-stuttery FPS controllers