node_added already fires once per node as it enters the tree (including every descendant of a newly-added subtree), so the recursive call_deferred in _on_node_added was redundant and held a raw Node reference across a frame boundary. If that node got freed before the deferred call fired (e.g. a container torn down and rebuilt the same frame), the engine's message queue couldn't marshal the argument and threw "Cannot convert argument 1 from Object to Object". Wiring synchronously removes the window entirely. Also silences the intentional integer division in _duration_text, matching the same annotation already applied to _elapsed_simulated_text.
134 lines
3.7 KiB
GDScript
134 lines
3.7 KiB
GDScript
extends Node
|
|
|
|
const SETTINGS_PATH := "user://settings.cfg"
|
|
const SETTINGS_SECTION := "audio"
|
|
const SETTINGS_KEY := "ui_sounds_enabled"
|
|
const PLAYER_COUNT := 8
|
|
const DEFAULT_VOLUME_DB := -12.0
|
|
const SOUND_PATHS := {
|
|
"click": "res://assets/audio/kenney_interface_sounds/click_003.ogg",
|
|
"select": "res://assets/audio/kenney_interface_sounds/select_003.ogg",
|
|
"toggle": "res://assets/audio/kenney_interface_sounds/toggle_002.ogg",
|
|
"eliminate": "res://assets/audio/kenney_interface_sounds/scratch_002.ogg",
|
|
"open": "res://assets/audio/kenney_interface_sounds/open_001.ogg",
|
|
"close": "res://assets/audio/kenney_interface_sounds/close_002.ogg",
|
|
"correct": "res://assets/audio/kenney_interface_sounds/confirmation_002.ogg",
|
|
"error": "res://assets/audio/kenney_interface_sounds/error_004.ogg",
|
|
"back": "res://assets/audio/kenney_interface_sounds/back_002.ogg",
|
|
"question": "res://assets/audio/kenney_interface_sounds/question_001.ogg"
|
|
}
|
|
|
|
var sounds := {}
|
|
var players: Array[AudioStreamPlayer] = []
|
|
var sounds_enabled := true
|
|
var next_player_index := 0
|
|
|
|
|
|
func _ready() -> void:
|
|
_load_setting()
|
|
_load_sounds()
|
|
_build_player_pool()
|
|
get_tree().node_added.connect(_on_node_added)
|
|
call_deferred("_wire_existing_buttons")
|
|
|
|
|
|
func play_sound(sound_name: String) -> void:
|
|
if not sounds_enabled or not sounds.has(sound_name) or players.is_empty():
|
|
return
|
|
var player := _next_available_player()
|
|
player.stream = sounds[sound_name]
|
|
player.play()
|
|
|
|
|
|
func set_sounds_enabled(is_enabled: bool) -> void:
|
|
if sounds_enabled == is_enabled:
|
|
return
|
|
sounds_enabled = is_enabled
|
|
if not sounds_enabled:
|
|
for player in players:
|
|
player.stop()
|
|
_save_setting()
|
|
if sounds_enabled:
|
|
play_sound("toggle")
|
|
|
|
|
|
func are_sounds_enabled() -> bool:
|
|
return sounds_enabled
|
|
|
|
|
|
func set_button_sound(button: Button, sound_name: String) -> void:
|
|
button.set_meta("ui_sound", sound_name)
|
|
_wire_button(button)
|
|
|
|
|
|
func _load_sounds() -> void:
|
|
for sound_name in SOUND_PATHS:
|
|
var stream := load(str(SOUND_PATHS[sound_name])) as AudioStream
|
|
if stream != null:
|
|
sounds[sound_name] = stream
|
|
else:
|
|
push_warning("Unable to load UI sound: %s" % SOUND_PATHS[sound_name])
|
|
|
|
|
|
func _build_player_pool() -> void:
|
|
for _player_index in range(PLAYER_COUNT):
|
|
var player := AudioStreamPlayer.new()
|
|
player.volume_db = DEFAULT_VOLUME_DB
|
|
add_child(player)
|
|
players.append(player)
|
|
|
|
|
|
func _next_available_player() -> AudioStreamPlayer:
|
|
for player in players:
|
|
if not player.playing:
|
|
return player
|
|
var player := players[next_player_index]
|
|
next_player_index = (next_player_index + 1) % players.size()
|
|
return player
|
|
|
|
|
|
func _on_node_added(node: Node) -> void:
|
|
if node is Button:
|
|
_wire_button(node)
|
|
|
|
|
|
func _wire_existing_buttons() -> void:
|
|
_wire_node(get_tree().root)
|
|
|
|
|
|
func _wire_node(node: Node) -> void:
|
|
if not is_instance_valid(node):
|
|
return
|
|
if node is Button:
|
|
_wire_button(node)
|
|
for child in node.get_children():
|
|
_wire_node(child)
|
|
|
|
|
|
func _wire_button(button: Button) -> void:
|
|
if button.has_meta("ui_sound_wired"):
|
|
return
|
|
button.set_meta("ui_sound_wired", true)
|
|
button.pressed.connect(_on_button_pressed.bind(button))
|
|
|
|
|
|
func _on_button_pressed(button: Button) -> void:
|
|
var sound_name := str(button.get_meta("ui_sound", "click"))
|
|
if sound_name != "none":
|
|
play_sound(sound_name)
|
|
|
|
|
|
func _load_setting() -> void:
|
|
var config := ConfigFile.new()
|
|
if config.load(SETTINGS_PATH) == OK:
|
|
sounds_enabled = bool(config.get_value(SETTINGS_SECTION, SETTINGS_KEY, true))
|
|
|
|
|
|
func _save_setting() -> void:
|
|
var config := ConfigFile.new()
|
|
config.load(SETTINGS_PATH)
|
|
config.set_value(SETTINGS_SECTION, SETTINGS_KEY, sounds_enabled)
|
|
var save_error := config.save(SETTINGS_PATH)
|
|
if save_error != OK:
|
|
push_warning("Unable to save UI sound preference.")
|