class_name PlayerInput extends Node enum Device { Unknown, KeyboardMouse, Gamepad } class GameAction: static var Run = 'run' static var Interact = 'interact' static var Ready = 'ready' static var Fire = 'fire' class UIAction: static var Accept = 'ui_accept' static var Select = 'ui_select' static var Cancel = 'ui_cancel' static var FocusNext = 'ui_focus_next' static var FocusPrev = 'ui_focus_prev' static var Left = 'ui_left' static var Right = 'ui_right' static var Up = 'ui_up' static var Down = 'ui_down' static var PageUp = 'ui_page_up' static var PageDown = 'ui_page_down' static var Home = 'ui_home' static var End = 'ui_end' signal walk signal run signal interact signal ready_weapon signal unready_weapon signal fire @export_flags_3d_physics var ground_collision_mask: int = 0 var last_known_device: Device = Device.Unknown var _is_running: bool = false var is_running: bool: get: return _is_running set(value): if _is_running != value: _is_running = value if value: run.emit() else: walk.emit() var _is_interacting: bool = false var is_interacting: bool: get: return _is_interacting set(value): if _is_interacting != value: _is_interacting = value if value: interact.emit() var _is_weapon_ready: bool = false var is_weapon_ready: bool: get: return _is_weapon_ready set(value): if _is_weapon_ready != value: _is_weapon_ready = value if value: ready_weapon.emit() else: unready_weapon.emit() var _is_firing: bool = false var is_firing: bool: get: return _is_firing set(value): if _is_firing != value: _is_firing = value if value: fire.emit() var movement_dir: Vector2: get: return Input.get_vector( 'move_left', 'move_right', 'move_up', 'move_down' ).normalized() var _active_camera: Camera3D: get: return get_viewport().get_camera_3d() var _mouse_position: Vector2: get: return get_viewport().get_mouse_position() var _analog_dir: Vector2: get: return Input.get_vector( 'look_left', 'look_right', 'look_up', 'look_down' ).normalized() func _process(_delta: float) -> void: is_running = Input.is_action_pressed(GameAction.Run) func _get_device(event: InputEvent) -> Device: if event is InputEventMouse or event is InputEventKey: return Device.KeyboardMouse elif event is InputEventJoypadButton or event is InputEventJoypadMotion: return Device.Gamepad else: return Device.Unknown func _unhandled_input(event: InputEvent) -> void: last_known_device = _get_device(event) if event.is_action_pressed(GameAction.Interact): is_interacting = true if event.is_action_released(GameAction.Interact): is_interacting = false if event.is_action_pressed(GameAction.Ready): is_weapon_ready = true if event.is_action_released(GameAction.Ready): is_weapon_ready = false if event.is_action_pressed(GameAction.Fire): is_firing = true if event.is_action_released(GameAction.Fire): is_firing = false func next_velocity(speed: float, dir: Vector2 = movement_dir) -> Vector3: return Vector3(dir.x, 0, dir.y) * speed func get_mouse_look() -> Vector3: var cam: Camera3D = _active_camera if not cam: return Vector3.ZERO var mouse_pos = _mouse_position var ray_origin = cam.project_ray_origin(mouse_pos) var ray_direction = cam.project_ray_normal(mouse_pos) var plane_normal = Vector3.UP var plane_d = 1.5 # make this read from the weapon transform, or at least export it var denominator = plane_normal.dot(ray_direction) if abs(denominator) < 0.0001: return Vector3(ray_origin.x, 1.5, ray_origin.z) var t = (plane_d - plane_normal.dot(ray_origin)) / denominator var intersection_point = ray_origin + ray_direction * t return intersection_point func get_look_target(position: Vector3) -> Option: match last_known_device: Device.KeyboardMouse: return Option.some(get_mouse_look()) Device.Gamepad: var pos = _analog_dir if pos.is_zero_approx(): return Option.none return Option.some(Vector3(pos.x, 0, pos.y) + position) _: return Option.none