class_name Sfx ## Tiny audio utility over the procedural kit in assets/sounds (tools/gen_sounds.py). ## Loops are flagged here rather than in per-file .import settings so a wav ## regenerated by the tool never silently loses its loop flag. const DIR := "res://assets/sounds/%s.wav" const LOOPS := ["engine", "skid", "boost", "siren"] static var _cache := {} static func stream(name: String) -> AudioStream: if _cache.has(name): return _cache[name] var path := DIR % name if not ResourceLoader.exists(path): return null var s: AudioStream = load(path) if s is AudioStreamWAV and name in LOOPS: var w := s as AudioStreamWAV w.loop_mode = AudioStreamWAV.LOOP_FORWARD w.loop_begin = 0 w.loop_end = w.data.size() / 2 # 16-bit mono: 2 bytes per frame _cache[name] = s return s static func looper(parent: Node, name: String, db: float) -> AudioStreamPlayer3D: ## Persistent 3D player for a looped bed (engine, skid, siren). Not playing ## until the caller decides; returns null when the kit isn't generated yet. var s := stream(name) if s == null: return null var p := AudioStreamPlayer3D.new() p.stream = s p.volume_db = db p.max_distance = 90.0 parent.add_child(p) return p static func shot(parent: Node, name: String, db := 0.0, pitch := 1.0) -> void: ## Fire-and-forget one-shot at the parent's position. var s := stream(name) if s == null: return var p := AudioStreamPlayer3D.new() p.stream = s p.volume_db = db p.pitch_scale = pitch p.max_distance = 160.0 parent.add_child(p) p.play() p.finished.connect(p.queue_free)