class_name Player extends CharacterBody3D @export var camera_acceleration: float = 10.0 @export var crouch_height: float = 0.95 ## Camera and RayCast height @export var crouch_speed: float = 3.0 @export var jump_velocity: float = 4.5 @export var mouse_sensitivity_horizontal: float = 0.002 @export var mouse_sensitivity_vertical: float = 0.002 @export var run_speed: float = 8 @export var standing_height: float = 1.8 ## Camera and RayCast height @export var walk_speed: float = 5.0 @onready var camera: Camera3D = $Camera @onready var collision_shape_crouching: CollisionShape3D = $CollisionShapeCrouching @onready var collision_shape_standing: CollisionShape3D = $CollisionShapeStanding @onready var ray_cast_look: RayCast3D = $RayCastLook @onready var ray_cast_crouch: RayCast3D = $RayCastCrouch var current_speed: float = 0 var is_crouching: bool = false var is_running: bool = false func _physics_process(delta: float) -> void: is_crouching = Input.is_action_pressed("crouch") apply_gravity(delta) handle_jumping() set_is_running() handle_movement() handle_crouching(delta) move_and_slide() func _unhandled_input(event: InputEvent) -> void: if event is InputEventMouseMotion: handle_mouse_look(event) func apply_gravity(delta: float) -> void: if is_on_floor(): return velocity += get_gravity() * delta ## It's not generally recommended to modify the shape/size of a collision shape during runtime. ## For this reason, we have 2 collision shapes, with 1 disabled at all times ## along with the changing the height of the camera (using lerp for smoother movement) func handle_crouching(delta: float) -> void: if is_crouching: collision_shape_crouching.disabled = false collision_shape_standing.disabled = true camera.transform.origin.y = lerp(camera.transform.origin.y, crouch_height, camera_acceleration * delta) ray_cast_look.transform.origin.y = crouch_height elif !ray_cast_crouch.is_colliding(): collision_shape_crouching.disabled = true collision_shape_standing.disabled = false camera.transform.origin.y = lerp(camera.transform.origin.y, standing_height, camera_acceleration * delta) ray_cast_look.transform.origin.y = standing_height else: pass # Continue crouching until nothing overhead func handle_jumping() -> void: if not Input.is_action_just_pressed("jump"): return if not is_on_floor(): return velocity.y = jump_velocity func handle_mouse_look(event: InputEvent) -> void: rotation.y = rotation.y - event.relative.x * mouse_sensitivity_horizontal camera.rotation.x = camera.rotation.x - event.relative.y * mouse_sensitivity_vertical camera.rotation.x = clamp(camera.rotation.x, deg_to_rad(-90), deg_to_rad(90)) ray_cast_look.rotation.x = camera.rotation.x func handle_movement() -> void: var input_dir: Vector2 = Input.get_vector("move_left", "move_right", "move_forward", "move_backward") var direction: Vector3 = (transform.basis * Vector3(input_dir.x, 0, input_dir.y)).normalized() set_movement_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) ## Determine if should be running. This needs to run prior to handle_movement(). ## Running is disabled when:[br] ## 1. The `run` input is pressed (ie toggled)[br] ## 2. Is no longer moving func set_is_running() -> void: if Input.is_action_just_pressed("run") and not is_running: is_running = true elif Input.is_action_just_pressed("run") and is_running: is_running = false if velocity == Vector3.ZERO: is_running = false func set_movement_speed() -> void: if is_crouching or ray_cast_crouch.is_colliding(): current_speed = crouch_speed elif is_running: current_speed = run_speed else: current_speed = walk_speed