WIP: implement full support for Steam Input

This commit is contained in:
betalars 2026-06-03 14:40:21 +02:00
parent 688c56a564
commit c3167f663b
7 changed files with 365 additions and 0 deletions

View File

@ -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"
}
}
}

View File

@ -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)

View File

@ -0,0 +1 @@
uid://cgfyo6u8wygjr

View File

@ -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!!!

View File

@ -0,0 +1 @@
uid://10u2ea21g7sy

View File

@ -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

View File

@ -0,0 +1 @@
uid://ba1kym4lim7lq