From c3167f663b21a38e886725ff85ad1e515b8b84b1 Mon Sep 17 00:00:00 2001 From: betalars Date: Wed, 3 Jun 2026 14:40:21 +0200 Subject: [PATCH] WIP: implement full support for Steam Input --- src/steam-input/manifest.vdf | 77 +++++ src/steam-input/steam-input-glyth-display.gd | 11 + .../steam-input-glyth-display.gd.uid | 1 + src/steam-input/steam-input-manager.gd | 264 ++++++++++++++++++ src/steam-input/steam-input-manager.gd.uid | 1 + src/steam-input/steam_action_state.gd | 10 + src/steam-input/steam_action_state.gd.uid | 1 + 7 files changed, 365 insertions(+) create mode 100644 src/steam-input/manifest.vdf create mode 100644 src/steam-input/steam-input-glyth-display.gd create mode 100644 src/steam-input/steam-input-glyth-display.gd.uid create mode 100644 src/steam-input/steam-input-manager.gd create mode 100644 src/steam-input/steam-input-manager.gd.uid create mode 100644 src/steam-input/steam_action_state.gd create mode 100644 src/steam-input/steam_action_state.gd.uid diff --git a/src/steam-input/manifest.vdf b/src/steam-input/manifest.vdf new file mode 100644 index 00000000..5d6e9ac4 --- /dev/null +++ b/src/steam-input/manifest.vdf @@ -0,0 +1,77 @@ +"In Game Actions" +{ + "actions" + { + "GameControls" + { + "title" "#Set_GameControls" + "StickPadGyro" + { + "move" + { + "title" "#Action_Move" + "input_mode" "joystick_move" + } + "look" + { + "title" "#Action_Look" + "input_mode" "absolute_mouse" + } + } + "Button" + { + "interact" "#Action_Interact" + "back" "#Action_Back" + "crouch" "#Action_Crouch" + "zoom" "#Action_Zoom" + "skip" "#Action_Skip" + "pause" "#Action_Pause" + "summarize" "#Action_Summarize" + } + } + "MenuControls" + { + "title" "#Set_MenuControls" + "Button" + { + "menu_up" "#Menu_Up" + "menu_down" "#Menu_Down" + "menu_left" "#Menu_Left" + "menu_right" "#Menu_Right" + "accept" "#Menu_Accept" + "cancel" "#Menu_Cancel" + "focus_next" "#Menu_FocusNext" + "focus_previous" "#Menu_FocusPrevious" + "scroll_up" "#Menu_ScrollUp" + "scroll_down" "#Menu_ScrollDown" + } + } + } + "localization" + { + "english" + { + "Set_GameControls" "Game Controls" + "Set_MenuControls" "Menu Controls" + "Action_Move" "Move" + "Action_Look" "Look" + "Action_Interact" "Pick up" + "Action_Back" "Go Back" + "Action_Crouch" "Crouch" + "Action_Zoom" "Zoom" + "Action_Skip" "Skip" + "Action_Pause" "Pause Game" + "Action_Summarize" "Summarize Content" + "Menu_Up" "Menu Up" + "Menu_Down" "Menu Down" + "Menu_Left" "Menu Left" + "Menu_Right" "Menu Right" + "Menu_Accept" "Accept" + "Menu_Canel" "Cancel" + "Menu_FocusNext" "Focus Next" + "Menu_FocusPrevious" "Focus Previous" + "Menu_ScrollUp" "Scroll Up" + "Menu_ScrollDown" "Scroll Down" + } + } +} diff --git a/src/steam-input/steam-input-glyth-display.gd b/src/steam-input/steam-input-glyth-display.gd new file mode 100644 index 00000000..14a0713c --- /dev/null +++ b/src/steam-input/steam-input-glyth-display.gd @@ -0,0 +1,11 @@ +class_name SteamInputGlypthDisplay extends TextureRect + +@export var action_name: StringName + +func _ready() -> void: + SteamInputManager.glyphs_loaded.connect(_update_glyph) + texture = PlaceholderTexture2D.new() + texture.size = Vector2(64, 64) + +func _update_glyph() -> void: + texture = SteamInputManager.get_glyph(action_name) diff --git a/src/steam-input/steam-input-glyth-display.gd.uid b/src/steam-input/steam-input-glyth-display.gd.uid new file mode 100644 index 00000000..caeeae9a --- /dev/null +++ b/src/steam-input/steam-input-glyth-display.gd.uid @@ -0,0 +1 @@ +uid://cgfyo6u8wygjr diff --git a/src/steam-input/steam-input-manager.gd b/src/steam-input/steam-input-manager.gd new file mode 100644 index 00000000..500eca2d --- /dev/null +++ b/src/steam-input/steam-input-manager.gd @@ -0,0 +1,264 @@ +extends Node + +signal glyphs_loaded + +# True means analog action! +const game_control_actios := { + "move": true, + "look": true, + "interact": false, + "back": false, + "crouch": false, + "zoom": false, + "skip": false, + "pause": false, + "summarize": false +} + +const menu_control_actions := { + "menu_up": false, + "menu_down": false, + "menu_left": false, + "menu_right": false, + "accept": false, + "cancel": false, + "focus_next": false, + "focus_previous": false, + "scroll_up": false, + "scroll_down": false, +} + +var action_set_ids: Dictionary[StringName, int] = {"game_control_actios": -1, "menu_control_actions": -1} + +var controller_id := -1 +var got_handles := false +var actions := {} +var analog_actions: PackedStringArray = [] +var action_states: Dictionary[StringName, SteamActionState] = {} + +var current_action_set: int: + set(value): + if Steam.isSteamRunning(): + Steam.activateActionSet(controller_id, value) + current_action_set = value + +var action_glyphs: Dictionary[StringName, Texture2D]= {} +var cooldown: float = 0.0 +var gdt_cooldown: float = 0.0 + + +func init() -> void: + process_mode = Node.PROCESS_MODE_ALWAYS + Steam.input_device_connected.connect(_on_controller_connect) + Steam.input_device_disconnected.connect(_on_controller_disconnect) + Steam.runFrame() + State.game_context_updated.connect(_on_set_change) + +func _process(delta: float) -> void: + Steam.runFrame() + if cooldown > 0: + cooldown -= delta + if gdt_cooldown > 0: + gdt_cooldown -= delta + + if controller_id != -1 and State.last_input_source == State.InputSource.KEYBOARD_MOUSE: + for action in actions.keys(): + if Steam.getDigitalActionData(controller_id, actions[action]).state: + State.last_input_source = State.InputSource.JOYPAD + break + +func kill(): + State.game_context_updated.disconnect(_on_set_change) + + +func si_to_godot(event_action: StringName): + if State.last_input_source == State.InputSource.JOYPAD and gdt_cooldown <= 0: + gdt_cooldown = 0.01 + var event := InputEventAction.new() + event.action = event_action + event.pressed = true + Input.parse_input_event(event) + + var event_release := InputEventAction.new() + event_release.action = event_action + event_release.pressed = false + Input.parse_input_event(event_release) + + +func get_handles() -> void: + if controller_id != -1: + for key in action_set_ids.keys(): + action_set_ids[key] = Steam.getActionSetHandle(key) + action_set_ids[key] = Steam.getActionSetHandle(key) + get_action_handles() + got_handles = true + + +func get_action_handles() -> void: + for action_set_key in action_set_ids.keys(): + for action in get(action_set_key).keys(): + if game_control_actios[action]: + actions[action] = Steam.getAnalogActionHandle(action) + analog_actions.append(action) + else: + actions[action] = Steam.getDigitalActionHandle(action) + +func get_action_strength(action: StringName, device: int = controller_id, exact_match: bool = false) -> float: + if device != -1: + if got_handles: + return Steam.getAnalogActionData(device, actions[action]).x + else: + return 0 + return Input.get_action_strength(action, exact_match) + + +func get_axis(base_name: StringName, device: int = controller_id) -> Vector2: + if device != -1: + if not got_handles: return Vector2.ZERO + + var action_data = Steam.getAnalogActionData(device, actions[base_name]) + return Vector2(action_data.x, -action_data.y).normalized() + return Vector2(Input.get_axis("%s_left" % base_name, "%s_right"), Input.get_axis("%s_positive", "%s_negative")).normalized() + + +func is_action_pressed(action: StringName, device: int = controller_id, exact_match: bool = false) -> bool: + if device != -1: + if not got_handles: return false + + var current_frame = Engine.get_process_frames() + if State.last_input_source == State.InputSource.JOYPAD: + var currently_held = Steam.getDigitalActionData(device, actions[action]).state + set_action_state(action, currently_held, current_frame) + return currently_held + + return Input.is_action_pressed(action, exact_match) + +func is_action_just_pressed(action: StringName, device: int = controller_id, exact_match: bool = false) -> bool: + if device >= 0: + if not got_handles: + return false + + if cooldown <= 0: + var current_frame = Engine.get_process_frames() + if State.last_input_source == State.InputSource.JOYPAD: + var currently_held = Steam.getDigitalActionData(device, actions[action]).state + var action_state = set_action_state(action, currently_held, current_frame) + if currently_held: + cooldown = 0.1 + return currently_held and action_state.press_frame == current_frame + + else: + return false + + return Input.is_action_just_pressed(action, exact_match) + +func is_action_just_released(action: StringName, device: int = controller_id, exact_match: bool = false) -> bool: + if device >= 0: + if not got_handles: return false + + var current_frame = Engine.get_process_frames() + var currently_held = Steam.getDigitalActionData(device, actions[action]).state + var action_state = set_action_state(action, currently_held, current_frame) + return not currently_held and action_state.release_frame == current_frame + + return Input.is_action_just_released(action, exact_match) + +func get_action_state(action: String) -> SteamActionState: + if not action_states.has(action): + action_states[action] = SteamActionState.new() + return action_states[action] + + +func set_action_state(action: StringName, currently_held: bool, current_frame: int) -> SteamActionState: + var previous_action_state = get_action_state(action) + + if currently_held and not previous_action_state.held: + action_states[action].pressed = true + action_states[action].frame_pressed = current_frame + + elif not currently_held and previous_action_state.pressed: + action_states[action].pressed = false + action_states[action].frame_released = current_frame + + return action_states[action] + + + +func _on_controller_connect(input_handle) -> void: + controller_id = input_handle + get_handles() + # a timer for a delayed reload of glyphs/icons as sometimes, Steam loads the wrong ones + + call_deferred("_delayed_reload", [10]) + + Steam.run_callbacks() + Steam.runFrame() + + _preload_glyphs() + set_glyphs() + + +func _on_controller_disconnect(_input_handle) -> void: + actions.clear() + action_states.clear() + action_glyphs.clear() + got_handles = false + # to pause the game if controller is disconnected while in game + get_node("/root/Game").on_controller_disconnect() + controller_id = -1 + set_glyphs() + State.last_input_source = State.InputSource.KEYBOARD_MOUSE + + +func _on_set_change(new_context: State.GameContext) -> void: + current_action_set = action_set_ids["game_control_actios"] if new_context == State.GameContext.WALK else action_set_ids["menu_control_actions"] + + +func _preload_glyphs() -> void: + var action_sets := action_set_ids.values() + + for set_id in action_sets: + Steam.activateActionSet(controller_id, set_id) + Steam.runFrame() + + for action_name in actions.keys(): + var origins := [] + + if analog_actions.has(action_name): + origins = Steam.getAnalogActionOrigins(controller_id, set_id, actions[action_name]) + else: + origins = Steam.getDigitalActionOrigins(controller_id, set_id, actions[action_name]) + + if origins.size() == 0: + continue + + var style := Steam.INPUT_GLYPH_STYLE_DARK | Steam.INPUT_GLYPH_STYLE_SOLID_ABXY + var path = Steam.getGlyphPNGForActionOrigin(origins[0], Steam.INPUT_GLYPH_SIZE_LARGE, style) + var img = Image.new() + if img.load(path) == OK: + var tex :Texture2D = ImageTexture.create_from_image(img) + action_glyphs[action_name] = tex + glyphs_loaded.emit() + + +func get_glyph(action_name: StringName) -> Texture2D: + if action_glyphs.has(action_name): + return action_glyphs[action_name] + else: + var placeholder = PlaceholderTexture2D.new() + placeholder.size = Vector2(64, 64) + return placeholder + + +func _delayed_reload(delay: float): + await get_tree().create_timer(delay).timeout + var current_set: int = current_action_set + _preload_glyphs() + set_glyphs() + current_action_set = current_set + Steam.runFrame() + +func set_glyphs(): + pass + +##TODO: update controller state in state!!! diff --git a/src/steam-input/steam-input-manager.gd.uid b/src/steam-input/steam-input-manager.gd.uid new file mode 100644 index 00000000..8504cafc --- /dev/null +++ b/src/steam-input/steam-input-manager.gd.uid @@ -0,0 +1 @@ +uid://10u2ea21g7sy diff --git a/src/steam-input/steam_action_state.gd b/src/steam-input/steam_action_state.gd new file mode 100644 index 00000000..5caa3549 --- /dev/null +++ b/src/steam-input/steam_action_state.gd @@ -0,0 +1,10 @@ +class_name SteamActionState extends Object + +var pressed: bool +var frame_pressed: int +var frame_released: int + +func _init(is_pressed: bool = false, first_frame_pressed: int = -1, first_frame_released: int = -1): + self.pressed = is_pressed + self.frame_pressed = first_frame_pressed + self.frame_released = first_frame_released diff --git a/src/steam-input/steam_action_state.gd.uid b/src/steam-input/steam_action_state.gd.uid new file mode 100644 index 00000000..253688e9 --- /dev/null +++ b/src/steam-input/steam_action_state.gd.uid @@ -0,0 +1 @@ +uid://ba1kym4lim7lq