Visão geral: SFX 2D vs música
Em um jogo 2D, normalmente separamos o áudio em duas categorias:
- SFX (efeitos sonoros): sons curtos e reativos (pulo, ataque, dano). Em geral, fazem sentido no espaço do jogo, então usamos
AudioStreamPlayer2Dpara ter posicionamento (pan/atenuação) conforme a câmera. - Música: trilha contínua e não-posicional (geralmente). Usamos
AudioStreamPlayer(sem 2D) para tocar em volume constante.
O objetivo aqui é: adicionar SFX de pulo/dano/ataque, configurar música, organizar tudo em buses (mixagem), criar controles de volume/mute na UI e persistir as configurações.
Preparando a mixagem: Audio Buses (Master, Music, SFX)
Criando buses no Audio Bus Layout
Abra o painel de áudio (Audio) e edite o Audio Bus Layout. Crie esta estrutura:
- Master (já existe)
- Music (saída para Master)
- SFX (saída para Master)
Assim, você controla música e efeitos separadamente, sem mexer no Master o tempo todo.
Atribuindo bus aos players
Cada player tem a propriedade bus. Regras práticas:
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
- Música:
bus = "Music" - SFX:
bus = "SFX"
Isso permite que sliders e mute atuem por categoria.
Adicionando música com AudioStreamPlayer (global)
Estrutura recomendada
Crie um nó dedicado para música (por exemplo, em uma cena global/gerenciador, ou na cena principal do jogo):
AudioStreamPlayerchamadoMusicPlayer
Configure no Inspector:
stream: selecione o arquivo de música (OGG/WAV)bus:Musicautoplay: opcional (você pode iniciar via código)loop: se o stream suportar loop (ou configure no recurso)
Play/stop e troca de música
Exemplo de script simples para controlar a música:
extends Node
@onready var music_player: AudioStreamPlayer = $MusicPlayer
func play_music(stream: AudioStream, from_position := 0.0) -> void:
if music_player.stream == stream and music_player.playing:
return
music_player.stream = stream
music_player.play(from_position)
func stop_music() -> void:
music_player.stop()Boa prática: evitar reiniciar a mesma música se ela já estiver tocando.
SFX com AudioStreamPlayer2D: pulo, dano e ataque
Quando usar AudioStreamPlayer2D
Use AudioStreamPlayer2D quando o som deve “vir” do personagem/inimigo no mundo. Ele aplica:
- Pan (esquerda/direita) conforme posição relativa à câmera
- Atenuação com distância (se configurada)
Para SFX do jogador (pulo/ataque/dano), normalmente faz sentido ser 2D.
Passo a passo: SFX no Player
No nó do jogador, adicione três players 2D (ou um sistema centralizado; aqui vamos pelo caminho direto e didático):
AudioStreamPlayer2DchamadoSfxJumpAudioStreamPlayer2DchamadoSfxAttackAudioStreamPlayer2DchamadoSfxHurt
No Inspector de cada um:
stream: atribua o áudio correspondentebus:SFXvolume_db: ajuste fino (ex.: -6 dB para não estourar)
No script do player, referencie e toque nos momentos corretos (exemplos de chamadas; adapte aos seus sinais/eventos):
@onready var sfx_jump: AudioStreamPlayer2D = $SfxJump
@onready var sfx_attack: AudioStreamPlayer2D = $SfxAttack
@onready var sfx_hurt: AudioStreamPlayer2D = $SfxHurt
func play_jump_sfx() -> void:
_safe_play(sfx_jump)
func play_attack_sfx() -> void:
_safe_play(sfx_attack)
func play_hurt_sfx() -> void:
_safe_play(sfx_hurt)Boas práticas essenciais para SFX
1) Evitar múltiplas instâncias simultâneas do mesmo som
Um erro comum é disparar o mesmo SFX várias vezes no mesmo frame (ou em sequência rápida), criando “embolado” e clipping. Para sons que não devem sobrepor (ex.: pulo), prefira reiniciar ou ignorar se já estiver tocando.
Crie uma função utilitária:
func _safe_play(player: AudioStreamPlayer) -> void:
# Para sons curtos que não devem empilhar
if player.playing:
player.stop()
player.play()Se você quiser permitir sobreposição em alguns casos (ex.: tiros rápidos), uma alternativa é usar um pequeno “pool” de players (várias instâncias) e tocar no primeiro livre. Para este capítulo, manteremos o controle simples por som.
2) Pitch aleatório leve para variar repetição
Repetição idêntica de SFX (principalmente ataque) pode cansar. Uma variação pequena de pitch dá naturalidade sem distorcer demais.
func _safe_play_with_pitch(player: AudioStreamPlayer, min_pitch := 0.95, max_pitch := 1.05) -> void:
player.pitch_scale = randf_range(min_pitch, max_pitch)
if player.playing:
player.stop()
player.play()Use com moderação (faixa estreita). Para dano, às vezes é melhor manter pitch fixo para consistência.
3) Pré-carregamento (preload) quando necessário
Se você carrega streams dinamicamente durante o jogo, pode haver micro travadas. Para SFX usados com frequência, prefira pré-carregar:
const SFX_JUMP: AudioStream = preload("res://audio/sfx/jump.ogg")
const SFX_ATTACK: AudioStream = preload("res://audio/sfx/attack.ogg")
const SFX_HURT: AudioStream = preload("res://audio/sfx/hurt.ogg")
func _ready() -> void:
sfx_jump.stream = SFX_JUMP
sfx_attack.stream = SFX_ATTACK
sfx_hurt.stream = SFX_HURTPara música, também é comum pré-carregar as faixas do menu/fase se forem poucas. Se forem muitas, carregue sob demanda, mas evite trocar em momentos críticos.
Controle de volume e mute via UI (sliders e botões)
Como volume funciona: dB vs linear
Na Godot, o volume do bus é em decibéis (dB). Sliders geralmente trabalham melhor em escala linear (0.0 a 1.0). Então precisamos converter:
linear_to_db(0.0)tende a -inf (silêncio)linear_to_db(1.0)é 0 dB (volume original)
Vamos usar as funções embutidas linear_to_db e db_to_linear.
Passo a passo: UI de áudio no menu de opções
Na sua cena de opções, crie (nomes sugeridos):
HSliderMusicSlider(min 0, max 1, step 0.01)HSliderSfxSlider(min 0, max 1, step 0.01)CheckBoxouButtontoggleMusicMuteCheckBoxouButtontoggleSfxMute
Conecte os sinais:
value_changed(value)dos sliderstoggled(button_pressed)dos mutes
Aplicando volume/mute nos buses
Use AudioServer para controlar buses:
extends Control
@onready var music_slider: HSlider = $MusicSlider
@onready var sfx_slider: HSlider = $SfxSlider
@onready var music_mute: BaseButton = $MusicMute
@onready var sfx_mute: BaseButton = $SfxMute
var bus_music := AudioServer.get_bus_index("Music")
var bus_sfx := AudioServer.get_bus_index("SFX")
func _ready() -> void:
# Inicializa UI a partir do estado atual dos buses
music_slider.value = db_to_linear(AudioServer.get_bus_volume_db(bus_music))
sfx_slider.value = db_to_linear(AudioServer.get_bus_volume_db(bus_sfx))
music_mute.button_pressed = AudioServer.is_bus_mute(bus_music)
sfx_mute.button_pressed = AudioServer.is_bus_mute(bus_sfx)
func _on_MusicSlider_value_changed(value: float) -> void:
AudioServer.set_bus_volume_db(bus_music, linear_to_db(value))
func _on_SfxSlider_value_changed(value: float) -> void:
AudioServer.set_bus_volume_db(bus_sfx, linear_to_db(value))
func _on_MusicMute_toggled(pressed: bool) -> void:
AudioServer.set_bus_mute(bus_music, pressed)
func _on_SfxMute_toggled(pressed: bool) -> void:
AudioServer.set_bus_mute(bus_sfx, pressed)Dica: se você quiser que “mute” também mova o slider para 0 (ou vice-versa), faça isso explicitamente para não criar comportamento confuso.
Persistindo configurações de áudio (volumes e mute)
Estrutura de dados recomendada
Vamos salvar em user://settings.cfg usando ConfigFile. Campos típicos:
audio/music_volume(0..1)audio/sfx_volume(0..1)audio/music_mute(bool)audio/sfx_mute(bool)
Script de Settings (autoload) para centralizar
Crie um script (ex.: Settings.gd) e adicione como Autoload (singleton) para ficar acessível em qualquer cena.
extends Node
const PATH := "user://settings.cfg"
var music_volume := 1.0
var sfx_volume := 1.0
var music_mute := false
var sfx_mute := false
func load_settings() -> void:
var cfg := ConfigFile.new()
var err := cfg.load(PATH)
if err == OK:
music_volume = float(cfg.get_value("audio", "music_volume", music_volume))
sfx_volume = float(cfg.get_value("audio", "sfx_volume", sfx_volume))
music_mute = bool(cfg.get_value("audio", "music_mute", music_mute))
sfx_mute = bool(cfg.get_value("audio", "sfx_mute", sfx_mute))
apply_audio()
func save_settings() -> void:
var cfg := ConfigFile.new()
cfg.set_value("audio", "music_volume", music_volume)
cfg.set_value("audio", "sfx_volume", sfx_volume)
cfg.set_value("audio", "music_mute", music_mute)
cfg.set_value("audio", "sfx_mute", sfx_mute)
cfg.save(PATH)
func apply_audio() -> void:
var bus_music := AudioServer.get_bus_index("Music")
var bus_sfx := AudioServer.get_bus_index("SFX")
AudioServer.set_bus_volume_db(bus_music, linear_to_db(music_volume))
AudioServer.set_bus_volume_db(bus_sfx, linear_to_db(sfx_volume))
AudioServer.set_bus_mute(bus_music, music_mute)
AudioServer.set_bus_mute(bus_sfx, sfx_mute)Carregando no início do jogo
No _ready() do autoload (ou em um nó inicial do projeto), chame:
func _ready() -> void:
load_settings()Assim, o áudio já inicia com os volumes corretos antes de tocar música/SFX.
Integrando a UI com o Settings e salvando
No script da tela de opções, em vez de ler/escrever direto no AudioServer, atualize o singleton e aplique:
func _ready() -> void:
# Garante que a UI reflita o que está salvo
music_slider.value = Settings.music_volume
sfx_slider.value = Settings.sfx_volume
music_mute.button_pressed = Settings.music_mute
sfx_mute.button_pressed = Settings.sfx_mute
func _on_MusicSlider_value_changed(value: float) -> void:
Settings.music_volume = value
Settings.apply_audio()
Settings.save_settings()
func _on_SfxSlider_value_changed(value: float) -> void:
Settings.sfx_volume = value
Settings.apply_audio()
Settings.save_settings()
func _on_MusicMute_toggled(pressed: bool) -> void:
Settings.music_mute = pressed
Settings.apply_audio()
Settings.save_settings()
func _on_SfxMute_toggled(pressed: bool) -> void:
Settings.sfx_mute = pressed
Settings.apply_audio()
Settings.save_settings()Se você preferir não salvar a cada mudança (para reduzir escrita em disco), salve ao sair do menu (botão “Voltar/Aplicar”). A lógica é a mesma: atualizar variáveis, aplicar áudio e salvar uma vez.
Checklist rápido de mixagem e implementação
| Item | O que verificar |
|---|---|
| Buses | Existem Music e SFX roteados para Master |
| Players | Música em AudioStreamPlayer (bus Music); SFX em AudioStreamPlayer2D (bus SFX) |
| Volumes | Use slider 0..1 e converta com linear_to_db |
| Mute | Use AudioServer.set_bus_mute por bus |
| Repetição | Evite empilhar o mesmo SFX: stop() antes de play() quando fizer sentido |
| Variedade | Use pitch_scale com variação leve em SFX repetitivos |
| Persistência | Salvar/carregar com ConfigFile em user:// e aplicar no início |