Audio manager implementation

This commit is contained in:
2026-03-11 10:07:47 -04:00
parent e11eddf1b4
commit ed4359686c
23 changed files with 61 additions and 7 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -1
View File
@@ -23,8 +23,9 @@ config/icon="res://assets/icon.svg"
# ===== AUTOLOAD INITIALIZATION ORDER ===== # ===== AUTOLOAD INITIALIZATION ORDER =====
# Order is critical! Dependencies must load before dependents. # Order is critical! Dependencies must load before dependents.
# Layer 0: Event system (no dependencies) # Layer 0: Core systems (no dependencies)
EventBus="*res://scripts/event_bus.gd" EventBus="*res://scripts/event_bus.gd"
AudioManager="*res://scripts/autoload/audio_manager.gd"
# Layer 1: Core managers (depend on EventBus only) # Layer 1: Core managers (depend on EventBus only)
GameManager="*res://scripts/game_manager.gd" GameManager="*res://scripts/game_manager.gd"
SaveManager="*res://scripts/save_manager.gd" SaveManager="*res://scripts/save_manager.gd"
+42
View File
@@ -0,0 +1,42 @@
extends Node
# ---------------------------------------------------------------------------
# AudioManager — singleton for playing sound effects
# Uses a pool of AudioStreamPlayers to allow overlapping sounds
# ---------------------------------------------------------------------------
const POOL_SIZE := 8
# Preloaded sound effects
var _sounds: Dictionary = {
"enemy_hit": preload("res://assets/audio/sfx/enemy_hit.wav"),
"player_hit": preload("res://assets/audio/sfx/player_hit.wav"),
"player_shoot": preload("res://assets/audio/sfx/player_shoot.wav"),
"explosion": preload("res://assets/audio/sfx/explosion.wav"),
"pickup_scrap": preload("res://assets/audio/sfx/pickup_scrap.wav"),
"item_pickup": preload("res://assets/audio/sfx/item_pickup.wav"),
"ui_click": preload("res://assets/audio/sfx/ui_click.wav"),
"ui_select": preload("res://assets/audio/sfx/ui_select.wav"),
}
var _pool: Array[AudioStreamPlayer] = []
var _pool_index: int = 0
func _ready() -> void:
process_mode = Node.PROCESS_MODE_ALWAYS
for i in POOL_SIZE:
var player := AudioStreamPlayer.new()
player.bus = "Master"
add_child(player)
_pool.append(player)
func play(sound_name: String) -> void:
if not _sounds.has(sound_name):
push_warning("AudioManager: Unknown sound '%s'" % sound_name)
return
var player := _pool[_pool_index]
_pool_index = (_pool_index + 1) % POOL_SIZE
player.stream = _sounds[sound_name]
player.play()
+1
View File
@@ -24,5 +24,6 @@ func _physics_process(delta: float) -> void:
func _on_body_entered(body: Node) -> void: func _on_body_entered(body: Node) -> void:
if body.is_in_group("player"): if body.is_in_group("player"):
AudioManager.play("pickup_scrap")
RunManager.earn_credits(value) RunManager.earn_credits(value)
queue_free() queue_free()
+1
View File
@@ -86,6 +86,7 @@ func get_slow_factor() -> float:
func take_damage(amount: float) -> void: func take_damage(amount: float) -> void:
current_health -= amount current_health -= amount
_flash_timer = 0.1 _flash_timer = 0.1
AudioManager.play("enemy_hit")
if current_health <= 0.0: if current_health <= 0.0:
_die() _die()
+1
View File
@@ -15,6 +15,7 @@ func _draw() -> void:
func _on_body_entered(body: Node) -> void: func _on_body_entered(body: Node) -> void:
if not body.is_in_group("player") or item == null: if not body.is_in_group("player") or item == null:
return return
AudioManager.play("item_pickup")
RunManager.collect_item(item) # Triggers item_collected signal -> player_stats recalculates RunManager.collect_item(item) # Triggers item_collected signal -> player_stats recalculates
if item.heal_on_pickup > 0.0: if item.heal_on_pickup > 0.0:
body.stats.heal(item.heal_on_pickup) body.stats.heal(item.heal_on_pickup)
+1
View File
@@ -68,6 +68,7 @@ func _attempt_purchase() -> void:
return return
RunManager.spend_credits(price) RunManager.spend_credits(price)
AudioManager.play("item_pickup")
var player := get_tree().get_first_node_in_group("player") as Node2D var player := get_tree().get_first_node_in_group("player") as Node2D
if player == null: if player == null:
+2
View File
@@ -94,6 +94,7 @@ func _handle_shooting(delta: float) -> void:
_shoot_timer = 1.0 / stats.fire_rate _shoot_timer = 1.0 / stats.fire_rate
func _fire(direction: Vector2) -> void: func _fire(direction: Vector2) -> void:
AudioManager.play("player_shoot")
# Overclock: every 3rd shot deals 0 damage # Overclock: every 3rd shot deals 0 damage
var is_zeroed_shot := false var is_zeroed_shot := false
if stats.overclock: if stats.overclock:
@@ -201,6 +202,7 @@ func take_damage(amount: float) -> void:
stats.current_health -= actual stats.current_health -= actual
_iframe_timer = IFRAME_DURATION _iframe_timer = IFRAME_DURATION
AudioManager.play("player_hit")
EventBus.player_health_changed.emit(stats.current_health, stats.max_health) EventBus.player_health_changed.emit(stats.current_health, stats.max_health)
# Reactive effects on taking damage # Reactive effects on taking damage
+1
View File
@@ -76,6 +76,7 @@ func _on_body_entered(body: Node) -> void:
# Explosive — AoE damage to all enemies in radius # Explosive — AoE damage to all enemies in radius
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
func _explode() -> void: func _explode() -> void:
AudioManager.play("explosion")
var space := get_world_2d().direct_space_state var space := get_world_2d().direct_space_state
var query := PhysicsShapeQueryParameters2D.new() var query := PhysicsShapeQueryParameters2D.new()
var circle := CircleShape2D.new() var circle := CircleShape2D.new()
+3
View File
@@ -82,13 +82,16 @@ func _input(event: InputEvent) -> void:
if event.is_action_pressed("ui_up"): if event.is_action_pressed("ui_up"):
_cursor = wrapi(_cursor - 1, 0, maxi(1, _items.size())) _cursor = wrapi(_cursor - 1, 0, maxi(1, _items.size()))
AudioManager.play("ui_select")
_refresh_labels() _refresh_labels()
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
elif event.is_action_pressed("ui_down"): elif event.is_action_pressed("ui_down"):
_cursor = wrapi(_cursor + 1, 0, maxi(1, _items.size())) _cursor = wrapi(_cursor + 1, 0, maxi(1, _items.size()))
AudioManager.play("ui_select")
_refresh_labels() _refresh_labels()
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
elif event.is_action_pressed("ui_accept"): elif event.is_action_pressed("ui_accept"):
AudioManager.play("ui_click")
_equip_selected() _equip_selected()
get_viewport().set_input_as_handled() get_viewport().set_input_as_handled()
elif event.is_action_pressed("ui_cancel"): elif event.is_action_pressed("ui_cancel"):
+4 -4
View File
@@ -113,24 +113,24 @@ func _build_ui() -> void:
var dep_btn := Button.new() var dep_btn := Button.new()
dep_btn.text = "DEPOSIT\n(ON HAND → BANK)" dep_btn.text = "DEPOSIT\n(ON HAND → BANK)"
dep_btn.pressed.connect(_on_deposit) dep_btn.pressed.connect(func(): AudioManager.play("ui_click"); _on_deposit())
action_row.add_child(dep_btn) action_row.add_child(dep_btn)
var wth_btn := Button.new() var wth_btn := Button.new()
wth_btn.text = "WITHDRAW\n(BANK → ON HAND)" wth_btn.text = "WITHDRAW\n(BANK → ON HAND)"
wth_btn.pressed.connect(_on_withdraw) wth_btn.pressed.connect(func(): AudioManager.play("ui_click"); _on_withdraw())
action_row.add_child(wth_btn) action_row.add_child(wth_btn)
# Close # Close
var close_btn := Button.new() var close_btn := Button.new()
close_btn.text = "CLOSE" close_btn.text = "CLOSE"
close_btn.pressed.connect(close) close_btn.pressed.connect(func(): AudioManager.play("ui_click"); close())
vbox.add_child(close_btn) vbox.add_child(close_btn)
func _add_btn(parent: Node, text: String, cb: Callable) -> void: func _add_btn(parent: Node, text: String, cb: Callable) -> void:
var b := Button.new() var b := Button.new()
b.text = text b.text = text
b.pressed.connect(cb) b.pressed.connect(func(): AudioManager.play("ui_click"); cb.call())
parent.add_child(b) parent.add_child(b)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
+1 -1
View File
@@ -43,7 +43,7 @@ func _add_button(parent: Node, text: String, callback: Callable) -> void:
var btn := Button.new() var btn := Button.new()
btn.text = text btn.text = text
btn.custom_minimum_size = Vector2(240, 48) btn.custom_minimum_size = Vector2(240, 48)
btn.pressed.connect(callback) btn.pressed.connect(func(): AudioManager.play("ui_click"); callback.call())
parent.add_child(btn) parent.add_child(btn)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
+1 -1
View File
@@ -53,7 +53,7 @@ func _show_victory() -> void:
var btn := Button.new() var btn := Button.new()
btn.text = "RETURN TO HUB" btn.text = "RETURN TO HUB"
btn.position = Vector2(-80, 70) btn.position = Vector2(-80, 70)
btn.pressed.connect(_fade_out_then_hub) btn.pressed.connect(func(): AudioManager.play("ui_click"); _fade_out_then_hub())
_root.add_child(btn) _root.add_child(btn)
_fade_in() _fade_in()
+1
View File
@@ -7,5 +7,6 @@ func _ready() -> void:
func _on_body_entered(body: Node) -> void: func _on_body_entered(body: Node) -> void:
if body.is_in_group("player") and part != null: if body.is_in_group("player") and part != null:
AudioManager.play("item_pickup")
UpgradeManager.acquire_part(part) UpgradeManager.acquire_part(part)
queue_free() queue_free()