diff --git a/assets/audio/sfx/enemy_death.wav b/assets/audio/sfx/enemy_death.wav new file mode 100644 index 0000000..bb46d57 Binary files /dev/null and b/assets/audio/sfx/enemy_death.wav differ diff --git a/assets/audio/sfx/enemy_hit.wav b/assets/audio/sfx/enemy_hit.wav new file mode 100644 index 0000000..851db78 Binary files /dev/null and b/assets/audio/sfx/enemy_hit.wav differ diff --git a/assets/audio/sfx/explosion.wav b/assets/audio/sfx/explosion.wav new file mode 100644 index 0000000..370eeb6 Binary files /dev/null and b/assets/audio/sfx/explosion.wav differ diff --git a/assets/audio/sfx/item_pickup.wav b/assets/audio/sfx/item_pickup.wav new file mode 100644 index 0000000..43d05c0 Binary files /dev/null and b/assets/audio/sfx/item_pickup.wav differ diff --git a/assets/audio/sfx/pickup_scrap.wav b/assets/audio/sfx/pickup_scrap.wav new file mode 100644 index 0000000..ac3980f Binary files /dev/null and b/assets/audio/sfx/pickup_scrap.wav differ diff --git a/assets/audio/sfx/player_hit.wav b/assets/audio/sfx/player_hit.wav new file mode 100644 index 0000000..9ed3de9 Binary files /dev/null and b/assets/audio/sfx/player_hit.wav differ diff --git a/assets/audio/sfx/player_shoot.wav b/assets/audio/sfx/player_shoot.wav new file mode 100644 index 0000000..349d351 Binary files /dev/null and b/assets/audio/sfx/player_shoot.wav differ diff --git a/assets/audio/sfx/teleport.wav b/assets/audio/sfx/teleport.wav new file mode 100644 index 0000000..245a8fd Binary files /dev/null and b/assets/audio/sfx/teleport.wav differ diff --git a/assets/audio/sfx/ui_click.wav b/assets/audio/sfx/ui_click.wav new file mode 100644 index 0000000..077b2e8 Binary files /dev/null and b/assets/audio/sfx/ui_click.wav differ diff --git a/assets/audio/sfx/ui_select.wav b/assets/audio/sfx/ui_select.wav new file mode 100644 index 0000000..a2f0563 Binary files /dev/null and b/assets/audio/sfx/ui_select.wav differ diff --git a/project.godot b/project.godot index 6d6cc61..134cbe0 100644 --- a/project.godot +++ b/project.godot @@ -23,8 +23,9 @@ config/icon="res://assets/icon.svg" # ===== AUTOLOAD INITIALIZATION ORDER ===== # 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" +AudioManager="*res://scripts/autoload/audio_manager.gd" # Layer 1: Core managers (depend on EventBus only) GameManager="*res://scripts/game_manager.gd" SaveManager="*res://scripts/save_manager.gd" diff --git a/scripts/autoload/audio_manager.gd b/scripts/autoload/audio_manager.gd new file mode 100644 index 0000000..10d62a5 --- /dev/null +++ b/scripts/autoload/audio_manager.gd @@ -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() diff --git a/scripts/enemies/currency_orb.gd b/scripts/enemies/currency_orb.gd index cd068ee..c7a95cc 100644 --- a/scripts/enemies/currency_orb.gd +++ b/scripts/enemies/currency_orb.gd @@ -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() \ No newline at end of file diff --git a/scripts/enemies/enemy_base.gd b/scripts/enemies/enemy_base.gd index f033570..200849f 100644 --- a/scripts/enemies/enemy_base.gd +++ b/scripts/enemies/enemy_base.gd @@ -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() diff --git a/scripts/items/run_item_pickup.gd b/scripts/items/run_item_pickup.gd index 476078d..d6729b0 100644 --- a/scripts/items/run_item_pickup.gd +++ b/scripts/items/run_item_pickup.gd @@ -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) diff --git a/scripts/items/shop_item_pedestal.gd b/scripts/items/shop_item_pedestal.gd index e8734c9..02cacfa 100644 --- a/scripts/items/shop_item_pedestal.gd +++ b/scripts/items/shop_item_pedestal.gd @@ -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: diff --git a/scripts/player/player.gd b/scripts/player/player.gd index 08ae2a3..8cc2353 100644 --- a/scripts/player/player.gd +++ b/scripts/player/player.gd @@ -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 diff --git a/scripts/player/projectile.gd b/scripts/player/projectile.gd index d0ba453..4ff44ba 100644 --- a/scripts/player/projectile.gd +++ b/scripts/player/projectile.gd @@ -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() diff --git a/scripts/ui/armory_ui.gd b/scripts/ui/armory_ui.gd index 52de41a..6ffce66 100644 --- a/scripts/ui/armory_ui.gd +++ b/scripts/ui/armory_ui.gd @@ -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"): diff --git a/scripts/ui/atm_ui.gd b/scripts/ui/atm_ui.gd index db35127..4c291d5 100644 --- a/scripts/ui/atm_ui.gd +++ b/scripts/ui/atm_ui.gd @@ -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) # --------------------------------------------------------------------------- diff --git a/scripts/ui/pause_menu.gd b/scripts/ui/pause_menu.gd index ed6f86d..67fbdad 100644 --- a/scripts/ui/pause_menu.gd +++ b/scripts/ui/pause_menu.gd @@ -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) # --------------------------------------------------------------------------- diff --git a/scripts/ui/run_end_screen.gd b/scripts/ui/run_end_screen.gd index fc74da2..8043836 100644 --- a/scripts/ui/run_end_screen.gd +++ b/scripts/ui/run_end_screen.gd @@ -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() diff --git a/scripts/upgrades/body_part_pickup.gd b/scripts/upgrades/body_part_pickup.gd index 1b5b261..4255bda 100644 --- a/scripts/upgrades/body_part_pickup.gd +++ b/scripts/upgrades/body_part_pickup.gd @@ -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()