Godot 4 GDScript 教程 / 音频系统
音频系统
概述
Godot 4 的音频系统提供了强大的音频播放、混音和效果处理能力。音频节点分为 2D/3D 空间音频和非空间音频(UI 音效、BGM),通过音频总线(Audio Bus)统一管理音量和效果。
| 节点 | 说明 | 适用场景 |
|---|
AudioStreamPlayer | 非空间音频 | BGM、UI 音效 |
AudioStreamPlayer2D | 2D 空间音频 | 2D 游戏音效 |
AudioStreamPlayer3D | 3D 空间音频 | 3D 游戏音效 |
AudioStreamPlayer(非空间音频)
基本用法
extends Node
@onready var bgm: AudioStreamPlayer = $BGMPlayer
@onready var sfx: AudioStreamPlayer = $SFXPlayer
func _ready():
# 设置音量(分贝)
bgm.volume_db = -6.0 # 负值降低音量
# 设置音调
bgm.pitch_scale = 1.0
# 自动播放
bgm.autoplay = true
# 播放
bgm.play()
func play_sfx(stream: AudioStream):
sfx.stream = stream
sfx.play()
# 多个音效同时播放
func play_sfx_concurrent(stream: AudioStream):
var player = AudioStreamPlayer.new()
player.stream = stream
player.bus = "SFX"
add_child(player)
player.play()
player.finished.connect(player.queue_free)
AudioStreamPlayer2D
extends Node2D
@onready var sfx: AudioStreamPlayer2D = $AudioStreamPlayer2D
func _ready():
# 最大距离(像素)
sfx.max_distance = 500.0
# 衰减模型
sfx.attenuation = 1.0 # 1.0 = 线性衰减
# 衰减滤波
sfx.attenuation_filter_cutoff_hz = 5000.0
sfx.attenuation_filter_db = -24.0
func play_sound():
sfx.play()
# 爆炸音效
func explosion_sound():
sfx.max_distance = 1000.0
sfx.volume_db = 3.0 # 爆炸声大一些
sfx.play()
2D 音频属性
| 属性 | 说明 | 默认值 |
|---|
max_distance | 最大可听距离 | 2000 |
attenuation | 衰减强度 | 1.0 |
panning_strength | 左右声道分离强度 | 1.0 |
bus | 音频总线名 | “Master” |
AudioStreamPlayer3D
extends Node3D
@onready var sfx: AudioStreamPlayer3D = $AudioStreamPlayer3D
func _ready():
# 最大距离(米)
sfx.max_distance = 30.0
# 衰减模型
sfx.attenuation_model = AudioStreamPlayer3D.ATTENUATION_INVERSE_DISTANCE
# 单位大小(距离参考值)
sfx.unit_size = 10.0
# 最大分贝
sfx.max_db = 6.0
# 多普勒效应
sfx.doppler_tracking = AudioStreamPlayer3D.DOPPLER_TRACKING_PHYSICS_STEP
3D 衰减模型
| 模型 | 公式 | 适用场景 |
|---|
ATTENUATION_INVERSE_DISTANCE | 1/d | 通用 |
ATTENUATION_INVERSE_SQUARE_DISTANCE | 1/d² | 真实物理 |
ATTENUATION_LOGARITHMIC | log(d) | 较大场景 |
ATTENUATION_DISABLED | 无衰减 | 不推荐 |
聆听者设置
# 主摄像机应挂载 AudioListener3D
# Godot 4 中 Camera3D 内置监听功能
extends Camera3D
func _ready():
# 3D 音频需要设置监听器
# Camera3D 默认作为监听器
make_current() # 确保摄像机是当前活动摄像机
音频总线 Bus
音频总线是混音器通道,控制音量和效果链。
代码管理音频总线
extends Node
func _ready():
# 创建新音频总线
AudioServer.add_bus()
var bus_idx = AudioServer.bus_count - 1
AudioServer.set_bus_name(bus_idx, "SFX")
AudioServer.set_bus_send(bus_idx, "Master")
# 设置音量
var master_idx = AudioServer.get_bus_index("Master")
AudioServer.set_bus_volume_db(master_idx, -6.0)
# 静音
AudioServer.set_bus_mute(master_idx, false)
# 声道平衡(-1 左,1 右)
AudioServer.set_bus_send(master_idx, "Master")
常用音频总线结构
Master(主输出)
├── BGM(背景音乐)
├── SFX(音效)
│ ├── SFX_Weapons
│ └── SFX_UI
└── Voice(语音)
音频效果
添加回声效果
extends Node
func _ready():
var bus_idx = AudioServer.get_bus_index("SFX")
# 添加回声效果
var echo = AudioEffectEcho.new()
echo.delay_ms = 200.0
echo.dry = 0.8 # 原始信号混合
echo.wet = 0.3 # 效果信号混合
echo.feedback = 0.4 # 反馈量
echo.filter = 0.5 # 滤波
AudioServer.add_bus_effect(bus_idx, echo)
添加混响效果
func add_reverb_to_bus(bus_name: String):
var bus_idx = AudioServer.get_bus_index(bus_name)
var reverb = AudioEffectReverb.new()
reverb.room_size = 0.8 # 房间大小(0~1)
reverb.damping = 0.5 # 阻尼
reverb.wet = 0.2 # 湿信号
reverb.dry = 0.8 # 干信号
reverb.spread = 1.0 # 扩散
reverb.hipass = 0.0 # 高通滤波
AudioServer.add_bus_effect(bus_idx, reverb)
# 岩洞场景开启混响
func enter_cave():
add_reverb_to_bus("SFX")
添加压缩器
func add_compressor(bus_name: String):
var bus_idx = AudioServer.get_bus_index(bus_name)
var comp = AudioEffectCompressor.new()
comp.threshold = -20.0 # 触发阈值(分贝)
comp.ratio = 4.0 # 压缩比
comp.attack_us = 50.0 # 攻击时间(微秒)
comp.release_ms = 200.0 # 释放时间(毫秒)
comp.gain = 6.0 # 增益(分贝)
AudioServer.add_bus_effect(bus_idx, comp)
添加 EQ 均衡器
func add_eq(bus_name: String):
var bus_idx = AudioServer.get_bus_index(bus_name)
var eq = AudioEffectEQ10.new()
# 10 段 EQ,每段增益(分贝)
eq.set_band_gain_db(0, -3.0) # 32 Hz
eq.set_band_gain_db(1, 0.0) # 64 Hz
eq.set_band_gain_db(2, 2.0) # 125 Hz
eq.set_band_gain_db(3, 3.0) # 250 Hz
eq.set_band_gain_db(4, 1.0) # 500 Hz
eq.set_band_gain_db(5, 0.0) # 1k Hz
eq.set_band_gain_db(6, -1.0) # 2k Hz
eq.set_band_gain_db(7, -2.0) # 4k Hz
eq.set_band_gain_db(8, -3.0) # 8k Hz
eq.set_band_gain_db(9, -4.0) # 16k Hz
AudioServer.add_bus_effect(bus_idx, eq)
音频效果速查表
| 效果 | 类名 | 用途 |
|---|
| 回声 | AudioEffectEcho | 延迟回声 |
| 混响 | AudioEffectReverb | 空间感 |
| 压缩 | AudioEffectCompressor | 动态范围控制 |
| 限制器 | AudioEffectLimiter | 防止爆音 |
| EQ 10 段 | AudioEffectEQ10 | 频率均衡 |
| EQ 21 段 | AudioEffectEQ21 | 精细均衡 |
| 低通滤波 | AudioEffectLowPassFilter | 柔化高频 |
| 高通滤波 | AudioEffectHighPassFilter | 去除低频 |
| 带通滤波 | AudioEffectBandPassFilter | 保留特定频段 |
| 失真 | AudioEffectDistortion | 过载效果 |
| 合唱 | AudioEffectChorus | 厚重感 |
| 音量切换 | AudioEffectAmplify | 增益调整 |
音量分贝控制
extends Node
# 线性音量(0.0~1.0)与分贝互转
func linear_to_db(linear: float) -> float:
if linear <= 0:
return -80.0 # 静音
return 20.0 * log(linear) / log(10)
func db_to_linear(db: float) -> float:
return pow(10.0, db / 20.0)
# 实际使用
func set_master_volume(linear: float):
var master_idx = AudioServer.get_bus_index("Master")
AudioServer.set_bus_volume_db(master_idx, linear_to_db(clamp(linear, 0.0, 1.0)))
| 线性值 | 分贝值 | 说明 |
|---|
| 1.0 | 0 dB | 原始音量 |
| 0.5 | -6 dB | 一半音量 |
| 0.25 | -12 dB | 四分之一 |
| 0.1 | -20 dB | 很小 |
| 0.0 | -∞ | 静音 |
音频流类型
| 格式 | 扩展名 | 特点 | 适用场景 |
|---|
| WAV | .wav | 无压缩,大文件 | 短音效 |
| OGG | .ogg | 有损压缩,体积小 | BGM、长音频 |
| MP3 | .mp3 | 有损压缩,广泛兼容 | BGM(兼容性) |
💡 提示:短音效(按键、脚步声)用 WAV,BGM 和长音频用 OGG 格式平衡质量与体积。
动态音乐系统
extends Node
@onready var layer_calm: AudioStreamPlayer = $CalmLayer
@onready var layer_combat: AudioStreamPlayer = $CombatLayer
@onready var layer_boss: AudioStreamPlayer = $BossLayer
var current_state: String = "calm"
func _ready():
# 所有层同时播放,通过音量控制切换
layer_calm.play()
layer_combat.play()
layer_boss.play()
layer_calm.volume_db = 0
layer_combat.volume_db = -80
layer_boss.volume_db = -80
func transition_to(new_state: String, duration: float = 2.0):
if new_state == current_state:
return
var tween = create_tween().set_parallel(true)
# 淡出当前层
match current_state:
"calm":
tween.tween_property(layer_calm, "volume_db", -80.0, duration)
"combat":
tween.tween_property(layer_combat, "volume_db", -80.0, duration)
"boss":
tween.tween_property(layer_boss, "volume_db", -80.0, duration)
# 淡入新层
match new_state:
"calm":
tween.tween_property(layer_calm, "volume_db", 0.0, duration)
"combat":
tween.tween_property(layer_combat, "volume_db", 0.0, duration)
"boss":
tween.tween_property(layer_boss, "volume_db", 0.0, duration)
current_state = new_state
# 使用示例
func _on_combat_started():
transition_to("combat")
func _on_boss_appeared():
transition_to("boss")
func _on_combat_ended():
transition_to("calm")
3D 空间音频实战
extends Node3D
# 环境音效系统
@onready var ambient_wind: AudioStreamPlayer3D = $AmbientWind
@onready var water_stream: AudioStreamPlayer3D = $WaterStream
func _ready():
# 风声:全局环境
ambient_wind.bus = "Ambient"
ambient_wind.max_distance = 100.0
ambient_wind.attenuation_model = AudioStreamPlayer3D.ATTENUATION_DISABLED
# 水流声:位置感
water_stream.bus = "SFX"
water_stream.max_distance = 30.0
water_stream.unit_size = 5.0
射击音效系统
extends Node3D
@export var shoot_sound: AudioStream
@export var impact_sound: AudioStream
func shoot():
# 武器音效(3D 空间音效)
var player = AudioStreamPlayer3D.new()
player.stream = shoot_sound
player.max_distance = 50.0
player.unit_size = 3.0
add_child(player)
player.play()
player.finished.connect(player.queue_free)
func play_impact(pos: Vector3):
var player = AudioStreamPlayer3D.new()
player.stream = impact_sound
player.global_position = pos
player.max_distance = 20.0
get_tree().current_scene.add_child(player)
player.play()
player.finished.connect(player.queue_free)
音频与游戏事件联动
extends Node
@onready var ui_hover: AudioStreamPlayer = $UIHover
@onready var ui_click: AudioStreamPlayer = $UIClick
@onready var damage_sound: AudioStreamPlayer = $DamageSound
@onready var collect_sound: AudioStreamPlayer = $CollectSound
# UI 音效
func play_ui_hover():
ui_hover.pitch_scale = randf_range(0.95, 1.05) # 轻微随机化
ui_hover.play()
func play_ui_click():
ui_click.pitch_scale = randf_range(0.98, 1.02)
ui_click.play()
# 游戏音效
func _on_player_damaged(amount: float):
damage_sound.pitch_scale = randf_range(0.9, 1.1)
damage_sound.volume_db = clampf(amount * 0.1, -12, 3)
damage_sound.play()
func _on_item_collected():
collect_sound.play()
# 脚步声随机化
func play_footstep(player: AudioStreamPlayer3D):
player.pitch_scale = randf_range(0.85, 1.15)
player.volume_db = randf_range(-3.0, 0.0)
player.play()
音频最佳实践
| 实践 | 说明 |
|---|
| 使用音频总线分组 | BGM / SFX / Voice 分开管理 |
| 短音效用 WAV | 避免解码开销 |
| 长音频用 OGG | 节省内存 |
| 随机化音调 | pitch_scale 加 randf_range 避免重复感 |
| 淡入淡出 | 场景切换时平滑过渡 |
| 对象池复用 | 频繁播放的音效复用 AudioStreamPlayer |
| 合理设置 max_distance | 远处物体不消耗音频资源 |
游戏开发场景
| 场景 | 推荐方案 |
|---|
| BGM | AudioStreamPlayer + 音频总线 |
| 2D 音效 | AudioStreamPlayer2D + 随机化 |
| 3D 环境音 | AudioStreamPlayer3D + Ambient 总线 |
| 动态音乐 | 多层 AudioStreamPlayer + 淡入淡出 |
| UI 音效 | AudioStreamPlayer + SFX 总线 |
| 语音对话 | AudioStreamPlayer + Voice 总线 |
⚠️ 常见陷阱
- 分贝不是线性的:
-6 dB 约等于一半音量,-20 dB 是原来的十分之一 pitch_scale 改变音调也改变播放速度,Godot 4 无独立速度控制- 同时播放太多音效会卡顿,使用对象池限制并发数
- OGG 文件在 Web 导出可能有兼容性问题,Web 平台优先使用 WAV/MP3
- AudioStreamPlayer3D 需要 Camera3D 作为监听器,否则无声
扩展阅读