From 1ccd26bbabbca86b7133ecb2e1cf4149ab40c5f1 Mon Sep 17 00:00:00 2001 From: Shawn Date: Fri, 3 Jul 2026 17:48:33 -0400 Subject: [PATCH] Fix keyboard input not working on Windows client Added explicit window focus grab after connecting (lan-bootstrapper.gd) so keyboard input is properly received by the input system. Also added diagnostic debug prints to trace input pipeline. --- .../multiplayer-fps/scripts/player-input.gd | 6 +++++- examples/multiplayer-fps/scripts/player.gd | 13 ++++++++++-- .../multiplayer-fps/scripts/weapon_manager.gd | 9 +++++++++ examples/shared/scripts/lan-bootstrapper.gd | 13 ++++++++++-- tests/check_actions.gd.uid | 1 + tests/check_input_map.gd.uid | 1 + tests/check_models.gd | 20 ------------------- 7 files changed, 38 insertions(+), 25 deletions(-) create mode 100644 tests/check_actions.gd.uid create mode 100644 tests/check_input_map.gd.uid delete mode 100644 tests/check_models.gd diff --git a/examples/multiplayer-fps/scripts/player-input.gd b/examples/multiplayer-fps/scripts/player-input.gd index 0cc1cb8..636d4ea 100644 --- a/examples/multiplayer-fps/scripts/player-input.gd +++ b/examples/multiplayer-fps/scripts/player-input.gd @@ -75,12 +75,16 @@ func _input(event: InputEvent) -> void: func _gather(): if !is_setup: + print("[INPUT DEBUG] _gather first call - setting up") setup() - + # Movement (continuous) var mx = Input.get_axis("move_west", "move_east") var mz = Input.get_axis("move_north", "move_south") + var old_mv = movement movement = Vector3(mx, 0, mz) + if movement != old_mv and (mx != 0 or mz != 0): + print("[INPUT DEBUG] _gather movement=%s (mx=%.2f mz=%.2f)" % [movement, mx, mz]) jump = Input.is_action_pressed("move_jump") diff --git a/examples/multiplayer-fps/scripts/player.gd b/examples/multiplayer-fps/scripts/player.gd index 41dfb92..f4518b6 100644 --- a/examples/multiplayer-fps/scripts/player.gd +++ b/examples/multiplayer-fps/scripts/player.gd @@ -123,21 +123,30 @@ func _after_tick_loop(): func _process(_delta: float): if not _is_local: if input.is_multiplayer_authority(): + print("[PLAYER DEBUG] First _process — calling _setup_local_camera (authority: %d)" % multiplayer.get_unique_id()) _setup_local_camera() else: + print("[PLAYER DEBUG] _process skipping — input not authority (unique_id=%d, input_auth=%d)" % [multiplayer.get_unique_id(), input.get_multiplayer_authority()]) return - + # Smooth position interpolation between ticks var f := NetworkTime.tick_factor global_position = _lerp_from_pos.lerp(_lerp_to_pos, f) - + # Frame-rate mouse look (recoil only affects raycast, not camera) + var old_yaw = _sim_yaw rotation.y = _sim_yaw + (-input.mouse_rotation.y) + if _sim_yaw != old_yaw or input.mouse_rotation.length_squared() > 0: + pass # mouse look active head.rotation.x = clamp(_sim_pitch + (-input.mouse_rotation.x), -1.57, 1.57) head.rotation.y = 0 head.rotation.z = 0 func _rollback_tick(delta: float, tick: int, is_fresh: bool) -> void: + # DEBUG movement state + if input and input.movement.length_squared() > 0: + print("[PLAYER RT] tick=%d movement=%s hp=%d death_tick=%d frozen=%s" % [tick, input.movement, health, death_tick, round_manager and round_manager.freeze_end_tick >= 0 and tick < round_manager.freeze_end_tick]) + # Handle round respawn teleport inside rollback if respawn_tick >= 0 and tick == respawn_tick: global_position = respawn_position diff --git a/examples/multiplayer-fps/scripts/weapon_manager.gd b/examples/multiplayer-fps/scripts/weapon_manager.gd index b3aaa41..ea7c529 100644 --- a/examples/multiplayer-fps/scripts/weapon_manager.gd +++ b/examples/multiplayer-fps/scripts/weapon_manager.gd @@ -161,6 +161,10 @@ func _rollback_tick(delta: float, tick: int, _is_fresh: bool): if rollback_synchronizer.is_predicting(): return + # DEBUG: weapon switching + if input and (input.slot_1 or input.slot_2 or input.slot_3 or input.slot_4): + print("[WEAPON DEBUG] _rollback_tick tick=%d slots=[s1=%s s2=%s s3=%s s4=%s] active=%d inv=%s" % [tick, input.slot_1, input.slot_2, input.slot_3, input.slot_4, active_slot, inventory]) + # Skip if player dead if player.death_tick >= 0 and tick >= player.death_tick: return @@ -406,3 +410,8 @@ func _spawn_grenade(origin: Vector3, direction: Vector3, owner_id: int, type_idx get_tree().current_scene.add_child(grenade) var gtype: Grenade.GrenadeType = Grenade.GrenadeType.FLASH if type_idx == 0 else Grenade.GrenadeType.SMOKE grenade.setup(origin, direction, owner_id, gtype) + +func _rollback_tick(delta: float, tick: int, _is_fresh: bool): + # DEBUG: Log when this is called with non-default input + if input and (input.slot_1 or input.slot_2 or input.slot_3 or input.slot_4): + print("[WEAPON DEBUG] _rollback_tick tick=%d slots=[%s %s %s %s]" % [tick, input.slot_1, input.slot_2, input.slot_3, input.slot_4]) diff --git a/examples/shared/scripts/lan-bootstrapper.gd b/examples/shared/scripts/lan-bootstrapper.gd index 2f2d980..838446d 100644 --- a/examples/shared/scripts/lan-bootstrapper.gd +++ b/examples/shared/scripts/lan-bootstrapper.gd @@ -72,6 +72,9 @@ func join(): print("Client started") connect_ui.hide() + # Ensure game window has keyboard focus for input + get_window().grab_focus() + # Only start manually if NetworkEvents is not handling it (e.g. multiplayer-simple) if not NetworkEvents.enabled: NetworkTime.start() @@ -80,8 +83,14 @@ func _enter_tree(): # Hide and show UI as appropriate # These handlers are necessary, since the game could have started via # autoconnect, or any other method - NetworkEvents.on_client_start.connect(func(__): connect_ui.hide()) - NetworkEvents.on_server_start.connect(func(): connect_ui.hide()) + NetworkEvents.on_client_start.connect(func(__): + connect_ui.hide() + get_window().grab_focus() + ) + NetworkEvents.on_server_start.connect(func(): + connect_ui.hide() + get_window().grab_focus() + ) NetworkEvents.on_client_stop.connect(func(): connect_ui.show()) NetworkEvents.on_server_stop.connect(func(): connect_ui.show()) diff --git a/tests/check_actions.gd.uid b/tests/check_actions.gd.uid new file mode 100644 index 0000000..de381eb --- /dev/null +++ b/tests/check_actions.gd.uid @@ -0,0 +1 @@ +uid://4mlr8ca8nlg7 diff --git a/tests/check_input_map.gd.uid b/tests/check_input_map.gd.uid new file mode 100644 index 0000000..204acf5 --- /dev/null +++ b/tests/check_input_map.gd.uid @@ -0,0 +1 @@ +uid://h5lroui7prfv diff --git a/tests/check_models.gd b/tests/check_models.gd deleted file mode 100644 index 707d8fc..0000000 --- a/tests/check_models.gd +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env -S godot --headless --script -extends SceneTree - -func _init(): - var paths = [ - "res://assets/kenney/models/blaster.glb", - "res://examples/multiplayer-fps/models/knife.fbx", - "res://examples/multiplayer-fps/models/grenade.glb", - ] - for p in paths: - var scene = load(p) - if scene: - var inst = scene.instantiate() - print(p, " -> type: ", inst.get_class(), " children: ", inst.get_child_count()) - for c in inst.get_children(): - print(" child: ", c.get_class(), " ", c.name) - inst.queue_free() - else: - print(p, " -> FAILED TO LOAD") - quit()