Godot 3 GDScript 教程 / Godot 3 GDScript 教程(十二):音频系统
音频系统
音频是游戏沉浸感的关键组成部分。Godot 3 提供了一套完整的音频系统,涵盖 2D/3D 空间音频、音频总线混音、音效处理以及动态音乐管理。
音频播放节点
Godot 3 提供三种音频播放节点,分别适用于不同场景。
节点对比
| 节点 | 适用场景 | 空间化 | 衰减 |
|---|---|---|---|
AudioStreamPlayer | BGM、UI 音效、全局音效 | ❌ | ❌ |
AudioStreamPlayer2D | 2D 游戏中的位置音效 | ✅ | ✅ |
AudioStreamPlayer3D | 3D 游戏中的位置音效 | ✅ | ✅ |
AudioStreamPlayer(非空间音频)
# 播放背景音乐
$BGMPlayer.stream = preload("res://audio/bgm_main.ogg")
$BGMPlayer.bus = "Music"
$BGMPlayer.volume_db = -6.0
$BGMPlayer.play()
# 延迟播放(从 1.0 秒处开始)
$BGMPlayer.play(1.0)
# 停止
$BGMPlayer.stop()
AudioStreamPlayer2D(2D 空间音频)
# 2D 环境中的位置音效
$ExplosionSound.stream = preload("res://audio/explosion.wav")
$ExplosionSound.bus = "SFX"
$ExplosionSound.max_distance = 500.0 # 超过此距离无声音
$ExplosionSound.attenuation = 1.0 # 衰减系数
$ExplosionSound.play()
常用属性
| 属性 | 说明 |
|---|---|
max_distance | 声音可听的最大距离(2D/3D) |
attenuation | 距离衰减系数(越大衰减越快) |
unit_db | 基础音量(dB) |
pitch_scale | 音高变化(1.0 为原速) |
playing | 是否正在播放 |
音频总线(Audio Bus)
音频总线用于分组管理和处理音频。
常用总线架构
Master(主输出)
├── Music(背景音乐)
├── SFX(音效)
├── Voice(语音)
└── Environment(环境音)
# 代码创建自定义音频总线
func _ready():
AudioServer.add_bus()
AudioServer.set_bus_name(1, "Music")
AudioServer.set_bus_send(1, "Master")
AudioServer.add_bus()
AudioServer.set_bus_name(2, "SFX")
AudioServer.set_bus_send(2, "Master")
💡 提示:将音频分配到不同总线后,可以独立控制每组的音量、静音和音效处理。
音效处理
Godot 的音频总线支持插入各种音效处理插件。
常用音效类型
| 效果类型 | 说明 | 常见用途 |
|---|---|---|
AudioEffectReverb | 混响 | 洞穴、大厅空间感 |
AudioEffectDelay | 延迟/回声 | 山谷回声 |
AudioEffectChorus | 合唱 | 丰富音色 |
AudioEffectCompressor | 压缩器 | 平衡音量动态范围 |
AudioEffectLowPassFilter | 低通滤波器 | 水下效果、隔墙听声 |
# 代码添加音效
func _ready():
var reverb = AudioEffectReverb.new()
reverb.room_size = 0.6
reverb.damping = 0.5
reverb.wet = 0.3
AudioServer.add_bus_effect(AudioServer.get_bus_index("SFX"), reverb)
# 动态开关效果(进入洞穴时开启混响)
func enter_cave():
var bus_idx = AudioServer.get_bus_index("SFX")
AudioServer.set_bus_effect_enabled(bus_idx, 0, true)
func exit_cave():
var bus_idx = AudioServer.get_bus_index("SFX")
AudioServer.set_bus_effect_enabled(bus_idx, 0, false)
⚠️ 注意:音效处理会增加 CPU 开销,移动端建议谨慎使用复杂效果链。混响效果尤其消耗资源。
音量控制
# 使用 dB(分贝)控制音量
# 0 dB = 原始音量,-6 dB ≈ 原始音量的一半
$BGMPlayer.volume_db = linear2db(0.5) # 50% 音量
# 全局音量设置系统(建议设为 Autoload)
extends Node
func set_master_volume(linear: float):
AudioServer.set_bus_volume_db(0, linear2db(linear))
func set_music_volume(linear: float):
var bus_idx = AudioServer.get_bus_index("Music")
AudioServer.set_bus_volume_db(bus_idx, linear2db(linear))
func toggle_mute(bus_name: String):
var bus_idx = AudioServer.get_bus_index(bus_name)
AudioServer.set_bus_mute(bus_idx, not AudioServer.is_bus_mute(bus_idx))
💡 提示:UI 滑块通常使用 0~1 的线性值,而 Godot 内部使用 dB。使用 linear2db() 和 db2linear() 进行转换。
音频流导入设置
常见格式对比
| 格式 | 压缩方式 | 文件大小 | 适用场景 |
|---|---|---|---|
| WAV | 无压缩 | 大 | 短音效 |
| OGG | 有损压缩 | 小 | 背景音乐、长音频 |
| MP3 | 有损压缩 | 小 | 背景音乐 |
# 运行时设置音频流的循环
var stream = preload("res://audio/ambience_rain.ogg")
stream.loop = true
$AmbiencePlayer.stream = stream
$AmbiencePlayer.play()
⚠️ 注意:3D 空间音频的音源必须使用单声道音频,立体声无法正确进行空间化处理。
动态音乐系统
动态音乐系统可以根据游戏状态实时调整音乐,增强沉浸感。
分层音乐系统
extends Node
onready var layers = {
"calm": $MusicCalm,
"action": $MusicAction,
"danger": $MusicDanger
}
var target_volumes := {}
var fade_speed := 1.0
func _ready():
for name in layers:
layers[name].bus = "Music"
layers[name].play()
target_volumes[name] = 0.0
target_volumes["calm"] = 1.0
func _process(delta):
for name in layers:
var current = db2linear(layers[name].volume_db)
var new_vol = lerp(current, target_volumes[name], delta * fade_speed)
layers[name].volume_db = linear2db(new_vol)
func set_music_state(state: String):
for name in target_volumes:
target_volumes[name] = 0.0
if state in target_volumes:
target_volumes[state] = 1.0
# 使用:进入战斗时切换
func _on_EnemyDetected():
$MusicManager.set_music_state("danger")
环境音效
# 环境音效管理器
extends Node
var active_ambience := {}
func play_ambience(name: String, stream: AudioStream, fade_time: float = 2.0):
if name in active_ambience:
return
var player = AudioStreamPlayer.new()
player.stream = stream
player.bus = "Environment"
player.volume_db = -80.0
add_child(player)
player.play()
active_ambience[name] = player
var tween = Tween.new()
add_child(tween)
tween.interpolate_property(player, "volume_db", -80.0, 0.0, fade_time)
tween.start()
func stop_ambience(name: String, fade_time: float = 2.0):
if not name in active_ambience:
return
var player = active_ambience[name]
var tween = Tween.new()
add_child(tween)
tween.interpolate_property(player, "volume_db", player.volume_db, -80.0, fade_time)
tween.start()
yield(tween, "tween_all_completed")
player.queue_free()
active_ambience.erase(name)
音频与游戏事件联动
实现音效池
extends Node
export var pool_size: int = 10
var sfx_pool: Array = []
func _ready():
for i in pool_size:
var player = AudioStreamPlayer.new()
player.bus = "SFX"
add_child(player)
sfx_pool.append(player)
func play_sfx(stream: AudioStream, volume: float = 0.0, pitch_range: Vector2 = Vector2(0.9, 1.1)):
for player in sfx_pool:
if not player.playing:
player.stream = stream
player.volume_db = volume
player.pitch_scale = rand_range(pitch_range.x, pitch_range.y)
player.play()
return
# 使用示例
var hit_sound = preload("res://audio/hit.wav")
$SFXPool.play_sfx(hit_sound, 0.0, Vector2(0.8, 1.2))
💡 提示:随机化 pitch_scale 可以让同一音效每次播放时略有不同,避免重复感。这是专业游戏开发的常用技巧。
游戏开发场景
场景一:UI 按钮音效
extends Button
export var click_sound: AudioStream
export var hover_sound: AudioStream
func _ready():
connect("pressed", self, "_on_pressed")
connect("mouse_entered", self, "_on_mouse_entered")
func _on_pressed():
var player = AudioStreamPlayer.new()
player.stream = click_sound
player.bus = "UI"
add_child(player)
player.play()
yield(player, "finished")
player.queue_free()
func _on_mouse_entered():
$HoverPlayer.stream = hover_sound
$HoverPlayer.pitch_scale = rand_range(0.95, 1.05)
$HoverPlayer.play()
扩展阅读
💡 总结:合理的音频总线架构是良好音频系统的基础。建议至少分出 Music、SFX、Voice 三条总线。音效池和 Pitch 随机化是提升音效品质的两个简单有效技巧。