Coding_Guy
Coding_Guy

Reputation: 151

godot4 first person sliding issue

After following these tutorials (first person controller series by https://www.youtube.com/@lukky.) and making a first person controller in godot4.

Every time I finish sliding, my camera snaps back to the front. But how can I avoid that and instead keep the position? Better explanation: I don't like it how after you slide, you snap back to your front. I want to just continue looking at was I was looking, so I don't have to move my mouse again, and can instantly go.

My code:

extends CharacterBody3D

# Player nodes

@onready var neck = $Neck
@onready var head = $Neck/Head
@onready var eyes = $Neck/Head/Eyes

@onready var standing_collision_shape = $StandingCollisionShape
@onready var crouching_collisiong_shape = $CrouchingCollisiongShape
@onready var up_ray_cast = $UpRayCast
@onready var camera_3d = $Neck/Head/Eyes/Camera3D
@onready var animation_player = $Neck/Head/Eyes/AnimationPlayer

# Speed vars

var current_speed = 5.0

const walking_speed = 5.0
const sprinting_speed = 8.0
const crouching_speed = 3.0

# States

var walking = false
var sprinting = false
var crouching = false
var free_looking = false
var sliding = false

# Slide vars

var slide_timer = 0.0
var slide_timer_max = 1.0
var slide_vector = Vector2.ZERO
var slide_speed = 13.0

# Head bobbing vars

const head_bobbing_sprinting_speed = 22.0
const head_bobbing_walking_speed = 14.0
const head_bobbing_crouching_speed = 10.0

const head_bobbing_sprinting_intensity = 0.2
const head_bobbing_walking_intensity = 0.1
const head_bobbing_crouching_intensity = 0.05

var head_bobbing_vector = Vector2.ZERO
var head_bobbing_index = 0.0
var head_bobbing_current_intensity = 0.0

# Movement vars

const jump_velocity = 5.5

var crouching_depth = -0.5

var lerp_speed = 10.0

var air_lerp_speed = 3.0

var free_look_tilt_amount = 8

var last_velocity = Vector3.ZERO

# Input vars

var direction = Vector3.ZERO
const mouse_sens = 0.1


# 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):
    # Mouse looking logic
    if event is InputEventMouseMotion:
        if free_looking:
            neck.rotate_y(deg_to_rad(-event.relative.x * mouse_sens))
            neck.rotation.y = clamp(neck.rotation.y, deg_to_rad(-120), deg_to_rad(120))
        else:
            rotate_y(deg_to_rad(-event.relative.x * mouse_sens))
        head.rotate_x(deg_to_rad(-event.relative.y * mouse_sens))
        head.rotation.x = clamp(head.rotation.x, deg_to_rad(-89), deg_to_rad(89))
    
    
func _physics_process(delta):
    # Getting movement input
    var input_dir = Input.get_vector("left", "right", "forward", "backward")
    
    # Handle movement state
    
    # Crouching
    if Input.is_action_pressed("crouch") || sliding:
        current_speed = lerp(current_speed, crouching_speed, delta * lerp_speed)
        head.position.y = lerp(head.position.y, crouching_depth, delta * lerp_speed)
        
        crouching_collisiong_shape.disabled = false
        standing_collision_shape.disabled = true
        
        # Slide begin logic
        if sprinting && input_dir != Vector2.ZERO:
            sliding = true
            slide_timer = slide_timer_max
            slide_vector = input_dir
            free_looking = true
        
        walking = false
        sprinting = false
        crouching = true

    elif not up_ray_cast.is_colliding():
        
        # Standing 
        
        standing_collision_shape.disabled = false
        crouching_collisiong_shape.disabled = true
        head.position.y = lerp(head.position.y, 0.0, delta * lerp_speed)
        if Input.is_action_pressed("sprint"):
            # Sprinting
            current_speed = lerp(current_speed, sprinting_speed, delta * lerp_speed)
            
            walking = false
            sprinting = true
            crouching = false
        else:
            # Walking
            current_speed = lerp(current_speed, walking_speed, delta * lerp_speed)
            
            walking = true
            sprinting = false
            crouching = false
    
    
    # Handle free looking
    if Input.is_action_pressed("free_look") || sliding:
        free_looking = true
        
        if sliding:
            eyes.rotation.z = lerp(eyes.rotation.z, -deg_to_rad(7.0), delta * lerp_speed)
        else:
            eyes.rotation.z = -deg_to_rad(neck.rotation.y * free_look_tilt_amount)
    else:
        free_looking = false
        neck.rotation.y = lerp(neck.rotation.y, 0.0, delta * lerp_speed)
        eyes.rotation.z = lerp(eyes.rotation.z, 0.0, delta * lerp_speed)
    
    # Hanle sliding
    if sliding:
        slide_timer -= delta
        if slide_timer <= 0:
            sliding = false
            free_looking = false
    
    # Handle headbob
    if sprinting:
        head_bobbing_current_intensity = head_bobbing_sprinting_intensity
        head_bobbing_index += head_bobbing_sprinting_speed * delta
    elif walking:
        head_bobbing_current_intensity = head_bobbing_walking_intensity
        head_bobbing_index += head_bobbing_walking_speed * delta
    elif crouching:
        head_bobbing_current_intensity = head_bobbing_crouching_intensity
        head_bobbing_index += head_bobbing_crouching_speed * delta
    
    if is_on_floor() && !sliding && input_dir != Vector2.ZERO:
        head_bobbing_vector.y = sin(head_bobbing_index)
        head_bobbing_vector.x = sin(head_bobbing_index / 2) + 0.5
        
        eyes.position.y = lerp(eyes.position.y, head_bobbing_vector.y * (head_bobbing_current_intensity / 2.0), delta * lerp_speed) # can tweak 2.0
        eyes.position.x = lerp(eyes.position.x, head_bobbing_vector.x * head_bobbing_current_intensity, delta * lerp_speed)
    else:
        eyes.position.y = lerp(eyes.position.y, 0.0, delta * lerp_speed)
        eyes.position.x = lerp(eyes.position.x, 0.0, delta * lerp_speed)
    
    
    
    
    
    # ------------MOVEMENT CODE------------ #
    
    # Add the gravity.
    if not is_on_floor():
        velocity.y -= gravity * delta

    # Handle Jump.
    if Input.is_action_just_pressed("jump") and is_on_floor():
        velocity.y = jump_velocity
        sliding = false # MAYBE LATER MAKE "C" SLIDE CANCEL INSTEAD OF SPACE (But still keep slide jumping)
        animation_player.play("jump")
        
    # Handle landing
    if is_on_floor():
        if last_velocity.y < -10.0:
            animation_player.play("roll")
        elif last_velocity.y < -4.0:
            animation_player.play("landing")

    # Get the input direction and handle the movement/deceleration.
    # As good practice, you should replace UI actions with custom gameplay actions.
    if is_on_floor():
        direction = lerp(direction, transform.basis * Vector3(input_dir.x, 0, input_dir.y).normalized(), delta * lerp_speed)
    else:
        if input_dir != Vector2.ZERO:
            direction = lerp(direction, transform.basis * Vector3(input_dir.x, 0, input_dir.y).normalized(), delta * air_lerp_speed)
    
    if sliding:
        direction = (transform.basis * Vector3(slide_vector.x, 0, slide_vector.y)).normalized()
        current_speed = (slide_timer + 0.1) * slide_speed
    
    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)
    
    last_velocity = velocity

    # RESPAWN
    if position.y < -10.0:
        position = Vector3(0, 0, 0)
        last_velocity = Vector3.ZERO
    
    move_and_slide()

enter image description here

Upvotes: 1

Views: 171

Answers (1)

Theraot
Theraot

Reputation: 40295

So here is the issue:

    # Handle free looking
    if Input.is_action_pressed("free_look") || sliding:
        free_looking = true
        
        if sliding:
            eyes.rotation.z = lerp(eyes.rotation.z, -deg_to_rad(7.0), delta * lerp_speed)
        else:
            eyes.rotation.z = -deg_to_rad(neck.rotation.y * free_look_tilt_amount)
    else:
        free_looking = false
        neck.rotation.y = lerp(neck.rotation.y, 0.0, delta * lerp_speed)
        eyes.rotation.z = lerp(eyes.rotation.z, 0.0, delta * lerp_speed)

This code will lerp the rotations when the character is not sliding and the player is not pressing "free_look".

I want to mention that this use of lerp is frame rate dependent, and move on.

Now let us take inventory of the rotations affected by the code above:

  • eyes.rotation.z
  • neck.rotation.y

And let us compare with the code that actually lets you look around:

func _input(event):
    # Mouse looking logic
    if event is InputEventMouseMotion:
        if free_looking:
            neck.rotate_y(deg_to_rad(-event.relative.x * mouse_sens))
            neck.rotation.y = clamp(neck.rotation.y, deg_to_rad(-120), deg_to_rad(120))
        else:
            rotate_y(deg_to_rad(-event.relative.x * mouse_sens))
        head.rotate_x(deg_to_rad(-event.relative.y * mouse_sens))
        head.rotation.x = clamp(head.rotation.x, deg_to_rad(-89), deg_to_rad(89))

Here we move:

  • neck.rotation.y
  • head.rotation.x
  • rotation.y

And we find that the overlap is neck.rotation.y.

We also observe that when free_looking the code rotates the neck around the y axis, and otherwise it moves the body around the y axis.

Thus, it seems that what we want is to transfer the rotation of the neck to the body when it stops free_looking (here I'm doing instantly):

    # Handle free looking
    if Input.is_action_pressed("free_look") || sliding:
        free_looking = true
        
        if sliding:
            eyes.rotation.z = lerp(eyes.rotation.z, -deg_to_rad(7.0), delta * lerp_speed)
        else:
            eyes.rotation.z = -deg_to_rad(neck.rotation.y * free_look_tilt_amount)
    else:
        if free_looking:
            rotation.y = neck.rotation.y
            neck.rotation.y = 0.0
            free_looking = false
        
        eyes.rotation.z = lerp(eyes.rotation.z, 0.0, delta * lerp_speed)

Another option is to remove the neck and always rotate the body around the y axis, and don't reset it ever, which seems to work on my tests. However, I do not know if you need the orientation of the body to remain the same for other purposes.

func _input(event):
    # Mouse looking logic
    if event is InputEventMouseMotion:
        if free_looking:
            rotate_y(deg_to_rad(-event.relative.x * mouse_sens))
            rotation.y = clamp(rotation.y, deg_to_rad(-120), deg_to_rad(120))
        else:
            rotate_y(deg_to_rad(-event.relative.x * mouse_sens))
        head.rotate_x(deg_to_rad(-event.relative.y * mouse_sens))
        head.rotation.x = clamp(head.rotation.x, deg_to_rad(-89), deg_to_rad(89))
    # Handle free looking
    if Input.is_action_pressed("free_look") || sliding:
        free_looking = true
        
        if sliding:
            eyes.rotation.z = lerp(eyes.rotation.z, -deg_to_rad(7.0), delta * lerp_speed)
        else:
            eyes.rotation.z = -deg_to_rad(rotation.y * free_look_tilt_amount)
    else:
        free_looking = false
        eyes.rotation.z = lerp(eyes.rotation.z, 0.0, delta * lerp_speed)

It is also possible to smoothly transfer the rotation between neck and body. The gist of it is that every frame you remove rotation from the neck and add it to the body.

I'm not a fan of using lerp this way, so I'll leave the exercise to you.

Upvotes: 1

Related Questions