How to use angular velocity to rotate a rigid body with mouse input?

:information_source: Attention Topic was automatically imported from the old Question2Answer platform.
:bust_in_silhouette: Asked By Noddy

;TLDR I’m trying to use the mouse’s x coord to rotate the RigidBody FPS controller I made using angular velocity.

Right now I’m trying to create a RigidBody FPS controller. Not new I know, but the main issue is that I find myself disliking most of the other examples that are out on the market since they add so many of their own physics calculations it makes it seem pointless to use a RigidBody instead of a KinematicBody.

So far I’ve actually gotten some janky but decent progress. Though right now I’m trying to use the x coord of the mouse to rotate the RigidBody controller using angular_velocity/state.set_angular_velocity. It’s set up pretty much like the Godot Docs FPS Tutorial, but I’m trying to replace self.rotate_y() with my own physics integrated version since I’m trying to work out some of the jank that’s present. Good news about that, I can get the camera rotating using it, bad news, it won’t stop.

Here is my code:

extends RigidBody

var MOUSE_SENSITIVITY : float = 0.2

var look_vector : Vector3 = Vector3()

onready var rotation_helper = $Rotation_Helper
onready var camera = $Rotation_Helper/Camera
onready var feet = $Feet

var speed : float = 10
var max_speed : float = 20
var jump_speed : float = 10
var deacceleration : float = 0.1

var rot_y : float = 0

# Called when the node enters the scene tree for the first time.
func _ready():

func _physics_process(_delta):
	if Input.is_action_just_pressed("ui_cancel"):
		if Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:

func integrated_rotate_y(state, radians : float):
	state.set_angular_velocity(Vector3.UP * radians)

func _integrate_forces(state):
	if Input.is_action_pressed("move_forward"):
		linear_velocity += (Vector3.FORWARD * speed).rotated(Vector3.UP, rotation.y)
	if Input.is_action_pressed("move_backward"):
		linear_velocity += (Vector3.BACK * speed).rotated(Vector3.UP, rotation.y)
	if Input.is_action_pressed("move_left"):
		linear_velocity += (Vector3.LEFT * speed).rotated(Vector3.UP, rotation.y)
	if Input.is_action_pressed("move_right"):
		linear_velocity += (Vector3.RIGHT * speed).rotated(Vector3.UP, rotation.y)

	if Input.is_action_just_pressed("move_jump") and feet.is_colliding():
		linear_velocity.y += jump_speed

	integrated_rotate_y(state, rot_y)

	linear_velocity.x = clamp(linear_velocity.x, -max_speed, max_speed)
	linear_velocity.y = clamp(linear_velocity.y, -jump_speed, jump_speed)
	linear_velocity.z = clamp(linear_velocity.z, -max_speed, max_speed)

	linear_velocity.x = lerp(linear_velocity.x, 0, deacceleration)
	linear_velocity.z = lerp(linear_velocity.z, 0, deacceleration)

func _input(event):
	if event is InputEventMouseMotion and Input.get_mouse_mode() == Input.MOUSE_MODE_CAPTURED:
		rotation_helper.rotate_x(deg2rad(event.relative.y * MOUSE_SENSITIVITY * -1))
		rot_y += deg2rad(event.relative.x * MOUSE_SENSITIVITY * -1)
		rotation_helper.rotation_degrees.x = clamp(rotation_helper.rotation_degrees.x, -70, 70)

And my node setup looks like:
Player : RigidBody (mass = 3, continuous_cd, contact_monitor, axis lock: ang x, ang z)
=>Rotation_Helper : Spatial (translation.y = 1)
==>Camera (current, fov = 90)
=>CollisionShape (Shape = capsule, rotation_degrees.x = 90)
=>Feet : Raycast (cast_to = (0, -1.5, 0)

:bust_in_silhouette: Reply From: CardboardComputers

When the mouse moves, you add the x-delta to rot_y, and then you set the angular velocity to that value. That means that the angular velocity will depend only the x-position of the mouse. If you want to stop turning, you’d have to get rot_y to go back to zero, which generally means moving the mouse back to the center.

If you want to stop turning automatically, you need to decrease the angular velocity as the rotation approaches the target. This would basically involve the same micro-management as setting the rotation directly using move_toward or some kind of interpolation, and I would probably recommend that. There are a few reasons you might want to do this, depending on your setup and desired outcome. However, if you really want to use angular velocity, you would probably want to decrease your angular velocity over time so that you eventually stop turning.

I’ll assume you want the character to end up pointing at the mouse. In that case, instead of using the rot_y as the target velocity (even though you’d want it to be the target rotation), I’d recommend something like:

const TURN_SPEED_MAX := 4*PI # in radians/sec
const TURN_DAMP_THRESHOLD := 0.01 # at a difference of less than 0.01 radian, the turn speed will be slower
const TURN_SPEED_MIN := 0.001

# ...

func phyiscs_process(delta: float) -> void:
    # angular speed depends on how far you want to turn
    var rot_diff_y := rot_y - rotation.y
    var target_angular_velocity_y := rot_diff_y * TURN_SPEED_MAX / TURN_DAMP_THRESHOLD
    # at the same time, it should not exceed the maximum
    target_angular_velocity_y = minf(TURN_SPEED_MAX , abs(target_angular_velocity_y)) * sign(target_angular_velocity_y)
    # stop turning if the difference is super small
    if abs(target_angular_velocity_y) < TURN_SPEED_MIN:
        target_angular_velocity_y = 0.0
        # snap to the target orientation once you're close enough
        # depending on the numbers, this snap might be a little jarring
        rotation.y = rot_y

Admittedly I haven’t tested the code, but the idea is that as rot_diff_y approaches zero (i.e. the character turns to face the target direction), the angular velocity will quickly drop towards zero. By applying damping, the character can turn quickly during quick mouse movements without overshooting its target. However, because of the damping, the character will never actually stop turning, instead just getting slower and slower. The minimum speed check makes sure the character will stop turning when it gets close enough to its target orientation.