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
+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:
if body.is_in_group("player"):
AudioManager.play("pickup_scrap")
RunManager.earn_credits(value)
queue_free()
+1
View File
@@ -86,6 +86,7 @@ func get_slow_factor() -> float:
func take_damage(amount: float) -> void:
current_health -= amount
_flash_timer = 0.1
AudioManager.play("enemy_hit")
if current_health <= 0.0:
_die()
+1
View File
@@ -15,6 +15,7 @@ func _draw() -> void:
func _on_body_entered(body: Node) -> void:
if not body.is_in_group("player") or item == null:
return
AudioManager.play("item_pickup")
RunManager.collect_item(item) # Triggers item_collected signal -> player_stats recalculates
if item.heal_on_pickup > 0.0:
body.stats.heal(item.heal_on_pickup)
+1
View File
@@ -68,6 +68,7 @@ func _attempt_purchase() -> void:
return
RunManager.spend_credits(price)
AudioManager.play("item_pickup")
var player := get_tree().get_first_node_in_group("player") as Node2D
if player == null:
+2
View File
@@ -94,6 +94,7 @@ func _handle_shooting(delta: float) -> void:
_shoot_timer = 1.0 / stats.fire_rate
func _fire(direction: Vector2) -> void:
AudioManager.play("player_shoot")
# Overclock: every 3rd shot deals 0 damage
var is_zeroed_shot := false
if stats.overclock:
@@ -201,6 +202,7 @@ func take_damage(amount: float) -> void:
stats.current_health -= actual
_iframe_timer = IFRAME_DURATION
AudioManager.play("player_hit")
EventBus.player_health_changed.emit(stats.current_health, stats.max_health)
# 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
# ---------------------------------------------------------------------------
func _explode() -> void:
AudioManager.play("explosion")
var space := get_world_2d().direct_space_state
var query := PhysicsShapeQueryParameters2D.new()
var circle := CircleShape2D.new()
+3
View File
@@ -82,13 +82,16 @@ func _input(event: InputEvent) -> void:
if event.is_action_pressed("ui_up"):
_cursor = wrapi(_cursor - 1, 0, maxi(1, _items.size()))
AudioManager.play("ui_select")
_refresh_labels()
get_viewport().set_input_as_handled()
elif event.is_action_pressed("ui_down"):
_cursor = wrapi(_cursor + 1, 0, maxi(1, _items.size()))
AudioManager.play("ui_select")
_refresh_labels()
get_viewport().set_input_as_handled()
elif event.is_action_pressed("ui_accept"):
AudioManager.play("ui_click")
_equip_selected()
get_viewport().set_input_as_handled()
elif event.is_action_pressed("ui_cancel"):
+4 -4
View File
@@ -113,24 +113,24 @@ func _build_ui() -> void:
var dep_btn := Button.new()
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)
var wth_btn := Button.new()
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)
# Close
var close_btn := Button.new()
close_btn.text = "CLOSE"
close_btn.pressed.connect(close)
close_btn.pressed.connect(func(): AudioManager.play("ui_click"); close())
vbox.add_child(close_btn)
func _add_btn(parent: Node, text: String, cb: Callable) -> void:
var b := Button.new()
b.text = text
b.pressed.connect(cb)
b.pressed.connect(func(): AudioManager.play("ui_click"); cb.call())
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()
btn.text = text
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)
# ---------------------------------------------------------------------------
+1 -1
View File
@@ -53,7 +53,7 @@ func _show_victory() -> void:
var btn := Button.new()
btn.text = "RETURN TO HUB"
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)
_fade_in()
+1
View File
@@ -7,5 +7,6 @@ func _ready() -> void:
func _on_body_entered(body: Node) -> void:
if body.is_in_group("player") and part != null:
AudioManager.play("item_pickup")
UpgradeManager.acquire_part(part)
queue_free()