feat(sprint-22): character SFX + UltrasoundMachine ambient heartbeat
This commit is contained in:
@@ -79,3 +79,17 @@ All CC0 or CC-BY from freesound.org. Replace placeholder 0-byte files with the d
|
|||||||
| `assets/audio/sfx/object_tap.ogg` | soft tap / click | search "soft tap" or "gentle click" |
|
| `assets/audio/sfx/object_tap.ogg` | soft tap / click | search "soft tap" or "gentle click" |
|
||||||
|
|
||||||
All files must be <1.5 s, child-friendly (no harsh/loud sounds), mono or stereo, 44100 Hz, OGG Vorbis.
|
All files must be <1.5 s, child-friendly (no harsh/loud sounds), mono or stereo, 44100 Hz, OGG Vorbis.
|
||||||
|
|
||||||
|
## Sprint 22 — Character & Ambient SFX
|
||||||
|
|
||||||
|
All CC0 or CC-BY from freesound.org. Replace placeholder 0-byte files with the downloads below.
|
||||||
|
|
||||||
|
| File | Description | Freesound suggestion |
|
||||||
|
|---|---|---|
|
||||||
|
| `assets/audio/sfx/ultrasound_heartbeat.ogg` | soft beep/blip ~1s, loops seamlessly | search "heartbeat beep soft" or "medical monitor beep" |
|
||||||
|
| `assets/audio/sfx/character_pickup.ogg` | happy soft squeak / whoosh | search "cartoon pickup soft" or "whoosh gentle" |
|
||||||
|
| `assets/audio/sfx/character_place.ogg` | gentle thud / landing | search "soft thud" or "gentle landing" |
|
||||||
|
| `assets/audio/sfx/character_tap.ogg` | short happy chime / pop | search "happy chime short" or "cartoon pop soft" |
|
||||||
|
|
||||||
|
All files must be child-friendly (no harsh/loud sounds), mono or stereo, 44100 Hz, OGG Vorbis.
|
||||||
|
`ultrasound_heartbeat.ogg` must loop seamlessly (start and end points match).
|
||||||
|
|||||||
@@ -27,6 +27,9 @@ const _SFX_MAP: Dictionary = {
|
|||||||
"ambulance_siren": "res://assets/audio/sfx/ambulance_siren.ogg",
|
"ambulance_siren": "res://assets/audio/sfx/ambulance_siren.ogg",
|
||||||
"delivery_cheer": "res://assets/audio/sfx/delivery_cheer.ogg",
|
"delivery_cheer": "res://assets/audio/sfx/delivery_cheer.ogg",
|
||||||
"object_tap": "res://assets/audio/sfx/object_tap.ogg",
|
"object_tap": "res://assets/audio/sfx/object_tap.ogg",
|
||||||
|
"character_pickup": "res://assets/audio/sfx/character_pickup.ogg",
|
||||||
|
"character_place": "res://assets/audio/sfx/character_place.ogg",
|
||||||
|
"character_tap": "res://assets/audio/sfx/character_tap.ogg",
|
||||||
}
|
}
|
||||||
|
|
||||||
var _current_floor: int = -1
|
var _current_floor: int = -1
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ func _handle_outfit_tap() -> void:
|
|||||||
|
|
||||||
|
|
||||||
func _on_drag_picked_up(pos: Vector2) -> void:
|
func _on_drag_picked_up(pos: Vector2) -> void:
|
||||||
|
AudioManager.play_sfx("character_pickup")
|
||||||
_is_held = true
|
_is_held = true
|
||||||
_drag_start_position = pos
|
_drag_start_position = pos
|
||||||
set_animation_state("held")
|
set_animation_state("held")
|
||||||
@@ -197,9 +198,11 @@ func _on_drag_released(pos: Vector2) -> void:
|
|||||||
_is_held = false
|
_is_held = false
|
||||||
var drag_distance: float = pos.distance_to(_drag_start_position)
|
var drag_distance: float = pos.distance_to(_drag_start_position)
|
||||||
if drag_distance < _TAP_THRESHOLD:
|
if drag_distance < _TAP_THRESHOLD:
|
||||||
|
AudioManager.play_sfx("character_tap")
|
||||||
set_animation_state("idle")
|
set_animation_state("idle")
|
||||||
_handle_outfit_tap()
|
_handle_outfit_tap()
|
||||||
return
|
return
|
||||||
|
AudioManager.play_sfx("character_place")
|
||||||
set_animation_state("idle")
|
set_animation_state("idle")
|
||||||
if data == null or data.id.is_empty():
|
if data == null or data.id.is_empty():
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
## UltrasoundMachine — displays a continuous heartbeat pulse on the screen.
|
## UltrasoundMachine — displays a continuous heartbeat pulse on the screen.
|
||||||
|
## Plays looping ambient heartbeat audio when the ultrasound room is active.
|
||||||
class_name UltrasoundMachine extends Node2D
|
class_name UltrasoundMachine extends Node2D
|
||||||
|
|
||||||
const BEAT_RISE_DURATION: float = 0.12
|
const BEAT_RISE_DURATION: float = 0.12
|
||||||
@@ -6,10 +7,50 @@ const BEAT_FALL_DURATION: float = 0.12
|
|||||||
const BEAT_INTERVAL: float = 0.60
|
const BEAT_INTERVAL: float = 0.60
|
||||||
const BEAT_SCALE_PEAK: Vector2 = Vector2(1.5, 1.5)
|
const BEAT_SCALE_PEAK: Vector2 = Vector2(1.5, 1.5)
|
||||||
const BEAT_SCALE_REST: Vector2 = Vector2(1.0, 1.0)
|
const BEAT_SCALE_REST: Vector2 = Vector2(1.0, 1.0)
|
||||||
|
const _HEARTBEAT_PATH: String = "res://assets/audio/sfx/ultrasound_heartbeat.ogg"
|
||||||
|
|
||||||
|
@export var trigger_floor: int = 2
|
||||||
|
@export var trigger_room: int = 0
|
||||||
|
|
||||||
|
var _audio: AudioStreamPlayer
|
||||||
|
|
||||||
|
|
||||||
func _ready() -> void:
|
func _ready() -> void:
|
||||||
_start_heartbeat_loop()
|
_start_heartbeat_loop()
|
||||||
|
_setup_audio()
|
||||||
|
RoomNavigator.room_changed.connect(_on_room_changed)
|
||||||
|
|
||||||
|
|
||||||
|
func _exit_tree() -> void:
|
||||||
|
if RoomNavigator.room_changed.is_connected(_on_room_changed):
|
||||||
|
RoomNavigator.room_changed.disconnect(_on_room_changed)
|
||||||
|
|
||||||
|
|
||||||
|
func _setup_audio() -> void:
|
||||||
|
_audio = AudioStreamPlayer.new()
|
||||||
|
add_child(_audio)
|
||||||
|
if AudioServer.get_driver_name() == "Dummy":
|
||||||
|
return
|
||||||
|
if not ResourceLoader.exists(_HEARTBEAT_PATH):
|
||||||
|
return
|
||||||
|
var base: AudioStream = load(_HEARTBEAT_PATH) as AudioStream
|
||||||
|
if base == null:
|
||||||
|
return
|
||||||
|
var ogg: AudioStreamOggVorbis = base as AudioStreamOggVorbis
|
||||||
|
if ogg == null:
|
||||||
|
return
|
||||||
|
ogg = ogg.duplicate() as AudioStreamOggVorbis
|
||||||
|
ogg.loop = true
|
||||||
|
_audio.stream = ogg
|
||||||
|
|
||||||
|
|
||||||
|
func _on_room_changed(floor_index: int, room_index: int) -> void:
|
||||||
|
if _audio == null or _audio.stream == null:
|
||||||
|
return
|
||||||
|
if floor_index == trigger_floor and room_index == trigger_room:
|
||||||
|
_audio.play()
|
||||||
|
else:
|
||||||
|
_audio.stop()
|
||||||
|
|
||||||
|
|
||||||
func _start_heartbeat_loop() -> void:
|
func _start_heartbeat_loop() -> void:
|
||||||
|
|||||||
@@ -83,3 +83,9 @@ func test_sfx_map_has_all_interactive_object_keys() -> void:
|
|||||||
assert_true(AudioManager._SFX_MAP.has("ambulance_siren"))
|
assert_true(AudioManager._SFX_MAP.has("ambulance_siren"))
|
||||||
assert_true(AudioManager._SFX_MAP.has("delivery_cheer"))
|
assert_true(AudioManager._SFX_MAP.has("delivery_cheer"))
|
||||||
assert_true(AudioManager._SFX_MAP.has("object_tap"))
|
assert_true(AudioManager._SFX_MAP.has("object_tap"))
|
||||||
|
|
||||||
|
|
||||||
|
func test_sfx_map_has_all_character_keys() -> void:
|
||||||
|
assert_true(AudioManager._SFX_MAP.has("character_pickup"))
|
||||||
|
assert_true(AudioManager._SFX_MAP.has("character_place"))
|
||||||
|
assert_true(AudioManager._SFX_MAP.has("character_tap"))
|
||||||
|
|||||||
Reference in New Issue
Block a user