第15章 音频系统
15.1 AudioStreamPlayer 基础
# AudioStreamPlayer:非位置音频(BGM、UI音效)
# AudioStreamPlayer2D:2D 位置音频(距离衰减)
# AudioStreamPlayer3D:3D 位置音频
extends AudioStreamPlayer
func _ready() -> void:
stream = preload("res://audio/bgm/battle_theme.ogg")
volume_db = -10 # 音量(分贝)
pitch_scale = 1.0
autoplay = false
bus = "Music" # 音频总线
func play_bgm() -> void:
if not playing:
play()
func stop_bgm() -> void:
if playing:
# 淡出
var tween := create_tween()
tween.tween_property(self, "volume_db", -40.0, 1.0)
await tween.finished
stop()
volume_db = -10 # 恢复音量
15.2 AudioStreamRandomizer(随机音效变体)
# AudioStreamRandomizer 自动在多个音效中随机选择
# 避免重复播放同一音效产生机械感
# 设置:
# 1. 创建 AudioStreamRandomizer 资源
# 2. 添加多个 AudioStream(如不同的脚步声)
# 3. 设置 Playback Mode: Random (No Repeat)
# 4. 设置 Random Pitch: 0.9 ~ 1.1(音高随机变化)
# 代码使用
extends AudioStreamPlayer2D
func play_footstep() -> void:
stream = preload("res://audio/sfx/footstep_randomizer.tres")
pitch_scale = randf_range(0.9, 1.1) # 额外随机
play()
15.3 音频总线(Audio Bus)
# 音频总线用于分组控制音量和效果
# 设置:底部面板 → Audio → Audio Bus
# 推荐总线布局:
# Bus 0: Master(主音量)
# Bus 1: Music(背景音乐)
# Bus 2: SFX(音效)
# Bus 3: Voice(语音)
# Bus 4: Ambient(环境音)
# 代码控制
func set_music_volume(volume_db: float) -> void:
var bus_idx := AudioServer.get_bus_index("Music")
AudioServer.set_bus_volume_db(bus_idx, volume_db)
func set_sfx_mute(muted: bool) -> void:
var bus_idx := AudioServer.get_bus_index("SFX")
AudioServer.set_bus_mute(bus_idx, muted)
# 添加音频效果
func add_reverb_to_sfx() -> void:
var bus_idx := AudioServer.get_bus_index("SFX")
var reverb := AudioEffectReverb.new()
reverb.room_size = 0.8
reverb.damping = 0.5
AudioServer.add_bus_effect(bus_idx, reverb)
15.4 位置音频(AudioStreamPlayer2D)
extends AudioStreamPlayer2D
func _ready() -> void:
# 距离衰减设置
max_distance = 500.0 # 超过这个距离听不到
# attenuation 属性在 Godot 4.x 中通过衰减曲线控制
# 默认使用反距离衰减
# 爆炸音效(在特定位置播放)
func play_explosion_sound(pos: Vector2) -> void:
var sound := AudioStreamPlayer2D.new()
sound.stream = preload("res://audio/sfx/explosion.wav")
sound.global_position = pos
sound.max_distance = 800.0
add_child(sound)
sound.play()
sound.finished.connect(sound.queue_free)
15.5 动态音乐系统
extends Node
@onready var exploration_player: AudioStreamPlayer = $ExplorationMusic
@onready var combat_player: AudioStreamPlayer = $CombatMusic
var is_in_combat: bool = false
var crossfade_duration: float = 2.0
func enter_combat() -> void:
if is_in_combat:
return
is_in_combat = true
# 交叉渐变
combat_player.volume_db = -40
combat_player.play()
var tween := create_tween().set_parallel(true)
tween.tween_property(exploration_player, "volume_db", -40, crossfade_duration)
tween.tween_property(combat_player, "volume_db", 0, crossfade_duration)
func exit_combat() -> void:
if not is_in_combat:
return
is_in_combat = false
var tween := create_tween().set_parallel(true)
tween.tween_property(combat_player, "volume_db", -40, crossfade_duration)
tween.tween_property(exploration_player, "volume_db", 0, crossfade_duration)
await tween.finished
combat_player.stop()
15.6 对话语音
extends RichTextLabel
@export var voice_stream: AudioStreamPlayer
var is_typing: bool = false
var typing_speed: float = 0.03 # 每个字符的时间
func show_dialogue(text: String, voice: AudioStream = null) -> void:
visible_characters = 0
self.text = text
is_typing = true
if voice and voice_stream:
voice_stream.stream = voice
voice_stream.play()
for i in text.length():
if not is_typing:
break
visible_characters = i + 1
await get_tree().create_timer(typing_speed).timeout
visible_characters = -1 # 显示全部
is_typing = false
func skip_typing() -> void:
is_typing = false
visible_characters = -1