Reputation: 151
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()
Upvotes: 1
Views: 171
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