From 218ef9fc194928697f7686ebdb6dfdd27d443322 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sat, 3 May 2025 05:23:17 +0200 Subject: [PATCH] Add Steam controller input manager and integrate with Steam API --- autoloads/steam_controller_input.gd | 169 ++++++++++++++++++++++++++++ objects/audio_controller.tscn | 1 - project.godot | 12 ++ scenes/test.tscn | 1 + scripts/steam_integration.gd | 29 ++++- 5 files changed, 207 insertions(+), 5 deletions(-) create mode 100644 autoloads/steam_controller_input.gd diff --git a/autoloads/steam_controller_input.gd b/autoloads/steam_controller_input.gd new file mode 100644 index 0000000..a3b5fdf --- /dev/null +++ b/autoloads/steam_controller_input.gd @@ -0,0 +1,169 @@ +extends Node + +## A globally accessible manager for device-specific actions using SteamInput for any controller +## and standard Godot Input for the keyboard. +## +## All methods in this class that have a "device" parameter can accept -1 +## which means the keyboard device. + +# The actions defined in the Steam .vdf file are listed here +# with true or false indicating if input is analog or digital. +# False is digital (buttons), true is analog (joysticks, triggers, etc). + +var action_names := { + "move": true, + "jump": false, + "left": false, + "right": false, + "down": false, + "up": false, + "interact": false, + "pause": false, + } + +var got_handles := false +var game_action_set +var current_action_set +var actions := {} +# Store the state of each action and the frame it entered that state. +var action_states := {} + + +func init() -> void: + Steam.input_device_connected.connect(_on_steam_input_device_connected) + Steam.input_device_disconnected.connect(_on_steam_input_device_disconnected) + + +func _on_steam_input_device_connected(input_handle: int) -> void: + if not got_handles: + get_handles() + + Steam.activateActionSet(input_handle, current_action_set) + print("Device connected %s" % str(input_handle)) + + +func _on_steam_input_device_disconnected(input_handle: int) -> void: + print("Device disconnected %s" % str(input_handle)) + + +func get_handles() -> void: + got_handles = true + game_action_set = Steam.getActionSetHandle("GameControls") + current_action_set = game_action_set + get_action_handles(action_names) + + +func get_action_handles(action_names: Dictionary) -> void: + for action in action_names.keys(): + if action_names[action]: + actions[action] = Steam.getAnalogActionHandle(action) + else: + actions[action] = Steam.getDigitalActionHandle(action) + + +func get_controllers() -> Array[int]: + var controllers: Array[int] = [-1] + var steam_controllers = Steam.getConnectedControllers() + if steam_controllers: + controllers.append_array(steam_controllers) + + return controllers + + +func get_action_strength(device: int, action: StringName, exact_match: bool = false) -> float: + if device >= 0: + if not got_handles: return 0 + var action_data = Steam.getAnalogActionData(device, actions[action]) + return action_data.x + return Input.get_action_strength(action, exact_match) + + +func get_axis(device: int, negative_action: StringName, positive_action: StringName) -> float: + if device >= 0: + if not got_handles: return 0 + var negative = Steam.getAnalogActionData(device, actions[negative_action]) + var positive = Steam.getAnalogActionData(device, actions[positive_action]) + return positive.x - negative.x + return Input.get_axis(negative_action, positive_action) + + +func get_vector(device: int, negative_x: StringName, positive_x: StringName, negative_y: StringName, positive_y: StringName, deadzone: float = -1.0) -> Vector2: + if device >= 0: + if not got_handles: return Vector2.ZERO + var negative_x_val = Steam.getAnalogActionData(device, actions[negative_x]) + var positive_x_val = Steam.getAnalogActionData(device, actions[positive_x]) + var negative_y_val = Steam.getAnalogActionData(device, actions[negative_y]) + var positive_y_val = Steam.getAnalogActionData(device, actions[positive_y]) + # Steam's y axis is inverted compared to Godot + return Vector2(positive_x_val - negative_x_val, -(positive_y_val - negative_y_val)).normalized() + return Input.get_vector(negative_x, positive_x, negative_y, positive_y, deadzone) + + +func get_move_input(device: int) -> Vector2: + if device >= 0: + if not got_handles: return Vector2.ZERO + # Get the analog stick movement + var action_data = Steam.getAnalogActionData(device, actions["Move"]) + return Vector2(action_data.x, -action_data.y).normalized() + return Vector2(Input.get_axis("left", "right"), Input.get_axis("up", "down")).normalized() + + +func get_action_state(device: int, action: String) -> Dictionary: + # Get the current action, but create the defaults along the way if they don't exist. + if not action_states.get(device): + action_states[device] = {} + if not action_states[device].get(action): + action_states[device][action] = { "held": false, "press_frame": -1, "release_frame": -1 } + return action_states[device][action] + + +func set_action_state(device: int, action: StringName, currently_held: bool, current_frame: int) -> Dictionary: + # Get the state of the action last frame + var previous_action_state := get_action_state(device, action) + + # If we're pressing the action now and we weren't pressing it last frame, + # track that we pressed the action this frame. + if currently_held and not previous_action_state.held: + action_states[device][action].held = true + action_states[device][action].press_frame = current_frame + # If we're not pressing it this frame but we were pressing it last frame, + # track that we released the action this frame. + elif not currently_held and previous_action_state.held: + action_states[device][action].held = false + action_states[device][action].release_frame = current_frame + + # Return the current state of the action + return action_states[device][action] + + +func is_action_pressed(device: int, action: StringName, 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 + set_action_state(device, action, currently_held, current_frame) + return currently_held + # If keyboard, use normal Godot input system. + return Input.is_action_pressed(action, exact_match) + + +func is_action_just_pressed(device: int, action: StringName, 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(device, action, currently_held, current_frame) + return currently_held and action_state.press_frame == current_frame + # If keyboard, use normal Godot input system. + return Input.is_action_just_pressed(action, exact_match) + + +func is_action_just_released(device: int, action: StringName, 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(device, action, currently_held, current_frame) + return not currently_held and action_state.release_frame == current_frame + # If keyboard, use normal Godot input system. + return Input.is_action_just_released(action, exact_match) \ No newline at end of file diff --git a/objects/audio_controller.tscn b/objects/audio_controller.tscn index 4bd0089..956df7f 100644 --- a/objects/audio_controller.tscn +++ b/objects/audio_controller.tscn @@ -6,5 +6,4 @@ [node name="Background Music" type="AudioStreamPlayer" parent="."] stream = ExtResource("1_fuc3i") -autoplay = true parameters/looping = true diff --git a/project.godot b/project.godot index 6b63678..e8a7092 100644 --- a/project.godot +++ b/project.godot @@ -30,6 +30,12 @@ GameManager="*res://objects/game_manager.tscn" PhantomCameraManager="*res://addons/phantom_camera/scripts/managers/phantom_camera_manager.gd" GUIDE="*res://addons/guide/guide.gd" AudioController="*res://objects/audio_controller.tscn" +SteamIntegrationNode="*res://objects/steam_integration.tscn" +SteamControllerInput="*res://autoloads/steam_controller_input.gd" + +[debug] + +file_logging/enable_file_logging=true [display] @@ -77,6 +83,11 @@ jump={ "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) ] } +up={ +"deadzone": 0.2, +"events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":87,"key_label":0,"unicode":119,"location":0,"echo":false,"script":null) +] +} down={ "deadzone": 0.5, "events": [Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":-1,"window_id":0,"alt_pressed":false,"shift_pressed":false,"ctrl_pressed":false,"meta_pressed":false,"pressed":false,"keycode":0,"physical_keycode":83,"key_label":0,"unicode":115,"location":0,"echo":false,"script":null) @@ -111,6 +122,7 @@ show_marketplace={ [physics] 2d/physics_engine="GodotPhysics2D" +common/physics_interpolation=true [rendering] diff --git a/scenes/test.tscn b/scenes/test.tscn index dc0eef9..6cb95ce 100644 --- a/scenes/test.tscn +++ b/scenes/test.tscn @@ -534,6 +534,7 @@ metadata/_edit_group_ = true [node name="Camera2D" type="Camera2D" parent="."] physics_interpolation_mode = 1 position = Vector2(903, -118) +process_callback = 0 limit_left = 0 limit_top = -896 limit_right = 4128 diff --git a/scripts/steam_integration.gd b/scripts/steam_integration.gd index 976da2e..1194220 100644 --- a/scripts/steam_integration.gd +++ b/scripts/steam_integration.gd @@ -1,13 +1,34 @@ class_name SteamIntegration extends Node -var app_id: String = "3575090" +var app_id: String = "3575090" +var is_on_steam_deck: bool = false +var is_online: bool = false +var has_bought_game: bool = false func _init() -> void: - OS.set_environment("STEAM_APP_ID", app_id) - OS.set_environment("STEAM_GAME_ID", app_id) + OS.set_environment("SteamAppId", app_id) + OS.set_environment("SteamGameId", app_id) func _ready() -> void: - pass \ No newline at end of file + Steam.steamInit() + Steam.enableDeviceCallbacks() + SteamControllerInput.init() + var is_running := Steam.isSteamRunning() + + if !is_running: + print("Steam is not running.") + return + + print("Steam is running.") + + is_on_steam_deck = Steam.isSteamRunningOnSteamDeck() + is_online = Steam.loggedOn() + has_bought_game = Steam.isSubscribed() + + if not has_bought_game: + print("You have not bought the game.") + get_tree().quit(69) + \ No newline at end of file