[agent:claude-cli] feat(audio): système audio complet — swim loop, boost, block break, echo ping, pearl collect, mort/respawn, deep sea ambient (placeholders AudioStreamGenerator si .ogg absent)
This commit is contained in:
@@ -64,6 +64,7 @@ func _ready() -> void:
|
||||
AudioManager.play_ambient_loop("underwater_ambient")
|
||||
AudioManager.play_music("underwater_theme")
|
||||
AudioManager.play_whale_call_random()
|
||||
AudioManager.play_deep_sea_ambient()
|
||||
|
||||
# Starting inventory
|
||||
inventory.add_item(2, 10)
|
||||
@@ -226,11 +227,11 @@ func _on_block_break(hit_position: Vector3, _normal: Vector3) -> void:
|
||||
broken_id = world.break_block(hit_position)
|
||||
if broken_id > 0:
|
||||
inventory.add_item(broken_id, 1)
|
||||
AudioManager.play_bubble_sfx(hit_position)
|
||||
AudioManager.play_block_break(hit_position)
|
||||
_award_break_xp(broken_id, hit_position)
|
||||
else:
|
||||
_world_sync.server_break_block(hit_position)
|
||||
AudioManager.play_bubble_sfx(hit_position)
|
||||
AudioManager.play_block_break(hit_position)
|
||||
|
||||
# Block break particle burst
|
||||
var bbp_script := load("res://scripts/dolphin/BlockBreakParticles.gd")
|
||||
@@ -335,7 +336,7 @@ func _spawn_consumable_popup(label: String, color: Color) -> void:
|
||||
|
||||
|
||||
func _on_echolocation(position: Vector3, _radius: float) -> void:
|
||||
AudioManager.play_bubble_sfx(position)
|
||||
# Echo ping is now fired directly from DolphinController; keep achievement hook
|
||||
var am: Node = get_node_or_null("/root/AchievementManager")
|
||||
if am != null:
|
||||
am.note_echolocation()
|
||||
|
||||
@@ -3,6 +3,8 @@ extends Node
|
||||
var music_player: AudioStreamPlayer
|
||||
var ambient_loop: AudioStreamPlayer
|
||||
var whale_player: AudioStreamPlayer
|
||||
var swim_loop_player: AudioStreamPlayer
|
||||
var boost_player: AudioStreamPlayer
|
||||
|
||||
var _whale_timer: Timer
|
||||
var _music_tween: Tween
|
||||
@@ -10,6 +12,9 @@ var _music_tween: Tween
|
||||
const MUSIC_PATH := "res://audio/music/"
|
||||
const SFX_PATH := "res://audio/sfx/"
|
||||
|
||||
# Generated stream cache (AudioStreamGenerator-based placeholders)
|
||||
var _gen_streams: Dictionary = {}
|
||||
|
||||
func _ready() -> void:
|
||||
music_player = AudioStreamPlayer.new()
|
||||
music_player.bus = "Music"
|
||||
@@ -26,6 +31,18 @@ func _ready() -> void:
|
||||
whale_player.volume_db = -15.0
|
||||
add_child(whale_player)
|
||||
|
||||
# Swim loop player (continuous while moving)
|
||||
swim_loop_player = AudioStreamPlayer.new()
|
||||
swim_loop_player.bus = "SFX"
|
||||
swim_loop_player.volume_db = -18.0
|
||||
add_child(swim_loop_player)
|
||||
|
||||
# Boost player (one-shot on boost start)
|
||||
boost_player = AudioStreamPlayer.new()
|
||||
boost_player.bus = "SFX"
|
||||
boost_player.volume_db = -10.0
|
||||
add_child(boost_player)
|
||||
|
||||
_whale_timer = Timer.new()
|
||||
_whale_timer.one_shot = true
|
||||
_whale_timer.timeout.connect(_on_whale_timer)
|
||||
@@ -33,6 +50,18 @@ func _ready() -> void:
|
||||
_schedule_next_whale()
|
||||
|
||||
|
||||
# Returns (or creates) a silent AudioStreamGenerator for placeholder sounds.
|
||||
func _get_silent_stream(duration_sec: float = 0.5) -> AudioStreamGenerator:
|
||||
var key := "silent_%.1f" % duration_sec
|
||||
if _gen_streams.has(key):
|
||||
return _gen_streams[key]
|
||||
var gen := AudioStreamGenerator.new()
|
||||
gen.mix_rate = 22050.0
|
||||
gen.buffer_length = duration_sec
|
||||
_gen_streams[key] = gen
|
||||
return gen
|
||||
|
||||
|
||||
func play_music(track_name: String) -> void:
|
||||
var path := MUSIC_PATH + track_name
|
||||
if not ResourceLoader.exists(path):
|
||||
@@ -56,6 +85,8 @@ func play_music(track_name: String) -> void:
|
||||
func play_ambient_loop(loop_name: String) -> void:
|
||||
var path := SFX_PATH + loop_name
|
||||
if not ResourceLoader.exists(path):
|
||||
# Fall back to silent generator so the slot is occupied
|
||||
ambient_loop.stream = _get_silent_stream(2.0)
|
||||
return
|
||||
|
||||
var stream: AudioStream = load(path)
|
||||
@@ -84,9 +115,6 @@ func play_whale_call_random() -> void:
|
||||
|
||||
func play_bubble_sfx(position: Vector3) -> void:
|
||||
var path := SFX_PATH + "bubbles.ogg"
|
||||
if not ResourceLoader.exists(path):
|
||||
return
|
||||
|
||||
var player := AudioStreamPlayer3D.new()
|
||||
player.bus = "SFX"
|
||||
player.volume_db = -8.0
|
||||
@@ -95,14 +123,176 @@ func play_bubble_sfx(position: Vector3) -> void:
|
||||
add_child(player)
|
||||
player.global_position = position
|
||||
|
||||
if ResourceLoader.exists(path):
|
||||
var stream: AudioStream = load(path)
|
||||
if stream == null:
|
||||
player.queue_free()
|
||||
return
|
||||
|
||||
if stream != null:
|
||||
player.stream = stream
|
||||
player.play()
|
||||
player.finished.connect(player.queue_free)
|
||||
return
|
||||
|
||||
# Placeholder: silent generator
|
||||
player.stream = _get_silent_stream(0.3)
|
||||
player.play()
|
||||
player.finished.connect(player.queue_free)
|
||||
|
||||
|
||||
# --- New: swim loop ---
|
||||
func play_swim_loop() -> void:
|
||||
if swim_loop_player.playing:
|
||||
return
|
||||
var path := SFX_PATH + "swim_loop.ogg"
|
||||
if ResourceLoader.exists(path):
|
||||
var s: AudioStream = load(path)
|
||||
if s != null:
|
||||
swim_loop_player.stream = s
|
||||
swim_loop_player.play()
|
||||
return
|
||||
# Silent placeholder keeps the architecture wired
|
||||
swim_loop_player.stream = _get_silent_stream(1.0)
|
||||
swim_loop_player.play()
|
||||
|
||||
|
||||
func stop_swim_loop() -> void:
|
||||
if swim_loop_player.playing:
|
||||
swim_loop_player.stop()
|
||||
|
||||
|
||||
# --- New: boost sound ---
|
||||
func play_boost() -> void:
|
||||
var path := SFX_PATH + "boost.ogg"
|
||||
if ResourceLoader.exists(path):
|
||||
var s: AudioStream = load(path)
|
||||
if s != null:
|
||||
boost_player.stream = s
|
||||
boost_player.play()
|
||||
return
|
||||
boost_player.stream = _get_silent_stream(0.4)
|
||||
boost_player.play()
|
||||
|
||||
|
||||
# --- New: block break ---
|
||||
func play_block_break(position: Vector3) -> void:
|
||||
var path := SFX_PATH + "block_break.ogg"
|
||||
var player := AudioStreamPlayer3D.new()
|
||||
player.bus = "SFX"
|
||||
player.volume_db = -6.0
|
||||
player.max_distance = 15.0
|
||||
add_child(player)
|
||||
player.global_position = position
|
||||
if ResourceLoader.exists(path):
|
||||
var s: AudioStream = load(path)
|
||||
if s != null:
|
||||
player.stream = s
|
||||
player.play()
|
||||
player.finished.connect(player.queue_free)
|
||||
return
|
||||
player.stream = _get_silent_stream(0.3)
|
||||
player.play()
|
||||
player.finished.connect(player.queue_free)
|
||||
|
||||
|
||||
# --- New: echolocation ping ---
|
||||
func play_echo_ping(position: Vector3) -> void:
|
||||
var path := SFX_PATH + "echo_ping.ogg"
|
||||
var player := AudioStreamPlayer3D.new()
|
||||
player.bus = "SFX"
|
||||
player.volume_db = -4.0
|
||||
player.max_distance = 40.0
|
||||
add_child(player)
|
||||
player.global_position = position
|
||||
if ResourceLoader.exists(path):
|
||||
var s: AudioStream = load(path)
|
||||
if s != null:
|
||||
player.stream = s
|
||||
player.play()
|
||||
player.finished.connect(player.queue_free)
|
||||
return
|
||||
player.stream = _get_silent_stream(0.5)
|
||||
player.play()
|
||||
player.finished.connect(player.queue_free)
|
||||
|
||||
|
||||
# --- New: pearl collect ---
|
||||
func play_pearl_collect(position: Vector3) -> void:
|
||||
var path := SFX_PATH + "pearl_collect.ogg"
|
||||
var player := AudioStreamPlayer3D.new()
|
||||
player.bus = "SFX"
|
||||
player.volume_db = -5.0
|
||||
player.max_distance = 20.0
|
||||
add_child(player)
|
||||
player.global_position = position
|
||||
if ResourceLoader.exists(path):
|
||||
var s: AudioStream = load(path)
|
||||
if s != null:
|
||||
player.stream = s
|
||||
player.play()
|
||||
player.finished.connect(player.queue_free)
|
||||
return
|
||||
player.stream = _get_silent_stream(0.4)
|
||||
player.play()
|
||||
player.finished.connect(player.queue_free)
|
||||
|
||||
|
||||
# --- New: death / respawn ---
|
||||
func play_death() -> void:
|
||||
var path := SFX_PATH + "death.ogg"
|
||||
if ResourceLoader.exists(path):
|
||||
var s: AudioStream = load(path)
|
||||
if s != null:
|
||||
var p := AudioStreamPlayer.new()
|
||||
p.bus = "SFX"
|
||||
p.volume_db = -5.0
|
||||
add_child(p)
|
||||
p.stream = s
|
||||
p.play()
|
||||
p.finished.connect(p.queue_free)
|
||||
return
|
||||
# silent fallback — architecture is wired
|
||||
var p2 := AudioStreamPlayer.new()
|
||||
p2.bus = "SFX"
|
||||
p2.stream = _get_silent_stream(0.6)
|
||||
add_child(p2)
|
||||
p2.play()
|
||||
p2.finished.connect(p2.queue_free)
|
||||
|
||||
|
||||
func play_respawn() -> void:
|
||||
var path := SFX_PATH + "respawn.ogg"
|
||||
if ResourceLoader.exists(path):
|
||||
var s: AudioStream = load(path)
|
||||
if s != null:
|
||||
var p := AudioStreamPlayer.new()
|
||||
p.bus = "SFX"
|
||||
p.volume_db = -6.0
|
||||
add_child(p)
|
||||
p.stream = s
|
||||
p.play()
|
||||
p.finished.connect(p.queue_free)
|
||||
return
|
||||
var p2 := AudioStreamPlayer.new()
|
||||
p2.bus = "SFX"
|
||||
p2.stream = _get_silent_stream(0.5)
|
||||
add_child(p2)
|
||||
p2.play()
|
||||
p2.finished.connect(p2.queue_free)
|
||||
|
||||
|
||||
# --- New: ambient deep sea (low drone, played on top of underwater_ambient) ---
|
||||
func play_deep_sea_ambient() -> void:
|
||||
var path := SFX_PATH + "deep_sea_ambient.ogg"
|
||||
var player := AudioStreamPlayer.new()
|
||||
player.bus = "SFX"
|
||||
player.volume_db = -20.0
|
||||
add_child(player)
|
||||
if ResourceLoader.exists(path):
|
||||
var s: AudioStream = load(path)
|
||||
if s != null:
|
||||
player.stream = s
|
||||
player.play()
|
||||
return
|
||||
player.stream = _get_silent_stream(2.0)
|
||||
player.play()
|
||||
|
||||
|
||||
func set_music_volume(db: float) -> void:
|
||||
|
||||
@@ -40,6 +40,7 @@ var _is_dead: bool = false
|
||||
@onready var bubble_trail: Node = $BubbleEmitterPoint/BubbleTrail
|
||||
|
||||
var _turn_input: float = 0.0
|
||||
var _was_boosting: bool = false
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -161,6 +162,21 @@ func _update_movement(delta: float) -> void:
|
||||
if bubble_trail:
|
||||
bubble_trail.set_intensity(speed_factor)
|
||||
|
||||
# Audio: swim loop
|
||||
var am: Node = get_node_or_null("/root/AudioManager")
|
||||
if am != null:
|
||||
if current_speed > 0.5:
|
||||
if am.has_method("play_swim_loop"):
|
||||
am.call("play_swim_loop")
|
||||
else:
|
||||
if am.has_method("stop_swim_loop"):
|
||||
am.call("stop_swim_loop")
|
||||
# Boost start sound
|
||||
if is_boosting and not _was_boosting:
|
||||
if am.has_method("play_boost"):
|
||||
am.call("play_boost")
|
||||
_was_boosting = is_boosting
|
||||
|
||||
|
||||
func _update_stats(delta: float) -> void:
|
||||
var changed: bool = false
|
||||
@@ -197,6 +213,9 @@ func _emit_stats() -> void:
|
||||
func _die() -> void:
|
||||
_is_dead = true
|
||||
player_died.emit()
|
||||
var am: Node = get_node_or_null("/root/AudioManager")
|
||||
if am != null and am.has_method("play_death"):
|
||||
am.call("play_death")
|
||||
# Respawn after short delay
|
||||
await get_tree().create_timer(2.0).timeout
|
||||
_respawn()
|
||||
@@ -210,6 +229,9 @@ func _respawn() -> void:
|
||||
hunger = max_hunger
|
||||
_is_dead = false
|
||||
_emit_stats()
|
||||
var am: Node = get_node_or_null("/root/AudioManager")
|
||||
if am != null and am.has_method("play_respawn"):
|
||||
am.call("play_respawn")
|
||||
|
||||
|
||||
func _animate_body() -> void:
|
||||
@@ -290,5 +312,8 @@ func _trigger_echolocation() -> void:
|
||||
echolocation_triggered.emit(global_position, 20.0)
|
||||
if is_instance_valid(_echo_pulse):
|
||||
_echo_pulse.trigger()
|
||||
var am: Node = get_node_or_null("/root/AudioManager")
|
||||
if am != null and am.has_method("play_echo_ping"):
|
||||
am.call("play_echo_ping", global_position)
|
||||
await get_tree().create_timer(1.2).timeout
|
||||
is_echolocating = false
|
||||
|
||||
@@ -58,5 +58,8 @@ func _on_body_entered(body: Node) -> void:
|
||||
return
|
||||
if body.is_in_group("player"):
|
||||
_collected = true
|
||||
var am: Node = get_node_or_null("/root/AudioManager")
|
||||
if am != null and am.has_method("play_pearl_collect"):
|
||||
am.call("play_pearl_collect", global_position)
|
||||
collected.emit(ITEM_ID_PEARL)
|
||||
queue_free()
|
||||
|
||||
Reference in New Issue
Block a user