The camera is slowing down when player rotates on Y axis (the closer it gets to 90 degrees the slower it moves)

Godot Version

Godot_v4.2.1-stable_mono_win64

Question

I’m making a physics based FPS, to optimize physics calculations I made a script that handles all of the calculations in the scene in one node, it works as intended, but it also caused the player to update it’s position at _PhysicsProcess’s frequency, to conquer that I made it so the camera smoothly follows the player instead of being it’s child, but it caused the camera to slow down it’s rotation on X axis when the player is rotated less than 90 degrees on Y axis, completly stop on exactly 90 and invert when the player is rotated more than 90 degrees. I’ve been stuck with it for a while and any help would be appreciated thanks!

Here is an example:

And here is the camera code (sorry if messy):

public partial class CameraController : Camera3D
{
	public CharacterBody3D playerModel;

	public const float sensitivity = -0.2f;

	private static float cameraSmoothing = 0.2f;

	public bool isPaused = true;

	public override void _Ready()
	{
		// Gets the refrence to the player (don't look at it it's temporary)
		playerModel = Physics.players[0];
	}


	public override void _Input(InputEvent @event)
	{
		if (@event is InputEventMouseMotion mouseMotion)
		{
			float mouseX = mouseMotion.Relative.X;
			float mouseY = mouseMotion.Relative.Y;

			RotatePlayerModel(mouseX * sensitivity);

			if (!isPaused)
			{
			// Rotate based on mouse input
			Rotate(Vector3.Right, Mathf.DegToRad(mouseY * sensitivity));

			// Clamp X rotation
			Vector3 cameraRot = RotationDegrees;
			cameraRot.X = Mathf.Clamp(cameraRot.X, -90, 89);
			RotationDegrees = cameraRot;
			}
		}

		// Pause control
		if (@event is InputEventMouseButton && isPaused) {
			Input.MouseMode = Input.MouseModeEnum.Captured;
			isPaused = false;
		}
		else if (Input.IsActionJustPressed("ui_cancel")) {
			Input.MouseMode = Input.MouseModeEnum.Visible;
			isPaused = true;
		}
	}

	private void RotatePlayerModel(float rotationAmount)
	{
    	if (playerModel != null && !isPaused)
    	{
        	// Rotate the playerModel around the Y-axis
        	playerModel.Rotate(Vector3.Up, Mathf.DegToRad(rotationAmount));

			// Sync with player's rotation
			Rotation = new Vector3(Rotation.X, playerModel.Rotation.Y, 0);
			GD.Print(playerModel.RotationDegrees);
    	}
	}

	public override void _Process(double delta)
	{
		// Camera position smoothing
		Position = new Vector3(
			Mathf.Lerp(Position.X, playerModel.Position.X, cameraSmoothing),
			Mathf.Lerp(Position.Y, playerModel.Position.Y, cameraSmoothing),
			Mathf.Lerp(Position.Z, playerModel.Position.Z, cameraSmoothing)
		);
	}
}

You’re rotating the camera aroung Vector3.RIGHT where it says “Rotate based on mouse input”.
That’s the global axis, not reight relative to the camera. You want to rotate along dot(Rotation.Y, Vector.UP) I believe.

Thanks for the response!

I replaced the Rotate(Vector3.Right, Mathf.DegToRad(mouseY * sensitivity)); with code below, but it didn’t solve the problem, it still stops on 90 degrees. (sorry if that’s not what you meant)

float dotProduct = RotationDegrees.Dot(playerModel.RotationDegrees);
Vector3 newRotationAxis = new Vector3(dotProduct, 0, 0).Normalized();
Rotate(newRotationAxis, Mathf.DegToRad(mouseY * sensitivity));

I am not super confident with my math skills, but I don’t see you even tried what I said lol
Anyway, I messed up, I said dot product, I meant cross product, so try:

if (!isPaused)
{
  // Rotate based on mouse input
  Rotate(Vector3(0, Rotation.Y, 0).cross(Vector3.UP), Mathf.DegToRad(mouseY * sensitivity));
  // Simplify the next lines too =3
  // Clamp X rotation
  RotationDegrees = Mathf.Clamp(RotationDegrees.X, -90, 89);
}

Well… it works now on 90 degrees, but breakes on 180 and somehow moves a bit on the Z axis by like 0.0001 - 0.02 units, also I had to rewrite it a bit, because I got the “ERROR: The axis Vector3 must be normalized.”, when rotating on the Vector3.Up. For the simplification, you can’t clamp a Vector3 with Mathf.Clamp(), or change the RotationDegrees.X directly so it’s good how it is now.

This is my new version:

Rotate(new Vector3(0, Rotation.Y, 0).Cross(Vector3.Right).Normalized(), Mathf.DegToRad(mouseY * sensitivity));

And sorry for wasting your time but I’m relatively new to programming.

Ah, sorry, my bad. The last line should have been.

RotationDegrees = new Vector3(Mathf.Clamp(RotationDegrees.X, -90, 89), Rotation.Y, 0);

I use GDS, not C#, so pardon the syntax errors~ =nwn=

Alr the clamping works now and it’s not rotating on Z, but the main issue still remains. And don’t mind the syntax I can deal with it.

The hitch you are seeing around the 180 degrees might be caused because you are updating the character model within the input callback, which happens at the end of the frame, but CharacterBody3D updates its own data withing the physics callback, which happens before the process callback.
This means your input update happens after rendering, but using the data of the physics before rendering.
You can make a better use of all of these mechanisms by setting only custom variables at the input callback and then processing them appropriately withing the next physics callback. That way you make sure to always have the data you want to use AND change the data you want to change at the same point in time, right before rendering.
This will take a fairly large bit of reorganizing, but it’s not hard. And if it doesn’t solve the issue, then we can talk quaternions, but I don’t think this case grants that…

1 Like

Okay I treid quite a few things, but I was only able to eliminate most of the stutter and Z rotation but it still slows down on 180.

From what I’ve tried this worked best:

public override void _Input(InputEvent @event)
	{	
		// Set mouse variabes here
		if (@event is InputEventMouseMotion mouseMotion)
		{
			mouseMovement.X = mouseMotion.Relative.X;
			mouseMovement.Y = mouseMotion.Relative.Y;
		}

		// Pause control
		if (@event is InputEventMouseButton && isPaused) {
			Input.MouseMode = Input.MouseModeEnum.Captured;
			isPaused = false;
		}
		else if (Input.IsActionJustPressed("ui_cancel")) {
			Input.MouseMode = Input.MouseModeEnum.Visible;
			isPaused = true;
		}
	}

public override void _Process(double delta)
	{
		// Camera position smoothing
		Position = new Vector3(
			Mathf.Lerp(Position.X, playerModel.Position.X, cameraSmoothing),
			Mathf.Lerp(Position.Y, playerModel.Position.Y, cameraSmoothing),
			Mathf.Lerp(Position.Z, playerModel.Position.Z, cameraSmoothing)
		);

		RotatePlayerModel(mouseMovement.X * sensitivity);

		CompareMousePos();
		
		if (!isPaused)
		{
			// Rotate based on mouse X input
			Rotate(new Vector3(0, Rotation.Y, 0).Cross(Vector3.Right).Normalized(), Mathf.DegToRad(mouseMovement.Y * sensitivity));
		}

		if (RotationDegrees.X >= 89 || RotationDegrees.X <= -90){
			mouseMovement.X = -mouseMovement.X;
		}
		// Clamp X rotation
		RotationDegrees = new Vector3(Mathf.Clamp(RotationDegrees.X, -90, 89), 
                playerModel.RotationDegrees.Y, 0);
		GD.Print(RotationDegrees.X);
	}

void CompareMousePos()
	{
		// Check if rotation from previous frame is the same and set it to 0 to avoid sliding
		if (mouseMovement.X == mouseMovementPrev.X){
			mouseMovement.X = 0;
		}

		if (mouseMovement.Y == mouseMovementPrev.Y){
			mouseMovement.Y = 0;
		}

		// Remember the mouse pos for comparison next frame
		mouseMovementPrev.X = mouseMovement.X;
		mouseMovementPrev.Y = mouseMovement.Y;
	}

I now realize that this thing I told you is absolutely wrong and not at all what I meant. Math is hard.
This should work, unless, of course, I have made yet another colossal mistake:

Rotate(Vector3.FORWARD.rotated(Vector3.UP, Rotation.Y).cross(Vector3.UP), Mathf.DegToRad(mouseMovement.Y * sensitivity));

Allright it works perfectly thank you so much for help <3

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.