extends CharacterBody3D const JUMP_SPEED: float = 5 const MOUSE_SENSITIVITY: float = 0.002 const CROUCH_MOVE_SPEED: float = 3 const BASE_MOVE_SPEED: float = 5 const SPRINT_MOVE_SPEED: float = 8 const BASE_HEIGHT: float = 2.0 const CROUCH_HEIGHT: float = 1.0 const FRICTION: float = 0.2 const AIR_FRICTION: float = 0.025 const CROUCH_TRANSITION_SPEED: float = 0.1 enum MovementMode { Crouching, Sprinting, Walking, } var current_movement := MovementMode.Walking var gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity") var move_speed: float = BASE_MOVE_SPEED var target_height: float = BASE_HEIGHT var flying := false @onready var body_collision_shape: CollisionShape3D = $BodyCollisionShape @onready var fps_camera: Camera3D = $FPSCamera @onready var head_collision_shape: CollisionShape3D \ = $HeadCollider/HeadCollisionShape @onready var head_collider: Area3D = $HeadCollider func _ready() -> void: Input.mouse_mode = Input.MOUSE_MODE_CAPTURED head_collision_shape.set_deferred("disabled", true) func _process(_delta: float) -> void: if Input.is_action_just_pressed("pause"): if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: Input.mouse_mode = Input.MOUSE_MODE_VISIBLE else: Input.mouse_mode = Input.MOUSE_MODE_CAPTURED func _physics_process(delta: float) -> void: handle_state_transition() handle_movement(delta) body_collision_shape.shape.height = lerpf(body_collision_shape.shape.height, target_height, CROUCH_TRANSITION_SPEED) move_and_slide() if (is_on_floor() or flying) and Input.is_action_just_pressed("jump"): velocity.y = JUMP_SPEED func _input(event: InputEvent) -> void: if event is InputEventMouseMotion \ and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: rotate_y(-event.relative.x * MOUSE_SENSITIVITY) fps_camera.rotate_x(-event.relative.y * MOUSE_SENSITIVITY) fps_camera.rotation.x = clampf(fps_camera.rotation.x, -deg_to_rad(70), deg_to_rad(70)) func handle_movement(delta: float) -> void: velocity.y += 0.0 if flying else -gravity * delta if Input.is_action_just_pressed("_cheat_fly"): flying = not flying var input = Input.get_vector("strafe_left", "strafe_right", "move_forward", "move_backward") var movement_dir := transform.basis * Vector3(input.x, 0, input.y) if movement_dir.x == 0: velocity.x = lerpf(velocity.x, 0.0, FRICTION if is_on_floor() else AIR_FRICTION) else: velocity.x = movement_dir.x * move_speed if movement_dir.z == 0: velocity.z = lerpf(velocity.z, 0.0, FRICTION if is_on_floor() else AIR_FRICTION) else: velocity.z = movement_dir.z * move_speed func handle_state_transition() -> void: match current_movement: MovementMode.Crouching: if not Input.is_action_pressed("crouch") \ and not head_collider.has_overlapping_bodies(): change_state(MovementMode.Walking) MovementMode.Walking: if Input.is_action_pressed("crouch"): change_state(MovementMode.Crouching) elif Input.is_action_pressed("sprint"): change_state(MovementMode.Sprinting) MovementMode.Sprinting: if Input.is_action_pressed("crouch"): change_state(MovementMode.Crouching) elif not Input.is_action_pressed("sprint"): change_state(MovementMode.Walking) func change_state(new_state: MovementMode) -> void: if new_state == current_movement: return exit_state() current_movement = new_state enter_state() func exit_state() -> void: match current_movement: MovementMode.Crouching: head_collision_shape.set_deferred("disabled", true) func enter_state() -> void: match current_movement: MovementMode.Walking: move_speed = BASE_MOVE_SPEED target_height = BASE_HEIGHT MovementMode.Crouching: head_collision_shape.set_deferred("disabled", false) move_speed = CROUCH_MOVE_SPEED target_height = CROUCH_HEIGHT MovementMode.Sprinting: move_speed = SPRINT_MOVE_SPEED target_height = BASE_HEIGHT