Voltar para o Blog
Quest Log

Áudio 3D Posicional na Godot 4 com AudioStreamPlayer3D

Cena 3D estilizada com ondas de som saindo de um inimigo e chegando na câmera do jogador

Aprenda áudio 3D posicional na Godot 4 com AudioStreamPlayer3D: som que vem da fonte, volume que cai pela distância, pan pela direção e boas práticas.

Se você toca o som do inimigo num AudioStreamPlayer comum, ele soa igual esteja o bicho colado em você ou do outro lado do mapa. Falta a informação mais importante de um jogo 3D: de onde o som vem. É isso que o áudio 3D posicional na Godot 4 resolve. Com o nó certo, o passo do inimigo fica mais alto quando ele se aproxima, some pela esquerda quando ele passa correndo e desaparece quando fica longe. O jogador sente a posição sem olhar.

A ferramenta pra isso é o AudioStreamPlayer3D. Ele é um nó posicionado no espaço, e a engine calcula sozinha o volume e o balanço estéreo com base em onde ele está em relação aos ouvidos do jogador. Neste tutorial eu mostro por que usar esse nó em vez do comum, como posicioná-lo no objeto que emite o som, as propriedades que realmente importam, e três padrões práticos: som na posição de um inimigo, um player reaproveitável e tocar som num ponto qualquer do mundo. Tudo em GDScript tipado do Godot 4.x.

Por que usar AudioStreamPlayer3D em vez do comum

A Godot tem três nós de player de áudio, e trocar um pelo outro é a causa número um de "meu som 3D não funciona":

  • AudioStreamPlayer: sem posição. Toca igual nos dois canais. Perfeito pra música, narração e sons de interface.
  • AudioStreamPlayer2D: posicional no plano 2D, pan e volume pela posição na tela.
  • AudioStreamPlayer3D: posicional no espaço 3D. É o que você quer pra passos, tiros, máquinas, vozes de NPC e qualquer coisa que exista dentro do mundo.

A diferença prática do AudioStreamPlayer3D é que ele faz dois cálculos automáticos. O primeiro é a atenuação: o volume cai conforme a fonte se afasta do ouvinte. O segundo é o pan (balanço estéreo): o som pende pra esquerda ou pra direita conforme a direção da fonte. Você não escreve nada disso, só posiciona o nó e a engine faz o resto a cada frame.

O "ouvido" do jogador, por padrão, é a Camera3D ativa da cena. Ou seja, a posição e a rotação da câmera definem o que soa perto, longe, à esquerda ou à direita. Isso funciona direto em jogos em primeira pessoa. Em terceira pessoa, às vezes você quer que a escuta aconteça na posição do personagem, não da câmera lá atrás. Pra isso existe o nó AudioListener3D: adicione ele onde quiser ouvir e chame make_current().

# Faz este nó virar o ouvinte, ignorando a Camera3D
func _ready() -> void:
    var listener: AudioListener3D = $AudioListener3D
    listener.make_current()

Posicionando o nó: filho do objeto que emite o som

A regra de ouro do áudio 3D posicional na Godot 4 é simples: o AudioStreamPlayer3D tem que estar onde o som nasce. Como ele é um nó espacial (Node3D), a forma mais limpa é torná-lo filho do objeto que emite. Assim ele acompanha o movimento de graça, sem uma linha de código atualizando posição.

Uma árvore típica de inimigo fica assim:

Inimigo (CharacterBody3D)
├── MeshInstance3D
├── CollisionShape3D
└── FootstepPlayer (AudioStreamPlayer3D)

Com o FootstepPlayer como filho, quando o inimigo anda o player anda junto, e a atenuação e o pan se atualizam automaticamente. Tocar o passo é só chamar o método:

extends CharacterBody3D

@onready var footstep_player: AudioStreamPlayer3D = $FootstepPlayer

func play_footstep() -> void:
    # is_playing() evita cortar um passo pra recomeçar em cima do outro
    if not footstep_player.is_playing():
        footstep_player.play()

Os métodos são os mesmos do player comum: play() toca, stop() para, is_playing() diz se está tocando. O que muda é tudo que acontece por baixo por causa da posição.

As propriedades que importam

Selecione o AudioStreamPlayer3D no editor e você vê muita coisa no Inspector. Estas são as que fazem diferença no dia a dia, e dá pra setar todas por código:

var player: AudioStreamPlayer3D = $FootstepPlayer

player.stream = preload("res://audio/footstep.ogg")
player.unit_size = 4.0        # distância em que o volume começa a cair
player.max_db = 3.0           # volume máximo em decibéis
player.max_distance = 25.0    # além disso, silêncio total (0 = sem limite)
player.attenuation_model = AudioStreamPlayer3D.ATTENUATION_INVERSE_DISTANCE
player.panning_strength = 1.0 # intensidade do pan pela direção
player.bus = "SFX"            # roteia pro bus de efeitos

O unit_size é o que mais confunde. Ele não é o alcance máximo: é a distância dentro da qual o som ainda soa cheio antes de começar a atenuar. Escala pequena de cena pede unit_size pequeno, escala grande pede maior. Se seu som "some cedo demais", aumente ele antes de mexer em qualquer outra coisa.

O max_distance corta a fonte de vez a partir de certa distância. Manter isso num valor sensato (não 0, que é infinito) é ótimo pra performance: sons longe simplesmente param de ser processados.

O attenuation_model decide a curva de queda do volume:

  • ATTENUATION_INVERSE_DISTANCE: padrão, queda natural e equilibrada.
  • ATTENUATION_INVERSE_SQUARE_DISTANCE: cai mais rápido, deixa o som bem local.
  • ATTENUATION_LOGARITHMIC: queda mais suave, o som viaja mais longe.
  • ATTENUATION_DISABLED: volume fixo com a distância, mas ainda aplica o pan de direção.
Próximo nível
Quer aprender isso na prática?

No CursoGame.Dev você sai dos tutoriais soltos e constrói jogos publicáveis, com trilha progressiva, quests práticas e feedback real.

Conhecer a plataforma
+500 alunos4.9/5Garantia 7 dias

Cone direcional, filtro por distância e bus

Três propriedades a mais que valem conhecer quando você quer capricho.

O cone direcional faz o som ter frente, como um alto-falante ou a boca de um NPC. Ligue emission_angle_enabled e defina o ângulo:

player.emission_angle_enabled = true
player.emission_angle_degrees = 45.0  # metade da abertura do cone

Com isso, um som atrás da fonte chega mais abafado do que um som na frente dela. Bom pra falantes, alto-falantes de arena e máquinas com uma saída óbvia.

O attenuation_filter_cutoff_hz simula como sons distantes perdem os agudos (o mundo real abafa as frequências altas com a distância). Baixar esse valor deixa fontes longe soando mais "abafadas", o que reforça a sensação de distância.

Sobre roteamento: assim como no player comum, o AudioStreamPlayer3D tem a propriedade bus. Sempre mande seus efeitos pra um bus de SFX em vez de deixar tudo no Master, pra conseguir controlar volume, aplicar reverb e mutar por grupo. Se você ainda não tem essa estrutura montada, vale ler antes o guia de mixagem com audio bus, porque o áudio 3D encaixa direto nela.

Efeito Doppler

O efeito Doppler é aquela mudança de tom quando uma fonte passa por você em alta velocidade (a sirene da ambulância que fica aguda ao chegar e grave ao se afastar). A Godot faz isso automaticamente, mas vem desligado.

player.doppler_tracking = AudioStreamPlayer3D.DOPPLER_TRACKING_PHYSICS_STEP

Você tem DOPPLER_TRACKING_IDLE_STEP (atualiza no frame de renderização) e DOPPLER_TRACKING_PHYSICS_STEP (atualiza no passo de física, melhor pra objetos movidos pela física). Use com moderação: faz sentido em carros, projéteis e naves, e nada em passos ou cliques de menu.

Padrão 1: som na posição de um inimigo

O caso mais comum. O player mora dentro do inimigo e você dispara nos momentos certos. Note o area_mask, que decide de quais Area3D com efeito de áudio (reverb de sala, por exemplo) essa fonte é afetada.

extends CharacterBody3D

@onready var growl_player: AudioStreamPlayer3D = $GrowlPlayer

func _ready() -> void:
    growl_player.stream = preload("res://audio/growl.ogg")
    growl_player.unit_size = 6.0
    growl_player.max_distance = 30.0
    growl_player.bus = "SFX"
    growl_player.area_mask = 1

func alert() -> void:
    growl_player.play()

Como o player é filho do inimigo, se ele estiver à sua direita o rosnado sai à direita, e some conforme ele recua. Zero código de posição.

Padrão 2: um AudioStreamPlayer3D reaproveitável

Instanciar um nó novo a cada tiro é desperdício. Um padrão melhor é ter um player que você reposiciona e reusa. Dá pra generalizar isso num gerenciador de áudio (audio manager) que serve a cena inteira, mas o núcleo é este:

extends AudioStreamPlayer3D

# Move este player pra uma posição do mundo e toca um som
func play_at(world_position: Vector3, sound: AudioStream) -> void:
    global_position = world_position
    stream = sound
    play()

Chamando de qualquer lugar:

var sfx: AudioStreamPlayer3D = $ReusableSfxPlayer
sfx.play_at(target.global_position, preload("res://audio/hit.ogg"))

Como um player só toca um som por vez, para sons que se sobrepõem (vários tiros juntos) mantenha um pequeno pool de players e revese entre eles. Bem mais barato que criar e destruir nós no meio da ação.

Padrão 3: tocar som num ponto do mundo

Às vezes o objeto que emite o som já morreu no mesmo frame (uma explosão que destrói o inimigo). Se o player for filho dele, o som some junto. A solução é instanciar um player temporário no ponto, tocar e destruir quando acabar.

func play_sound_3d(sound: AudioStream, world_position: Vector3) -> void:
    var player: AudioStreamPlayer3D = AudioStreamPlayer3D.new()
    player.stream = sound
    player.unit_size = 5.0
    player.max_distance = 30.0
    player.bus = "SFX"
    add_child(player)
    player.global_position = world_position
    player.play()
    # Libera o nó assim que o som terminar
    player.finished.connect(player.queue_free)

O sinal finished dispara quando o stream chega ao fim, e aí o queue_free limpa o nó. É o padrão certo pra sons "dispare e esqueça" de eventos pontuais.

Boas práticas de performance

Áudio 3D é barato até deixar de ser. Alguns cuidados que evitam dor de cabeça:

  • Limite o número de sons simultâneos. Dezenas de AudioStreamPlayer3D tocando ao mesmo tempo custam CPU. Um pool com reuso resolve a maioria dos casos.
  • Defina max_distance sensato. Fontes além do limite param de ser processadas. Deixar em 0 (infinito) processa som que o jogador nem ouve.
  • Ajuste unit_size à escala da cena antes de sair mexendo em decibéis. Metade dos problemas de "som errado" é escala.
  • Use Doppler só onde importa (veículos, projéteis), não em tudo.
  • Roteie pra um bus de SFX pra controlar o grupo inteiro de uma vez.

Com esses três padrões e as propriedades certas, você cobre praticamente todo som que existe dentro do mundo do seu jogo. O próximo passo natural é integrar isso com o resto do áudio (música, UI, sliders de volume) num sistema central, e amarrar tudo à jogabilidade. Se você ainda está montando a base da sua cena tridimensional, comece pelo seu primeiro jogo 3D na Godot e depois volte pra dar vida ao mundo com som posicional.

Perguntas frequentes

Qual a diferença entre AudioStreamPlayer e AudioStreamPlayer3D?

O AudioStreamPlayer comum toca o som sem posição, igual nos dois alto-falantes (bom pra música e UI). O AudioStreamPlayer3D existe no espaço 3D: o volume cai com a distância e o pan muda com a direção da fonte em relação ao ouvinte.

O que a propriedade unit_size controla?

A unit_size define a distância (em unidades do mundo) em que o som ainda soa em volume cheio antes de começar a atenuar. Valor maior faz o som viajar mais longe antes de cair. Ajuste conforme a escala da sua cena.

Como o Godot sabe onde estão os ouvidos do jogador?

Por padrão a Camera3D ativa funciona como ouvinte. Se você quer separar a posição da escuta da câmera (jogo em terceira pessoa, por exemplo), adicione um nó AudioListener3D e chame make_current() nele.

Como escolho o modelo de atenuação certo?

ATTENUATION_INVERSE_DISTANCE é o padrão equilibrado. ATTENUATION_INVERSE_SQUARE_DISTANCE cai mais rápido (som fica local). ATTENUATION_LOGARITHMIC é mais suave. ATTENUATION_DISABLED mantém o volume fixo mas ainda aplica o pan de direção.

Áudio 3D pesa na performance?

Muitos AudioStreamPlayer3D tocando ao mesmo tempo custam CPU. Limite o número de sons simultâneos, use max_distance sensato pra cortar fontes longe e reaproveite players em vez de instanciar um por evento.