class_name PlayerController extends CharacterBody3D ## First-person player controller. ## WASD movement, mouse-look, gravity, and an interaction raycast. @export var walk_speed: float = 4.0 @export var mouse_sensitivity: float = 0.0025 @export var pitch_limit_deg: float = 89.0 ## When true, all movement and look input is ignored (e.g. while seated). var movement_locked: bool = false @onready var camera: Camera3D = $Camera3D @onready var interaction_ray: RayCast3D = $Camera3D/InteractionRay var _gravity: float = ProjectSettings.get_setting("physics/3d/default_gravity", 9.8) func _ready() -> void: Input.mouse_mode = Input.MOUSE_MODE_CAPTURED func _unhandled_input(event: InputEvent) -> void: if movement_locked: return # Mouse look. if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED: rotate_y(-event.relative.x * mouse_sensitivity) camera.rotate_x(-event.relative.y * mouse_sensitivity) var limit := deg_to_rad(pitch_limit_deg) camera.rotation.x = clampf(camera.rotation.x, -limit, limit) # Release / recapture mouse with Escape (handy for alt-tab / debugging). if event.is_action_pressed("ui_cancel"): 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: # Gravity. if not is_on_floor(): velocity.y -= _gravity * delta if movement_locked: velocity.x = 0.0 velocity.z = 0.0 move_and_slide() return # Horizontal movement from input. var input_dir := Input.get_vector( "move_left", "move_right", "move_forward", "move_back" ) var direction := (transform.basis * Vector3(input_dir.x, 0.0, input_dir.y)).normalized() if direction != Vector3.ZERO: velocity.x = direction.x * walk_speed velocity.z = direction.z * walk_speed else: velocity.x = move_toward(velocity.x, 0.0, walk_speed) velocity.z = move_toward(velocity.z, 0.0, walk_speed) move_and_slide() ## Returns the object currently under the interaction ray, or null. func get_interaction_target() -> Object: if interaction_ray and interaction_ray.is_colliding(): return interaction_ray.get_collider() return null ## Lock or unlock player movement/look (used by sit-down and minigames). func set_movement_locked(locked: bool) -> void: movement_locked = locked