1

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

2
  • For what I read in your code you have two ways to control the camera, and you switch based on the free_looking variable, and in the "sliding" part of the code you set it to free_looking = false, does changing to to true do what you want?
    – Theraot
    Commented Sep 30, 2023 at 8:23
  • no. I tried that already.
    – Coding_Guy
    Commented Oct 4, 2023 at 5:58

1 Answer 1

1
+200

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.

2
  • can you please send me your project if possible?
    – Coding_Guy
    Commented Oct 7, 2023 at 6:13
  • @Coding_Guy I made this as part of a project where I throw stuff I'm experimenting with, so I would rather not. Instead I made a gist with the player script and the scene file: gist.github.com/theraot/ae77307db79eaf0c4beebb27d218bf19 - they were in the folder "res://scenes/world/" in the project. I hope that helps. Addendum: I remind you that I proposed multiple solutions, so that is just the last thing I tried. Also: I use static typing for everything in my code.
    – Theraot
    Commented Oct 7, 2023 at 7:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.