diff --git a/assets/audio/silly-menu-hover-test.ogg b/assets/audio/silly-menu-hover-test.ogg new file mode 100644 index 0000000..2d8ad2b Binary files /dev/null and b/assets/audio/silly-menu-hover-test.ogg differ diff --git a/assets/audio/silly-menu-hover-test.ogg.import b/assets/audio/silly-menu-hover-test.ogg.import new file mode 100644 index 0000000..0a47544 --- /dev/null +++ b/assets/audio/silly-menu-hover-test.ogg.import @@ -0,0 +1,19 @@ +[remap] + +importer="oggvorbisstr" +type="AudioStreamOggVorbis" +uid="uid://64dplcgx2icb" +path="res://.godot/imported/silly-menu-hover-test.ogg-101de051c9810c756b28483653a4c618.oggvorbisstr" + +[deps] + +source_file="res://assets/audio/silly-menu-hover-test.ogg" +dest_files=["res://.godot/imported/silly-menu-hover-test.ogg-101de051c9810c756b28483653a4c618.oggvorbisstr"] + +[params] + +loop=false +loop_offset=0 +bpm=0 +beat_count=0 +bar_beats=4 diff --git a/assets/audio/silly-test.ogg.import b/assets/audio/silly-test.ogg.import index 3a487af..db3d768 100644 --- a/assets/audio/silly-test.ogg.import +++ b/assets/audio/silly-test.ogg.import @@ -2,7 +2,7 @@ importer="oggvorbisstr" type="AudioStreamOggVorbis" -uid="uid://bftct74auklgw" +uid="uid://txgki0ijeuud" path="res://.godot/imported/silly-test.ogg-4a08df8a26b9ee1e5d13235e013c7cfc.oggvorbisstr" [deps] @@ -12,8 +12,8 @@ dest_files=["res://.godot/imported/silly-test.ogg-4a08df8a26b9ee1e5d13235e013c7c [params] -loop=true -loop_offset=0.0 -bpm=130.0 +loop=false +loop_offset=0 +bpm=0 beat_count=0 bar_beats=4 diff --git a/audio/bus_layout.tres b/audio/bus_layout.tres new file mode 100644 index 0000000..03b66b7 --- /dev/null +++ b/audio/bus_layout.tres @@ -0,0 +1,12 @@ +[gd_resource type="AudioBusLayout" format=3] + +[resource] +bus/0/name = "Master" +bus/0/volume_db = 0.0 +bus/0/send = "" +bus/1/name = "Music" +bus/1/volume_db = 0.0 +bus/1/send = "Master" +bus/2/name = "SFX" +bus/2/volume_db = 0.0 +bus/2/send = "Master" diff --git a/project.godot b/project.godot index 3e531ef..f3d0906 100644 --- a/project.godot +++ b/project.godot @@ -15,9 +15,14 @@ run/main_scene="uid://b4k81taauef4q" config/features=PackedStringArray("4.5", "Forward Plus") config/icon="res://icon.svg" +[audio] + +bus_layout="res://audio/bus_layout.tres" + [autoload] MenuMusic="*res://scenes/UI/menu_music.tscn" +MenuSfx="*res://scenes/UI/menu_sfx.tscn" [dotnet] diff --git a/scenes/Levels/menu.gd b/scenes/Levels/menu.gd index e0f1cd2..42c2f57 100644 --- a/scenes/Levels/menu.gd +++ b/scenes/Levels/menu.gd @@ -2,6 +2,9 @@ extends CanvasLayer @onready var pause_menu = $Control +func _ready() -> void: + _register_focus_sounds() + func _input(event): if event.is_action_pressed("ui_cancel"): if get_tree().paused: @@ -22,3 +25,21 @@ func _on_quit_button_pressed(): func _on_continue_button_pressed(): resume_game() + +func _register_focus_sounds() -> void: + if pause_menu == null: + return + var vbox := pause_menu.get_node_or_null("VBoxContainer") + if vbox == null: + return + for child in vbox.get_children(): + if child is BaseButton: + var button: BaseButton = child + if not button.is_connected("focus_entered", Callable(self, "_on_menu_item_focus")): + button.focus_entered.connect(_on_menu_item_focus) + if not button.is_connected("mouse_entered", Callable(self, "_on_menu_item_focus")): + button.mouse_entered.connect(_on_menu_item_focus) + +func _on_menu_item_focus() -> void: + if MenuSfx: + MenuSfx.play_hover() diff --git a/scenes/UI/Settings.gd b/scenes/UI/Settings.gd index 3a2c0d7..2b4d84b 100644 --- a/scenes/UI/Settings.gd +++ b/scenes/UI/Settings.gd @@ -5,7 +5,11 @@ extends Node2D @onready var _music_volume_slider: HSlider = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup/MusicVolumeSlider @onready var _music_volume_value: Label = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup/MusicVolumeValue @onready var _music_mute_checkbox: CheckBox = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/MusicVolumeGroup/MusicMuteCheckBox +@onready var _sfx_volume_slider: HSlider = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup/SfxVolumeSlider +@onready var _sfx_volume_value: Label = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup/SfxVolumeValue +@onready var _sfx_mute_checkbox: CheckBox = $MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup/SfxMuteCheckBox @onready var _menu_music: AudioStreamPlayer = get_tree().get_root().get_node_or_null("MenuMusic") +@onready var _menu_sfx: AudioStreamPlayer = get_tree().get_root().get_node_or_null("MenuSfx") func _ready() -> void: _tab_bar.tab_changed.connect(_on_tab_bar_tab_changed) @@ -13,7 +17,10 @@ func _ready() -> void: _tab_container.current_tab = _tab_bar.current_tab _music_volume_slider.value_changed.connect(_on_music_volume_changed) _music_mute_checkbox.toggled.connect(_on_music_mute_toggled) + _sfx_volume_slider.value_changed.connect(_on_sfx_volume_changed) + _sfx_mute_checkbox.toggled.connect(_on_sfx_mute_toggled) _sync_audio_controls() + _register_focus_sounds() func _input(event): if event.is_action_pressed("ui_cancel"): @@ -35,29 +42,71 @@ func _sync_audio_controls() -> void: value = _menu_music.get_user_volume() if _menu_music.has_method("is_user_muted"): muted = _menu_music.is_user_muted() - _music_volume_slider.set_block_signals(true) - _music_volume_slider.value = value - _music_volume_slider.set_block_signals(false) - _music_volume_slider.editable = not muted - _music_mute_checkbox.set_block_signals(true) - _music_mute_checkbox.button_pressed = muted - _music_mute_checkbox.set_block_signals(false) - _update_music_volume_label(value, muted) + _apply_audio_control_state(_music_volume_slider, _music_mute_checkbox, _music_volume_value, value, muted) + + var sfx_value: float = 0.7 + var sfx_muted: bool = false + if _menu_sfx: + if _menu_sfx.has_method("get_user_volume"): + sfx_value = _menu_sfx.get_user_volume() + if _menu_sfx.has_method("is_user_muted"): + sfx_muted = _menu_sfx.is_user_muted() + _apply_audio_control_state(_sfx_volume_slider, _sfx_mute_checkbox, _sfx_volume_value, sfx_value, sfx_muted) func _on_music_volume_changed(value: float) -> void: if _menu_music and _menu_music.has_method("set_user_volume"): _menu_music.set_user_volume(value) - _update_music_volume_label(value, _music_mute_checkbox.button_pressed) + _update_volume_label(_music_volume_value, value, _music_mute_checkbox.button_pressed) func _on_music_mute_toggled(pressed: bool) -> void: if _menu_music and _menu_music.has_method("set_user_muted"): _menu_music.set_user_muted(pressed) _music_volume_slider.editable = not pressed - _update_music_volume_label(_music_volume_slider.value, pressed) + _update_volume_label(_music_volume_value, _music_volume_slider.value, pressed) -func _update_music_volume_label(value: float, muted: bool) -> void: +func _on_sfx_volume_changed(value: float) -> void: + if _menu_sfx and _menu_sfx.has_method("set_user_volume"): + _menu_sfx.set_user_volume(value) + _update_volume_label(_sfx_volume_value, value, _sfx_mute_checkbox.button_pressed) + +func _on_sfx_mute_toggled(pressed: bool) -> void: + if _menu_sfx and _menu_sfx.has_method("set_user_muted"): + _menu_sfx.set_user_muted(pressed) + _sfx_volume_slider.editable = not pressed + _update_volume_label(_sfx_volume_value, _sfx_volume_slider.value, pressed) + +func _apply_audio_control_state(slider: HSlider, checkbox: CheckBox, value_label: Label, value: float, muted: bool) -> void: + slider.set_block_signals(true) + slider.value = value + slider.set_block_signals(false) + slider.editable = not muted + checkbox.set_block_signals(true) + checkbox.button_pressed = muted + checkbox.set_block_signals(false) + _update_volume_label(value_label, value, muted) + +func _update_volume_label(value_label: Label, value: float, muted: bool) -> void: if muted: - _music_volume_value.text = "Muted" + value_label.text = "Muted" else: var percent: int = int(round(value * 100.0)) - _music_volume_value.text = str(percent) + "%" + value_label.text = str(percent) + "%" + +func _register_focus_sounds() -> void: + _connect_focus_recursive(self) + +func _connect_focus_recursive(node: Node) -> void: + if node is Control: + var control: Control = node + if control.focus_mode != Control.FOCUS_NONE: + if not control.is_connected("focus_entered", Callable(self, "_on_menu_item_focus")): + control.focus_entered.connect(_on_menu_item_focus) + if control.has_signal("mouse_entered") and (control is BaseButton or control.focus_mode != Control.FOCUS_NONE): + if not control.is_connected("mouse_entered", Callable(self, "_on_menu_item_focus")): + control.mouse_entered.connect(_on_menu_item_focus) + for child in node.get_children(): + _connect_focus_recursive(child) + +func _on_menu_item_focus() -> void: + if MenuSfx: + MenuSfx.play_hover() diff --git a/scenes/UI/Settings.tscn b/scenes/UI/Settings.tscn index 836a799..f325614 100644 --- a/scenes/UI/Settings.tscn +++ b/scenes/UI/Settings.tscn @@ -78,9 +78,9 @@ grow_horizontal = 2 grow_vertical = 2 theme_override_constants/separation = 10 offset_left = 120.0 -offset_top = 120.0 +offset_top = 240.0 offset_right = -120.0 -offset_bottom = -120.0 +offset_bottom = -40.0 [node name="MusicVolumeLabel" type="Label" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox"] layout_mode = 2 @@ -115,6 +115,39 @@ layout_mode = 2 text = "Mute" size_flags_horizontal = 1 +[node name="SfxVolumeLabel" type="Label" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox"] +layout_mode = 2 +text = "Menu SFX Volume" +horizontal_alignment = 1 +size_flags_horizontal = 4 + +[node name="SfxVolumeGroup" type="HBoxContainer" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox"] +layout_mode = 2 +size_flags_horizontal = 4 +theme_override_constants/separation = 12 +alignment = 1 +custom_minimum_size = Vector2(0, 40) + +[node name="SfxVolumeSlider" type="HSlider" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup"] +layout_mode = 2 +min_value = 0.0 +max_value = 1.0 +step = 0.01 +size_flags_horizontal = 3 +custom_minimum_size = Vector2(320, 0) + +[node name="SfxVolumeValue" type="Label" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup"] +layout_mode = 2 +text = "70%" +size_flags_horizontal = 1 +horizontal_alignment = 1 +custom_minimum_size = Vector2(60, 0) + +[node name="SfxMuteCheckBox" type="CheckBox" parent="MarginContainer/VBoxContainer/TabContainer/Audio/AudioVBox/SfxVolumeGroup"] +layout_mode = 2 +text = "Mute" +size_flags_horizontal = 1 + [node name="Dev" type="Control" parent="MarginContainer/VBoxContainer/TabContainer"] layout_mode = 3 anchors_preset = 15 diff --git a/scenes/UI/menu_music.gd b/scenes/UI/menu_music.gd index 098b414..23de7c4 100644 --- a/scenes/UI/menu_music.gd +++ b/scenes/UI/menu_music.gd @@ -25,22 +25,28 @@ func _process(_delta: float) -> void: var current := get_tree().current_scene if current != _last_scene: _update_playback(current) + elif _should_play_scene(current) and not playing and not _is_muted and _user_volume_linear > 0.0: + play() func _update_playback(scene: Node) -> void: if scene == null: _last_scene = null return - _last_scene = scene - var should_play := false - var scene_path := scene.get_scene_file_path() - should_play = MENU_SCENES.has(scene_path) - if should_play: + if _should_play_scene(scene): if not playing: play() elif playing: stop() +func _should_play_scene(scene: Node) -> bool: + if scene == null: + return false + var scene_path: String = scene.get_scene_file_path() + if scene_path.is_empty(): + return false + return MENU_SCENES.has(scene_path) + func set_user_volume(value: float) -> void: var clamped_value: float = clamp(value, 0.0, 1.0) if is_equal_approx(_user_volume_linear, clamped_value): diff --git a/scenes/UI/menu_music.tscn b/scenes/UI/menu_music.tscn index 1b4ed67..ab2ec7b 100644 --- a/scenes/UI/menu_music.tscn +++ b/scenes/UI/menu_music.tscn @@ -5,7 +5,9 @@ [ext_resource type="Script" path="res://scenes/UI/menu_music.gd" id="2_21d4q"] [node name="MenuMusic" type="AudioStreamPlayer"] +bus = &"Music" stream = ExtResource("1_ek0t3") autoplay = true volume_db = -3.0 +priority = 10.0 script = ExtResource("2_21d4q") diff --git a/scenes/UI/menu_sfx.gd b/scenes/UI/menu_sfx.gd new file mode 100644 index 0000000..ab57e14 --- /dev/null +++ b/scenes/UI/menu_sfx.gd @@ -0,0 +1,76 @@ +extends AudioStreamPlayer + +const CONFIG_PATH := "user://settings.cfg" +const CONFIG_SECTION := "audio" +const CONFIG_KEY_VOLUME := "menu_sfx_volume" +const CONFIG_KEY_MUTED := "menu_sfx_muted" +const DEFAULT_VOLUME := 0.7 + +var _user_volume_linear: float = DEFAULT_VOLUME +var _is_muted: bool = false + +func _ready() -> void: + _load_settings() + _apply_volume() + +func play_hover() -> void: + if stream == null or _is_muted or _user_volume_linear <= 0.0: + return + play(0.0) + +func set_user_volume(value: float) -> void: + var clamped_value: float = clamp(value, 0.0, 1.0) + if is_equal_approx(_user_volume_linear, clamped_value): + return + _user_volume_linear = clamped_value + _apply_volume() + _save_settings() + +func get_user_volume() -> float: + return _user_volume_linear + +func set_user_muted(muted: bool) -> void: + if _is_muted == muted: + return + _is_muted = muted + _apply_volume() + _save_settings() + if _is_muted: + stop() + +func is_user_muted() -> bool: + return _is_muted + +func _apply_volume() -> void: + if _is_muted or _user_volume_linear <= 0.0: + volume_db = -80.0 + else: + volume_db = linear_to_db(_user_volume_linear) + +func _load_settings() -> void: + var config: ConfigFile = ConfigFile.new() + var err: int = config.load(CONFIG_PATH) + if err == OK: + _user_volume_linear = clamp(float(config.get_value(CONFIG_SECTION, CONFIG_KEY_VOLUME, DEFAULT_VOLUME)), 0.0, 1.0) + _is_muted = bool(config.get_value(CONFIG_SECTION, CONFIG_KEY_MUTED, false)) + elif err == ERR_DOES_NOT_EXIST: + _user_volume_linear = DEFAULT_VOLUME + _is_muted = false + else: + push_warning("Failed to load settings.cfg: %s" % err) + _user_volume_linear = DEFAULT_VOLUME + _is_muted = false + +func _save_settings() -> void: + var config: ConfigFile = ConfigFile.new() + var err: int = config.load(CONFIG_PATH) + if err != OK and err != ERR_DOES_NOT_EXIST: + push_warning("Failed to load settings.cfg before saving: %s" % err) + config = ConfigFile.new() + elif err != OK: + config = ConfigFile.new() + config.set_value(CONFIG_SECTION, CONFIG_KEY_VOLUME, _user_volume_linear) + config.set_value(CONFIG_SECTION, CONFIG_KEY_MUTED, _is_muted) + var save_err: int = config.save(CONFIG_PATH) + if save_err != OK: + push_warning("Failed to save settings.cfg: %s" % save_err) diff --git a/scenes/UI/menu_sfx.gd.uid b/scenes/UI/menu_sfx.gd.uid new file mode 100644 index 0000000..d0a31e3 --- /dev/null +++ b/scenes/UI/menu_sfx.gd.uid @@ -0,0 +1 @@ +uid://c7ixr4hbh5ad6 diff --git a/scenes/UI/menu_sfx.tscn b/scenes/UI/menu_sfx.tscn new file mode 100644 index 0000000..2c07257 --- /dev/null +++ b/scenes/UI/menu_sfx.tscn @@ -0,0 +1,13 @@ +[gd_scene load_steps=3 format=3 uid="uid://dt785sv7ie7uj"] + +[ext_resource type="AudioStream" uid="uid://64dplcgx2icb" path="res://assets/audio/silly-menu-hover-test.ogg" id="1_a5j5k"] +[ext_resource type="Script" path="res://scenes/UI/menu_sfx.gd" id="1_ijvfa"] + +[node name="MenuSfx" type="AudioStreamPlayer"] +bus = &"SFX" +stream = ExtResource("1_a5j5k") +volume_db = -6.0 +autoplay = false +priority = 0.5 +max_polyphony = 4 +script = ExtResource("1_ijvfa") diff --git a/scenes/UI/start_screen.gd b/scenes/UI/start_screen.gd index 6470fce..dbc509f 100644 --- a/scenes/UI/start_screen.gd +++ b/scenes/UI/start_screen.gd @@ -1,5 +1,17 @@ extends Control +func _ready(): + _register_focus_sounds() + +func _register_focus_sounds() -> void: + var button_container := $MarginContainer/MarginContainer/VBoxContainer + for child in button_container.get_children(): + if child is BaseButton: + var button: BaseButton = child + if not button.is_connected("focus_entered", Callable(self, "_on_menu_item_focus")): + button.focus_entered.connect(_on_menu_item_focus) + if not button.is_connected("mouse_entered", Callable(self, "_on_menu_item_focus")): + button.mouse_entered.connect(_on_menu_item_focus) func _on_start_button_pressed(): get_tree().change_scene_to_file("uid://dchj6g2i8ebph") @@ -9,3 +21,7 @@ func _on_settings_button_pressed(): func _on_quit_button_pressed(): get_tree().quit() + +func _on_menu_item_focus() -> void: + if MenuSfx: + MenuSfx.play_hover()