AudioManager: volume slider persistence (ConfigFile), settings UI with 5 teal-bordered sliders, default_bus_layout.tres for editor. Zone crossfade: defensive zone_id read from snapshot tiles, 1.5-2s ambient tween, auto-activates when server ships OQ-09. Dip profiles: dialogue dip in show/hide_dialogue, ListeningFocus 30-tick gate via stationary_ticks in game_state.gd. NPC murmur: client plumbing for event-driven World SFX playback, no-ops until audio asset arrives. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
160 lines
4.0 KiB
GDScript
160 lines
4.0 KiB
GDScript
extends Control
|
|
|
|
## #528: Audio settings dialog — 5-bus volume sliders.
|
|
## Opens on OPEN_MENU (ESC) from main.gd. Closes on OPEN_MENU again or CLOSE button.
|
|
## Volumes persist via AudioManager._save_prefs() on each slider change.
|
|
|
|
const BG_COLOR := Color(0.05, 0.05, 0.08, 0.90)
|
|
const BORDER_COLOR := Color("#4a9ebb")
|
|
const TEXT_COLOR := Color(0.878, 0.969, 0.98, 1)
|
|
const TITLE_COLOR := Color("#4a9ebb")
|
|
const FONT_SIZE := 14
|
|
|
|
const BOX_WIDTH := 460
|
|
const BOX_HEIGHT := 340
|
|
const PADDING := 20
|
|
const ROW_HEIGHT := 36
|
|
|
|
# Bus display labels → bus name strings (must match AudioManager BUS_* constants)
|
|
const BUS_ROWS: Array = [
|
|
["Music", "Music"],
|
|
["Ambient", "Ambient"],
|
|
["World SFX", "WorldSFX"],
|
|
["Player Actions", "PlayerActions"],
|
|
["UI Sounds", "UISounds"],
|
|
]
|
|
|
|
var _active: bool = false
|
|
var _container: VBoxContainer = null
|
|
|
|
signal closed
|
|
|
|
|
|
func _ready() -> void:
|
|
visible = false
|
|
mouse_filter = Control.MOUSE_FILTER_STOP
|
|
|
|
|
|
func open() -> void:
|
|
if _active:
|
|
return
|
|
_active = true
|
|
visible = true
|
|
_build_ui()
|
|
queue_redraw()
|
|
|
|
|
|
func close() -> void:
|
|
if not _active:
|
|
return
|
|
_active = false
|
|
visible = false
|
|
_destroy_ui()
|
|
queue_redraw()
|
|
closed.emit()
|
|
|
|
|
|
func is_open() -> bool:
|
|
return _active
|
|
|
|
|
|
func _build_ui() -> void:
|
|
var vp_size := get_viewport_rect().size
|
|
var box_pos := Vector2(
|
|
(vp_size.x - BOX_WIDTH) / 2.0,
|
|
(vp_size.y - BOX_HEIGHT) / 2.0
|
|
)
|
|
|
|
_container = VBoxContainer.new()
|
|
_container.position = box_pos + Vector2(PADDING, PADDING + 28)
|
|
_container.custom_minimum_size = Vector2(BOX_WIDTH - PADDING * 2, 0)
|
|
_container.add_theme_constant_override("separation", 4)
|
|
add_child(_container)
|
|
|
|
for row_data in BUS_ROWS:
|
|
var label_text: String = row_data[0]
|
|
var bus_name: String = row_data[1]
|
|
|
|
var hbox := HBoxContainer.new()
|
|
hbox.custom_minimum_size = Vector2(0, ROW_HEIGHT)
|
|
_container.add_child(hbox)
|
|
|
|
var label := Label.new()
|
|
label.text = label_text
|
|
label.custom_minimum_size = Vector2(150, 0)
|
|
label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
label.add_theme_font_size_override("font_size", FONT_SIZE)
|
|
label.add_theme_color_override("font_color", TEXT_COLOR)
|
|
hbox.add_child(label)
|
|
|
|
var slider := HSlider.new()
|
|
slider.min_value = -40.0
|
|
slider.max_value = 0.0
|
|
slider.step = 0.5
|
|
slider.value = AudioManager.get_volume(bus_name)
|
|
slider.size_flags_horizontal = Control.SIZE_EXPAND_FILL
|
|
hbox.add_child(slider)
|
|
|
|
var db_label := Label.new()
|
|
db_label.text = _format_db(slider.value)
|
|
db_label.custom_minimum_size = Vector2(70, 0)
|
|
db_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_RIGHT
|
|
db_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER
|
|
db_label.add_theme_font_size_override("font_size", FONT_SIZE)
|
|
db_label.add_theme_color_override("font_color", TEXT_COLOR)
|
|
hbox.add_child(db_label)
|
|
|
|
slider.value_changed.connect(func(value: float) -> void:
|
|
AudioManager.set_volume(bus_name, value)
|
|
db_label.text = _format_db(value)
|
|
)
|
|
|
|
# Spacer
|
|
var spacer := Control.new()
|
|
spacer.custom_minimum_size = Vector2(0, 8)
|
|
_container.add_child(spacer)
|
|
|
|
# Close button
|
|
var close_btn := Button.new()
|
|
close_btn.text = "CLOSE"
|
|
close_btn.add_theme_font_size_override("font_size", FONT_SIZE)
|
|
close_btn.pressed.connect(close)
|
|
_container.add_child(close_btn)
|
|
|
|
|
|
func _destroy_ui() -> void:
|
|
if _container:
|
|
_container.queue_free()
|
|
_container = null
|
|
|
|
|
|
func _draw() -> void:
|
|
if not _active:
|
|
return
|
|
var viewport_size := get_viewport_rect().size
|
|
|
|
# Dim overlay
|
|
draw_rect(Rect2(Vector2.ZERO, viewport_size), BG_COLOR)
|
|
|
|
# Dialog box
|
|
var box_pos := Vector2(
|
|
(viewport_size.x - BOX_WIDTH) / 2.0,
|
|
(viewport_size.y - BOX_HEIGHT) / 2.0
|
|
)
|
|
var box_rect := Rect2(box_pos, Vector2(BOX_WIDTH, BOX_HEIGHT))
|
|
draw_rect(box_rect, Color(0.08, 0.08, 0.12, 0.95))
|
|
draw_rect(box_rect, BORDER_COLOR, false, 1.0)
|
|
|
|
# Title
|
|
var font := ThemeDB.fallback_font
|
|
draw_string(font,
|
|
box_pos + Vector2(PADDING, PADDING + 18),
|
|
"AUDIO SETTINGS",
|
|
HORIZONTAL_ALIGNMENT_LEFT, -1, FONT_SIZE + 2, TITLE_COLOR)
|
|
|
|
|
|
static func _format_db(db: float) -> String:
|
|
if db <= -40.0:
|
|
return "mute"
|
|
return "%d dB" % int(db)
|