From 510138eb0a39a1e3de03071e66f86d4fce973064 Mon Sep 17 00:00:00 2001 From: Josh Creek <8179928+jcreek@users.noreply.github.com> Date: Tue, 1 Sep 2026 23:19:48 +0100 Subject: [PATCH] feat(audio): wire menu click feedback --- Game/scripts/audio_manager.gd | 15 +++++++++++++++ Game/scripts/main_menu.gd | 1 + Game/scripts/matchmaking.gd | 1 + Game/scripts/settings_menu.gd | 1 + Game/tests/cases/test_audio_manager.gd | 10 ++++++++++ 5 files changed, 28 insertions(+) diff --git a/Game/scripts/audio_manager.gd b/Game/scripts/audio_manager.gd index cdd312dd..f9d10797 100644 --- a/Game/scripts/audio_manager.gd +++ b/Game/scripts/audio_manager.gd @@ -9,6 +9,21 @@ const MAX_INTENSITY := 1.0 var enabled := true +func bind_tree_buttons(root: Node) -> void: + if root == null: + return + for node in root.find_children("*", "BaseButton", true, false): + bind_button(node as BaseButton) + + +func bind_button(button: BaseButton) -> void: + if button == null: + return + var callback := Callable(self, "play_ui_click") + if not button.pressed.is_connected(callback): + button.pressed.connect(callback) + + func play_ui_click() -> void: _play_tone(880.0, 0.045, 0.10) diff --git a/Game/scripts/main_menu.gd b/Game/scripts/main_menu.gd index 5a3e3bc8..d0f8dec2 100644 --- a/Game/scripts/main_menu.gd +++ b/Game/scripts/main_menu.gd @@ -37,6 +37,7 @@ const DIFFICULTIES := [ func _ready() -> void: + AudioManager.bind_tree_buttons(self) # An idle menu has no reason to render past the display's own refresh # rate; gameplay scenes are uncapped again by _leave_to_gameplay below. var refresh_rate := DisplayServer.screen_get_refresh_rate() diff --git a/Game/scripts/matchmaking.gd b/Game/scripts/matchmaking.gd index 88c5da59..eadefafe 100644 --- a/Game/scripts/matchmaking.gd +++ b/Game/scripts/matchmaking.gd @@ -21,6 +21,7 @@ var _recovery_poll_seconds := 0.0 func _ready() -> void: + AudioManager.bind_tree_buttons(self) playlist_dropdown.add_item("Casual") playlist_dropdown.set_item_metadata(0, "casual") playlist_dropdown.add_item("Ranked") diff --git a/Game/scripts/settings_menu.gd b/Game/scripts/settings_menu.gd index 2ccb70d9..2c9c0a01 100644 --- a/Game/scripts/settings_menu.gd +++ b/Game/scripts/settings_menu.gd @@ -48,6 +48,7 @@ var _populating := false func _ready() -> void: + AudioManager.bind_tree_buttons(self) # An idle settings screen has no reason to render past the display's own # refresh rate; _on_back_pressed only returns to another capped menu, so # no uncap is needed there (contrast main_menu.gd's _leave_to_gameplay). diff --git a/Game/tests/cases/test_audio_manager.gd b/Game/tests/cases/test_audio_manager.gd index f1b3f3c2..ca3d93aa 100644 --- a/Game/tests/cases/test_audio_manager.gd +++ b/Game/tests/cases/test_audio_manager.gd @@ -15,3 +15,13 @@ func test_countdown_frequency_has_bounded_monotonic_mapping() -> void: assert_eq(AudioManager.countdown_frequency(3), 605.0, "countdown tone is deterministic") assert_eq(AudioManager.countdown_frequency(99), 935.0, "large countdown values are capped") assert_true(AudioManager.countdown_frequency(2) < AudioManager.countdown_frequency(3), "countdown tones rise predictably") + + +func test_button_binding_is_idempotent() -> void: + var manager := AudioManager.new() + var button := Button.new() + manager.bind_button(button) + manager.bind_button(button) + assert_eq(button.pressed.get_connections().size(), 1, "UI click hook is not duplicated") + button.free() + manager.free()