Why does my head rotate super incorrectly?

Godot Version

4.0

Question

In my code, I was following a tutorial. In the tutorial, the code worked perfectly fine. It was a 4.0 tutorial aswell. I followed the exact instructions, my head however rotates diagonally and sometimes messes up the entire player model (a capsule) which makes it fall down or walk reverse. There are no visible errors, so I’m very confused.

My code snippet:

func _ready():
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	
func _input(event):
	if event is InputEventMouseMotion:
		head.rotate_y(deg_to_rad(-event.relative.x * mouse_sensitivity))
		head.rotate_x(deg_to_rad(-event.relative.y * mouse_sensitivity))
		head.rotation.y = clamp(head.rotation.y, deg_to_rad(-89), deg_to_rad(89))

My entire code if needed:

extends CharacterBody3D

@onready var head = $Head

var current_speed = 5.0

const walking_speed = 5.0
const sprinting_speed = 8.0
const crouching_speed = 2.0
const crouching_depth = 0.5

const jump_velocity = 4.5

const mouse_sensitivity = 0.05

const lerp_speed = 5.0

# Get the gravity from the project settings to be synced with RigidBody nodes.
var gravity = ProjectSettings.get_setting("physics/3d/default_gravity")

func _ready():
	Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
	
func _input(event):
	if event is InputEventMouseMotion:
		head.rotate_y(deg_to_rad(-event.relative.x * mouse_sensitivity))
		head.rotate_x(deg_to_rad(-event.relative.y * mouse_sensitivity))
		head.rotation.y = clamp(head.rotation.y, deg_to_rad(-89), deg_to_rad(89))
		

func _physics_process(delta):
	if Input.is_action_pressed("sprint"):
		current_speed = sprinting_speed
	else:
		current_speed = walking_speed
	
	if Input.is_action_pressed("crouch"):
		current_speed = crouching_speed
		head.position.y = lerp(head.position.y, 1.8 - crouching_depth, delta * lerp_speed)
	else:
		head.position.y = lerp(head.position.y, 1.8 + crouching_depth, delta * lerp_speed)
		current_speed = walking_speed
	# Add the gravity.
	if not is_on_floor():
		velocity.y -= gravity * delta

	# Handle jump.
	if Input.is_action_just_pressed("ui_accept") and is_on_floor():
		velocity.y = jump_velocity

	# Get the input direction and handle the movement/deceleration.
	# As good practice, you should replace UI actions with custom gameplay actions.
	var input_dir = Input.get_vector("ui_left", "ui_right", "ui_up", "ui_down")
	var direction = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
	if direction:
		velocity.x = -direction.x * current_speed
		velocity.z = -direction.z * current_speed
	else:
		velocity.x = move_toward(velocity.x, 0, current_speed)
		velocity.z = move_toward(velocity.z, 0, current_speed)

	move_and_slide()

Check this out:

The problem is that you rotate without resetting the basis

# accumulators
var rot_x = 0
var rot_y = 0

func _input(event):
	if event is InputEventMouseMotion and event.button_mask & 1:
		# modify accumulated mouse rotation
		rot_x += event.relative.x * LOOKAROUND_SPEED
		rot_y += event.relative.y * LOOKAROUND_SPEED
		transform.basis = Basis() # reset rotation
		rotate_object_local(Vector3(0, 1, 0), rot_x) # first rotate in Y
		rotate_object_local(Vector3(1, 0, 0), rot_y) # then rotate in X

There are other ways of doing this but I think this will fix it for you

This does fix the original issue, however I can’t seem to limit the Y rotation, which lets my player do a complete turn from up to down, such as in the prior code where I clamped the Y rotation.
Edit: I just realized that it’s physically moving the player body, rather than just the head, is there way to make it so it doesn’t actually move the player body? If I do head.rot_x and y it says “Invalid to get index ‘rot_x’ (on base: ‘Node3D’)”

rot_x and rot_y are variables you need to define.

before I pasted that code I modified it, but didnt test i retested and it had some edge case issues. I will come back to this with a better answer.

working mouse look

func look(mouse_movement:Vector2):
	rotate_object_local(Vector3.LEFT,mouse_movement.y * speed)
	if transform.basis.y.y < 0.0:
		self.rotation.x = -PI/2.0 * sign(transform.basis.z.y)
	rotate_y(-mouse_movement.x * speed)

transform.basis.y.y < 0.0 checks if the the basis vector is below the horizen. i.e. you are looking straight up, or straight down. the top of your head is pointing at the horizon

sign(transform.basis.z.y) tells me if I’m looking at the ground or in the air (1 or -1).

I needed to throw some minus signs around to change inversion of the controls

I think the issue with Godot 4 is that the max rotation is PI/2 and then it starts going back down as you rotate. the editor input shows you are able to go beyond +/-90 degrees, but internally the rotation is in radians and it maxes out at pi/2 (90 degrees).

basically it is impossible to have a value over 90 degrees to clamp on. and with your code you have set to 89 it may be that your sensitivity overshoots the limit by 2 or more degrees after the adjustment. I think if you slowed down your sensitivity you would get limited on the boundary you set.

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