Godot Version
v4.6.1.stable.official [14d19694e]
Question
Im trying to set up FPS controller. Here is the problem: I set up a scene with a tunnel I can access only if Im crouching or sliding into it. When Im crouch walking into it everything is fine. But when Im running and then sliding into this tunnel a problem emerges. If I hold crouch at the end of a slide I transition into crouching state so everything is fine, but if Im not holding “crouch“ button at the end of my slide Im ending up in a position where I still in the tunnel and my collision is, as expected, crouching collision, but the speed at which I move is running speed as if Im standing, and camera position end up, as expected, at crouching level, but slightly higher than it supposed to be, as if its stuck inbetween. First thing that came to mind is to abandon this mess and to set up a proper state machine with clear transitions between separate states, but Im not knowledgeable enough to do it at the moment. Im basically learning engine and programming as I go and simple boolean operations is as far as my knowledge goes. So how do I fix it within my current framework?
Here is my code:
extends CharacterBody3D
var RUNNING : bool = false
var WALKING : bool = false
var CROUCHING : bool = false
var SLIDING : bool = false
var JUMPING : bool = false
var input_dir : Vector2 = Vector2.ZERO
var direction : Vector3 = Vector3.ZERO
var current_speed : float = 0.0
var last_velocity : Vector3 = Vector3.ZERO
@export_group("Movement Variables")
@export var GRAVITY : float = 20.0
@export var JUMP_VELOCITY : float = 7.5
@export var CROUCH_DEPTH : float = 0.8
@export_group("Speed Variables")
@export var RUN_SPEED : float = 7.2
@export var WALK_SPEED : float = 3.6
@export var CROUCH_SPEED : float = 3.6
@export_group("Slide Variables")
@export var SLIDE_SPEED : float = 7.2
@export var SLIDE_TIMER_MAX : float = 0.6
var slide_timer : float = 0.0
var slide_vector : Vector2 = Vector2.ZERO
@export_group("Other Variables")
@export var SENSITIVITY : float = 0.001
@export var LERP_SPEED : float = 8.0
@export var AIR_LERP_SPEED : float = 2.0
func _ready() -> void:
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _physics_process(delta: float) -> void:
if not is_on_floor():
velocity.y -= GRAVITY * delta
input_dir = Input.get_vector("move_left", "move_right", "move_forward", "move_back")
#JUMPING
if Input.is_action_just_pressed("jump") and is_on_floor():
velocity.y = JUMP_VELOCITY
%CameraAnimPlayer.play("Jump")
self.rotation.y += %CameraHandler.rotation.y
%CameraHandler.rotation.y = 0.0 #I need this so at the end of the slide CharacterBody3D y rotation wont be displaced relative to camera's y rotation
RUNNING = false
WALKING = false
CROUCHING = false
SLIDING = false
JUMPING = true
#Handles landing
if is_on_floor():
if last_velocity.y < -15.0:
%CameraAnimPlayer.play("JumpLandingHeavyBob")
print(last_velocity.y)
elif last_velocity.y < -4.0:
%CameraAnimPlayer.play("JumpLandingBob")
print(last_velocity.y)
#CROUCHING
if Input.is_action_pressed("crouch") or SLIDING:
%StandCollisionShape.disabled = true
%CrouchCollisionShape.disabled = false
current_speed = lerp(current_speed, CROUCH_SPEED, delta * LERP_SPEED)
%CameraHandler.position.y = lerp(%CameraHandler.position.y, 1.5 - CROUCH_DEPTH, delta * LERP_SPEED)
#Slide begin logic
if RUNNING and input_dir != Vector2.ZERO and is_on_floor():
SLIDING = true
slide_timer = SLIDE_TIMER_MAX
slide_vector = input_dir
print("SlideBegin")
RUNNING = false
WALKING = false
CROUCHING = true
JUMPING = false
elif !%CanStandCheck.is_colliding():
#Standing
%StandCollisionShape.disabled = false
%CrouchCollisionShape.disabled = true
%CameraHandler.position.y = lerp(%CameraHandler.position.y, 1.5, delta * LERP_SPEED)
if Input.is_action_pressed("run") and is_on_floor():
#Walk/run (sorry for confusing mess, its flipped for now cause I want to be running by default)
current_speed = lerp(current_speed, WALK_SPEED, delta * LERP_SPEED)
self.rotation.y += %CameraHandler.rotation.y
%CameraHandler.rotation.y = 0.0
RUNNING = false
WALKING = true
CROUCHING = false
SLIDING = false
JUMPING = false
else:
current_speed = lerp(current_speed, RUN_SPEED, delta * LERP_SPEED)
self.rotation.y += %CameraHandler.rotation.y
%CameraHandler.rotation.y = 0.0
RUNNING = true
WALKING = false
CROUCHING = false
SLIDING = false
JUMPING = false
#Handles SLIDING
if SLIDING:
slide_timer -= delta
if not Input.is_action_pressed("crouch") or slide_timer <= 0.0:
SLIDING = false
self.rotation.y += %CameraHandler.rotation.y
%CameraHandler.rotation.y = 0.0
print("SlideEnd")
#Horizontal walk bob logic
if input_dir != Vector2.ZERO:
play_walk_bob_anim()
else:
%WalkBobAnimPlayer.pause()
# Get the input direction and handle the movement/deceleration.
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.9) * SLIDE_SPEED
if direction:
velocity.x = direction.x * current_speed
velocity.z = direction.z * current_speed
#Last velocity is used to determine that player landed after a jump so it can play landing anim
last_velocity = velocity
move_and_slide()
#Mouse look logic
func _unhandled_input(event: InputEvent) -> void:
if event is InputEventMouseMotion:
if SLIDING:
%CameraHandler.rotation.y = %CameraHandler.rotation.y - event.relative.x * SENSITIVITY
%CameraHandler.rotation.x = %CameraHandler.rotation.x - event.relative.y * SENSITIVITY
%CameraHandler.rotation.x = clamp(%CameraHandler.rotation.x, deg_to_rad(-90), deg_to_rad(90))
else:
rotation.y = rotation.y - event.relative.x * SENSITIVITY
%CameraHandler.rotation.x = %CameraHandler.rotation.x - event.relative.y * SENSITIVITY
%CameraHandler.rotation.x = clamp(%CameraHandler.rotation.x, deg_to_rad(-90), deg_to_rad(90))
#For horizontal walk bob
func play_walk_bob_anim() -> void:
if RUNNING and is_on_floor():
%WalkBobAnimPlayer.play("WalkBob")
%WalkBobAnimPlayer.speed_scale = 1.0
elif WALKING and is_on_floor():
%WalkBobAnimPlayer.play("WalkBob")
%WalkBobAnimPlayer.speed_scale = 0.75
elif CROUCHING and is_on_floor():
%WalkBobAnimPlayer.play("WalkBob")
%WalkBobAnimPlayer.speed_scale = 0.5
else:
%WalkBobAnimPlayer.pause()