Ir al contenido

Wired

ss-gameforge-wired es un sistema completo de gestión de input sobre el manejo de input nativo de Godot. Añade aislamiento de input por jugador, rebinding en tiempo de ejecución con persistencia, detección automática de dispositivos y soporte “press to join” para co-op local.

  1. Descarga ss-gameforge-wired desde la página de releases.
  2. Copia addons/ss-gameforge-wired/ dentro de res://addons/.
  3. Activa ss-gameforge-singleton y ss-gameforge-wired en Project Settings → Plugins.

Al activar el plugin aparece una pestaña GodotWired en la pantalla principal del editor de Godot (junto a 2D/3D/Script).

Desde ahí puedes:

  • Configurar el modo de jugador (SINGLE, LOCAL_COOP, ONLINE) y el máximo de jugadores locales.
  • Añadir, renombrar y eliminar acciones.
  • Asignar a cada acción su nombre de display, descripción, categoría, tipo (Button o Axis) y si permite rebinding.
  • Guardar la configuración como un recurso GWConfig (ruta por defecto: res://gw_config.tres).

Los bindings por defecto se añaden desde código con define_action(), o directamente sobre el recurso GWConfig en el inspector.

ClaseDescripción
GWInputManagerCoordinador central — punto de entrada singleton para todas las operaciones de input.
GWConfigRecurso de configuración del proyecto (modo de jugador, máx. jugadores, lista de acciones).
GWActionConfigPlantilla para definir una acción (nombre, bindings, categoría, tipo).
GWPlayerJugador lógico con sus propios dispositivos y copias independientes de todas las acciones.
GWActionInstancia de acción en tiempo de ejecución por jugador (rebindeable independientemente).
GWBindingUn mapeo de input físico individual (tecla, botón, eje, botón de ratón).
GWDeviceAbstracción sobre un dispositivo físico (teclado/ratón o gamepad).
GWProfileManagerUtilidad estática para guardar y cargar perfiles de binding con nombre.

Crea via el panel de editor, o con New Resource → GWConfig en el inspector.

enum PlayerMode {
SINGLE, # Un jugador — teclado + cualquier gamepad
LOCAL_COOP, # N jugadores — cada uno recibe un dispositivo exclusivo
ONLINE, # Un jugador local, el resto se gestiona por la capa de red
}
@export var player_mode: PlayerMode = SINGLE
@export_range(2, 8) var max_local_players: int = 2
@export var actions: Array[GWActionConfig] = []

Carga la configuración al iniciar:

var config := preload("res://gw_config.tres") as GWConfig
GWInputManager.i.load_config(config)
CampoTipoDescripción
action_nameStringIdentificador interno usado en el código de gameplay (ej. "saltar").
display_nameStringEtiqueta mostrada en la UI de rebinding (ej. "Saltar").
descriptionStringTooltip opcional en la pantalla de rebinding.
categoryStringAgrupa acciones en la UI (ej. "Movimiento", "Combate").
max_bindingsintMáximo de slots de binding por jugador. 0 = ilimitado.
action_typeActionTypeBUTTON o AXIS.
positive_nameStringSolo Axis — etiqueta para la dirección +1.0 (ej. "derecha").
negative_nameStringSolo Axis — etiqueta para la dirección -1.0 (ej. "izquierda").
allow_rebindboolSi es false, la acción no aparece como editable en la UI de rebinding.
default_bindingsArray[GWBinding]Bindings clonados en cada jugador al llamar a load_config().

Usa los métodos factory estáticos para crear bindings:

GWBinding.from_key(KEY_SPACE)
GWBinding.from_key(KEY_W)
GWBinding.from_joy_button(JOY_BUTTON_A)
GWBinding.from_joy_axis(JOY_AXIS_LEFT_Y, -1.0) # Analógico arriba
GWBinding.from_mouse_button(MOUSE_BUTTON_LEFT)

Usa get_display_name() para obtener una etiqueta legible de cualquier binding:

var b: GWBinding = action.bindings[0]
label.text = b.get_display_name() # "Space", "A", "LX +", "Mouse L", etc.
func _enter_tree() -> void:
GWInputManager.ensure(get_tree().root)
var p1 := GWInputManager.i.create_player(0, "Jugador 1")
var p2 := GWInputManager.i.create_player(1, "Jugador 2")
GWInputManager.i.get_player(player_id) -> GWPlayer
GWInputManager.i.get_default_player() -> GWPlayer # atajo de get_player(0)
GWInputManager.i.remove_player(player_id)

Seguro llamarlo desde _process() o _physics_process() — no requiere InputEvent:

GWInputManager.i.is_action_pressed("saltar", player_id)
GWInputManager.i.is_action_just_pressed("saltar", null, player_id) # por frame
GWInputManager.i.is_action_just_released("saltar", null, player_id) # por frame
GWInputManager.i.get_action_strength("mover_derecha", player_id)
GWInputManager.i.get_action_axis("mover_horizontal", player_id)
GWInputManager.i.get_vector("mover_izq", "mover_der", "mover_arr", "mover_abj", player_id)

Para consultas basadas en evento (desde _input() o _unhandled_input()), pasa el evento en lugar de null:

func _input(event: InputEvent) -> void:
if GWInputManager.i.is_action_just_pressed("saltar", event):
velocity.y = -400.0
GWInputManager.i.define_action(
"saltar", # action_name
"Saltar", # display_name
[GWBinding.from_key(KEY_SPACE), GWBinding.from_joy_button(JOY_BUTTON_A)],
"Movimiento", # category
2 # max_bindings
)
# Inicia escucha del siguiente input del jugador
GWInputManager.i.start_rebind_listen(player_id, "saltar", binding_index)
# Cancela una escucha en curso
GWInputManager.i.cancel_rebind_listen()
# Verifica si está escuchando
GWInputManager.i.is_listening_for_rebind() -> bool
GWInputManager.i.save_all_profiles("user://input_profiles.json")
GWInputManager.i.load_all_profiles("user://input_profiles.json")
GWInputManager.i.reset_all_to_defaults()
GWInputManager.i.get_connected_devices() -> Array # todos los dispositivos incluyendo teclado/ratón
GWInputManager.i.get_unassigned_gamepads() -> Array # gamepads sin jugador asignado
signal player_created(player_id: int)
signal player_removed(player_id: int)
signal device_connected(device_id: int, device_name: String)
signal device_disconnected(device_id: int)
signal binding_remapped(player_id: int, action_name: String)
signal rebind_listening(player_id: int, action_name: String, binding_index: int)
signal rebind_cancelled(player_id: int)
signal unassigned_device_pressed(device_id: int) # Soporte "press to join"
# Asignación de dispositivos
p1.assign_gamepad(device_id)
p1.unassign_gamepad(device_id)
p1.unassign_all_gamepads()
p1.accepts_keyboard = true # permitir teclado/ratón para este jugador
p1.has_any_device() -> bool
# Consultas de input por polling (sin InputEvent)
p1.is_action_pressed("saltar")
p1.get_action_strength("mover_derecha")
p1.get_action_axis("mover_horizontal") # devuelve -1.0 a 1.0
p1.get_vector("mover_izq", "mover_der", "mover_arr", "mover_abj")
# Consultas basadas en evento (desde _input / _unhandled_input)
p1.is_action_just_pressed("saltar", event)
p1.is_action_just_released("saltar", event)
# Rebinding
p1.set_binding("saltar", index, binding)
p1.clear_action_bindings("saltar")
p1.find_conflicts(event, exclude_action) # devuelve Array[GWAction]
signal device_assigned(device_id: int)
signal device_unassigned(device_id: int)
signal action_rebound(action_name: String)

Utilidad estática para guardar y cargar perfiles de binding con nombre. Cada perfil se guarda como un archivo separado: gw_profile_{nombre}.json.

GWProfileManager.save(GWInputManager.i, "perfil_jugador1")
GWProfileManager.load_profile(GWInputManager.i, "perfil_jugador1")
GWProfileManager.profile_exists("perfil_jugador1") -> bool
GWProfileManager.list_profiles() -> Array[String]
GWProfileManager.delete_profile("perfil_jugador1")
extends Node
func _enter_tree() -> void:
GWInputManager.ensure(get_tree().root)
func _ready() -> void:
GWInputManager.i.define_action(
"saltar", "Saltar",
[GWBinding.from_key(KEY_SPACE), GWBinding.from_joy_button(JOY_BUTTON_A)],
"Movimiento", 2
)
GWInputManager.i.define_action(
"mover_derecha", "Mover Derecha",
[GWBinding.from_key(KEY_D), GWBinding.from_joy_axis(JOY_AXIS_LEFT_X, 1.0)],
"Movimiento", 2
)
GWInputManager.i.define_action(
"mover_izquierda", "Mover Izquierda",
[GWBinding.from_key(KEY_A), GWBinding.from_joy_axis(JOY_AXIS_LEFT_X, -1.0)],
"Movimiento", 2
)
var p1 := GWInputManager.i.create_player(0)
p1.accepts_keyboard = true
func _physics_process(_delta: float) -> void:
if GWInputManager.i.is_action_just_pressed("saltar"):
velocity.y = -400.0
velocity.x = GWInputManager.i.get_action_axis("mover_horizontal") * 200.0
func _ready() -> void:
var config := preload("res://gw_config.tres") as GWConfig
GWInputManager.i.load_config(config) # player_mode = LOCAL_COOP, max = 4
GWInputManager.i.unassigned_device_pressed.connect(_on_press_to_join)
var _player_count := 0
func _on_press_to_join(device_id: int) -> void:
var p := GWInputManager.i.create_player(_player_count)
p.assign_gamepad(device_id)
_player_count += 1
print("Jugador %d se unió con dispositivo %d" % [_player_count, device_id])

Usa las señales de GWInputManager para construir tu propia pantalla de rebinding:

# Mostrar la etiqueta del binding actual
func refresh_binding_label(player_id: int, action_name: String) -> void:
var p := GWInputManager.i.get_player(player_id)
var action: GWAction = p.actions[action_name]
if action.bindings.is_empty():
label.text = "Sin asignar"
else:
label.text = action.bindings[0].get_display_name()
# Iniciar escucha
func _on_rebind_button_pressed() -> void:
GWInputManager.i.start_rebind_listen(0, "saltar", 0)
# Reaccionar al completarse
func _ready() -> void:
GWInputManager.i.binding_remapped.connect(
func(pid, action): refresh_binding_label(pid, action)
)