extends Node ## 简易音效:用 AudioStreamWAV 程序合成 4 种音效。 ## 每次播放 new 一个 AudioStreamPlayer,结束后自动释放。 ## 之后要替换成 .wav 素材,把 _generate_xxx() 换成 load() 资源即可。 const SAMPLE_RATE: int = 22050 func play_hit() -> void: _play(_make_sin(880.0, 0.05, 0.4)) func play_break() -> void: _play(_make_noise(0.18, 0.5)) func play_lost() -> void: _play(_make_sweep(440.0, 110.0, 0.4, 0.5)) func play_win() -> void: _play(_make_sweep(523.0, 1046.0, 0.5, 0.5)) # ───────── 合成函数 ───────── func _make_sin(freq: float, duration: float, volume: float) -> AudioStreamWAV: return _build(duration, func(t: float, d: float) -> float: var env: float = 1.0 - t / d # 线性衰减 return sin(TAU * freq * t) * volume * env ) func _make_noise(duration: float, volume: float) -> AudioStreamWAV: return _build(duration, func(t: float, d: float) -> float: var env: float = 1.0 - t / d return randf_range(-volume, volume) * env ) func _make_sweep(from_freq: float, to_freq: float, duration: float, volume: float) -> AudioStreamWAV: return _build(duration, func(t: float, d: float) -> float: var progress: float = t / d var f: float = lerp(from_freq, to_freq, progress) var env: float = (1.0 - progress) * (1.0 - progress) # 平方衰减 return sin(TAU * f * t) * volume * env ) # 构造一个 AudioStreamWAV;sample_fn(t, duration) 返回 [-1, 1] 的振幅 func _build(duration: float, sample_fn: Callable) -> AudioStreamWAV: var sample_count: int = int(SAMPLE_RATE * duration) var data := PackedByteArray() data.resize(sample_count * 2) # 16-bit 单声道 for i in range(sample_count): var t: float = float(i) / SAMPLE_RATE var s: float = clamp(sample_fn.call(t, duration), -1.0, 1.0) # 16-bit: 范围 [-32768, 32767] var sample_i: int = int(s * 32767.0) data[2 * i] = sample_i & 0xFF data[2 * i + 1] = (sample_i >> 8) & 0xFF var stream := AudioStreamWAV.new() stream.format = AudioStreamWAV.FORMAT_16_BITS stream.mix_rate = SAMPLE_RATE stream.stereo = false stream.data = data return stream func _play(stream: AudioStream) -> void: var player := AudioStreamPlayer.new() player.stream = stream add_child(player) player.play() player.finished.connect(player.queue_free)