So I've got a camera controller that I'm trying to move smoothly. I'm trying to get it such that I press the button to move, the camera gradually increases speed, the when the button is released, the camera gradually comes to a stop. I'd like to do the same for camera rotation.

The code for the camera is as follows:


@export_range(1.0, 10.0, 0.5) var move_speed: float = 5.0
@export_range(0.5, 3.0, 0.25) var rot_speed: float = 1.5
@export_range(0.5, 3.0, 0.25) var camera_smoothness: float = 0.5

var camera_can_move: bool = true
var camera_can_rotate: bool = true

func _process(delta) -> void:
	move_camera(delta)
	rotate_camera(delta)

func move_camera(delta) -> void:
	if !camera_can_move: return

	# Get desired movement based on input
	var desired_movement = Vector3(
		Input.get_axis("camera_left", "camera_right"),
		0,
		Input.get_axis("camera_forward", "camera_backward")
	) * move_speed

	# Current camera position
	var current_position = global_transform.origin

	# Lerp between current and desired position with delta for smoothness
	var new_position = lerp(current_position, current_position + desired_movement, delta * camera_smoothness)

	# Update camera position
	translate(new_position - current_position)

func rotate_camera(delta) -> void:
	if !camera_can_rotate: return

	# Get desired rotation based on input
	var desired_rotation = Vector3(0, Input.get_axis("camera_cw", "camera_ccw"), 0) * rot_speed

	# Current camera rotation (assuming rotation around Y-axis)
	var current_rotation = rotation.y

	# Lerp between current and desired rotation with delta for smoothness
	var new_rotation = lerp(current_rotation, current_rotation + desired_rotation.y, delta * camera_smoothness)

	# Update camera rotation (assuming rotation around Y-axis)
	rotate_y(new_rotation - current_rotation)
  • xyz replied to this.
  • Lousifr To get the desired quaternion you need to multiply the current quaternion with relative quaternion that represents the delta rotation gained from input in the current frame:

    	# current orientation quat
    	var q_cur = global_transform.basis.get_rotation_quaternion()
    	# relative quat
    	var q_relative = Quaternion(Vector3.UP, Input.get_axis("camera_cw", "camera_ccw") * rot_speed)
    	# desired quat = current * relative
    	var q_desired = q_cur * q_relative
    	# slerp to desired quat
    	var q_new = q_cur.slerp(q_desired, camera_smoothness * delta)
    	# assign slerped quat
    	global_transform.basis = Basis(q_new)

    Lousifr Don't work with euler angles when dealing with orientation in 3d space. Instead, slerp the camera orientation quaternions.

    I used https://docs.godotengine.org/en/4.2/classes/class_@globalscope.html#class-globalscope-method-lerp-angle to smoothly rotate something towards a certain angle. If you only want to rotate around the y axis I guess this is good.

    I used it to rotate the player into the movement direction relative to my camera:

    var input_dir := Input.get_vector("move_left", "move_right", "move_forward", "move_backward")
    var direction := (camera.transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized()
    	if direction:
                  player.rotation.y = lerp_angle(player.rotation.y, atan2(-direction.x, -direction.z), 0.3)

    I'd ultimately like to include rotation along the x-axis as well. The solution is going to be simple, so I'm sorry for showing my noobishness. Updating the rotation function, how would I apply the new rotation to the camera's global transform?

    func rotate_camera(delta) -> void:
    	if !camera_can_rotate: return
    
    	# Get current camera rotation
    	var camera_rotation = Quaternion.IDENTITY
    	# Get desired rotation based on input
    	var desired_rotation = Quaternion(Vector3.UP, Input.get_axis("camera_cw", "camera_ccw") * rot_speed * delta);
    	# Slerp between current and desired rotation with delta for smoothness
    	var new_rotation = camera_rotation.slerp(desired_rotation, delta * camera_smoothness)
    	# Apply the new rotation to the camera's global transform
            ?????????
    • xyz replied to this.

      Lousifr First, you should interpolate between the current quaternion and the desired quaternion, not between identity and desired.

      Once you get the interpolated quaternion, you can simply assign it to camera's quaternion property. Alternatively, if there is some hierarchy involved and you need to operate in global space, you can construct a new global Basis from the quaternion and assign it to camera's global_basis property: cam.global_basis = Basis(interpolated_quaternion)

        xyz
        The following code try's to rotate the camera, but doesn't rotate more than a few degrees and returns to original position once the button is released.

        func rotate_camera(delta) -> void:
        	if !camera_can_rotate: return
        	# Get camera rotation quaternion
        	var camera_rotation = global_transform.basis.get_rotation_quaternion()
        	# Get desired rotation based on input
        	var desired_rotation = Quaternion(Vector3.UP, Input.get_axis("camera_cw", "camera_ccw") * rot_speed * delta)
        	# Slerp between current and desired rotation with delta for smoothness
        	var new_rotation = camera_rotation.slerp(desired_rotation, delta * camera_smoothness)
        	# Update the rotation component of the camera's global transform
        	global_transform.basis = Basis(new_rotation)
        • xyz replied to this.

          Lousifr That's because you constantly interpolate to absolute desired rotation. Even if zero vector is returned by Input.get_axis() your code will still interpolate to identity quaternion each frame.

          You should update desired_rotation only when there is some input. If there is no input, it should retain its last value, not be re-assigned the identity as is currently the case.

          Your second problem (only minimal rotation) probably happens because you multiply the input axis vector with delta, which makes no sense whatsoever. On top if it you're using this value as an absolute desired value when it actually represents the relative addition to the desired value.

            xyz This almost works, but now it's rotating to a set value and not updating, and when I "over extend" the rotation, it will rotate backwards until a set value until I "over extend" the rotation again:

            func rotate_camera(delta) -> void:
            	if !camera_can_rotate: return
            	# Get desired rotation based on input
            	if Input.get_axis("camera_cw", "camera_ccw") != 0:
            		# Get camera rotation quaternion
            		var camera_rotation = global_transform.basis.get_rotation_quaternion()
            		var desired_rotation = Quaternion(Vector3.UP, Input.get_axis("camera_cw", "camera_ccw") * rot_speed)
            		# Slerp between current and desired rotation with delta for smoothness
            		var new_rotation = camera_rotation.slerp(desired_rotation, camera_smoothness * delta)
            		# Update the rotation component of the camera's global transform
            		global_transform.basis = Basis(new_rotation)

            I can upload gifs if my explanation makes no sense.

            • xyz replied to this.

              Lousifr To get the desired quaternion you need to multiply the current quaternion with relative quaternion that represents the delta rotation gained from input in the current frame:

              	# current orientation quat
              	var q_cur = global_transform.basis.get_rotation_quaternion()
              	# relative quat
              	var q_relative = Quaternion(Vector3.UP, Input.get_axis("camera_cw", "camera_ccw") * rot_speed)
              	# desired quat = current * relative
              	var q_desired = q_cur * q_relative
              	# slerp to desired quat
              	var q_new = q_cur.slerp(q_desired, camera_smoothness * delta)
              	# assign slerped quat
              	global_transform.basis = Basis(q_new)