How would I go about making a KinematicBody3D lean towards its velocity direction

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

I have a character
that has a velocity value,
but I want to try make it be able to lean towards the current velocity value
like in this video: https://youtu.be/SAtwQa8t_3g?t=31
The problem is I don`t know how I would go about doing this.
Any help is appreciated!

:bust_in_silhouette: Reply From: haydenv

If you’re okay with the lean being instantaneous instead of smooth, then rotate(axis, lean_angle) should do the trick. The lean angle can be 30 degrees, or PI/6 radians. The rotation axis must be perpendicular to both the velocity and the up direction. So var axis = velocity.cross(Vector2(0,1,0)) should do the trick. We used the vector cross product to find a vector perpendicular to two other vectors.


A smoother lean can be achieved by interpolating quaternions, which is usually the best way of interpolating rotations because it doesn’t result in a jaggedy rotation (unlike interpolating Euler rotation vectors). I’ll give a sketch, but there might be a mistake or two. All the following code should be in _physics_process so that it’s not dependent on framerate, and should be on a Spatial node.

# Find the current quaternion from the current transform. 
var current_quat = transform.basis.get_rotation_quat()

# Find the quaternion you want to interpolate towards based on the current velocity 
# and the maximum lean angle (say PI / 6 radians). 
var desired_quat = Quat(velocity.cross(Vector2(0,1,0)).normalized(), PI / 6)

# Calculate an interpolated quaternion (using so-called spherical linear interpolation). 
var next_quat = current_quat.slerp(desired_quat, 0.5)

# Make the character lean by updating the character's transform. 
transform.basis = Basis(next_quat)

The 0.5 means next_quat will be halfway between current_quat and desired_quat. Decreasing this value closer to 0.0 makes the lean slower, and increasing the value closer to 1.0 will makes the lean faster. But no matter what the lean will start faster than it ends (i.e. ease out but not ease in).

So that is a sketch of how to interpolate rotations with quaternions. It’s supposed to give you a smooth lean even when the player is turning towards a new direction.

Feel free to comment further questions.

Thanks dude! Thank you for including the awnser and an explination!

Stefoto | 2022-07-18 15:20

One last question! if I wanted the character also rotate towards the Up and Down movement (i.e. jumping and falling down). how would I need to change the current script to do that?

Stefoto | 2022-07-18 16:15

Okay no problem. Let’s break down how the leaning works with the jumping in the video you linked.

Firstly, if the character wasn’t moving forwards when they jumped then there wasn’t any forward or backward lean. Secondly, if the character jumped while moving forwards, then the forward lean transitioned into a backward lean near the end of the jump. So let’s aim to make a jump that will transition from a forward lean to a backward lean if the character was moving forwards when they jumped.

Keep three boolean variables var going_forward = false, var going_up = false, and var going_down = false that you use to keep track of whether the player is going forward, upwards, or downwards.

Set the values every physics cycle

if is_zero_approx(Vector2(velocity.x, velocity.z).length()):
    going_forward = false
else:
    going_forward = true
if is_zero_approx(velocity.y):
    going_up = false
    going_down = false
else:
    if velocity.y > 0:
        going_up = true
        going_down = false
    else:
        going_up = false
        going_down = true

Also keep a constant const LEAN_ANGLE = PI / 6.

Use the booleans to influence the desired lean angle value like this

var desired_lean_angle = int(going_forward) * (1 - int(going_up) - 2 * int(going_down)) * LEAN_ANGLE
var desired_quat = Quat(velocity.cross(Vector2(0,1,0)).normalized(), desired_lean_angle)

The desired_lean_angle calculation assigns the same value as the following if statements would

if not going_forward:
    desired_lean_angle = 0.0
else:
    if going_up: 
        desired_lean_angle = 0.0
    elif going_down: 
        desired_lean_angle = -LEAN_ANGLE
    else: 
        desired_lean_angle = LEAN_ANGLE

One issue with this is that it will jerk into the backwards lean near the top of the jump. I suppose a more advanced approach would be to make the animations for the forward movement and the jump/fall separately, and then blend them together using the various animation nodes available in Godot. But I’m not sure how to do that.

    
    

haydenv | 2022-07-19 19:13

That has pointed me in the right direction for making the lean to work! Thank you so much for the help you`ve given me, I would have not been able to do this for my game without you help!

Stefoto | 2022-07-19 21:36