Tutorial 11 - Hello Audio

jMonkeyEngine 3 Tutorial (11) - Hello Audio

Anterior: Hello Terrain, Próximo: Hello Effects

Este tutorial explica como adicionar som 3D para um jogo, e como fazer sons tocarem juntos com eventos, tais como clicar. Você aprende a como usar um Ouvinte de Áudio (Audio Listener) e Nós de Áudio (Audio Nodes). Você também faz uso de um Ouvinte de Ação (Action Listener) e um MouseButtonTrigger do tutorial anterior Hello Input para fazer um clique do mouse disparar um som de tiro.

Para usar os ativos de exemplo em um novo projeto do SDK da jMonkeyEngine, dê um clique com o botão direito em seu projeto e selecione "Propriedades" ("Properties"), vá para "Bibliotecas" ("Libraries"), pressione "Adicionar Biblioteca" ("Add Library") e adicione a biblioteca "jme3-test-data".

Código de Amostra

package jme3test.helloworld;

import com.jme3.app.SimpleApplication;
import com.jme3.audio.AudioNode;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.MouseButtonTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;

/** Sample 11 - playing 3D audio. */
public class HelloAudio extends SimpleApplication {

  private AudioNode audio_gun;
  private AudioNode audio_nature;
  private Geometry player;

  public static void main(String[] args) {
    HelloAudio app = new HelloAudio();
    app.start();
  }

  @Override
  public void simpleInitApp() {
    flyCam.setMoveSpeed(40);

    /** just a blue box floating in space */
    Box box1 = new Box(Vector3f.ZERO, 1, 1, 1);
    player = new Geometry("Player", box1);
    Material mat1 = new Material(assetManager, 
            "Common/MatDefs/Misc/Unshaded.j3md");
    mat1.setColor("Color", ColorRGBA.Blue);
    player.setMaterial(mat1);
    rootNode.attachChild(player);

    /** custom init methods, see below */
    initKeys();
    initAudio();
  }

  /** We create two audio nodes. */
  private void initAudio() {
    /* gun shot sound is to be triggered by a mouse click. */
    audio_gun = new AudioNode(assetManager, "Sound/Effects/Gun.wav", false);
    audio_gun.setLooping(false);
    audio_gun.setVolume(2);
    rootNode.attachChild(audio_gun);

    /* nature sound - keeps playing in a loop. */
    audio_nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", false);
    audio_nature.setLooping(true);  // activate continuous playing
    audio_nature.setPositional(true);
    audio_nature.setLocalTranslation(Vector3f.ZERO.clone());
    audio_nature.setVolume(3);
    rootNode.attachChild(audio_nature);
    audio_nature.play(); // play continuously!
  }

  /** Declaring "Shoot" action, mapping it to a trigger (mouse click). */
  private void initKeys() {
    inputManager.addMapping("Shoot", new MouseButtonTrigger(0));
    inputManager.addListener(actionListener, "Shoot");
  }

  /** Defining the "Shoot" action: Play a gun sound. */
  private ActionListener actionListener = new ActionListener() {
    @Override
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("Shoot") && !keyPressed) {
        audio_gun.playInstance(); // play each instance once!
      }
    }
  };

  /** Move the listener with the a camera - for 3D audio. */
  @Override
  public void simpleUpdate(float tpf) {
    listener.setLocation(cam.getLocation());
    listener.setRotation(cam.getRotation());
  }

}

Quando você executar a amostra você deveria ver um cubo azul. Você deveria ouvir um som ambiente ao estilo da natureza. Quando você clicar, você ouve um disparo alto.

Entendendo a Amostra de Código

No método initSimpleApp(), você cria uma geometria de cubo simples chamada player e anexa ela para a cena – isto é apenas conteúdo da amostra arbitrário, então você vê alguma coisa quando executando a amostra de áudio.

Vamos dar uma olhada mais de perto em initAudio() para aprender a como usar AudioNodes.

AudioNodes

Adicionar som para seu jogo é muito simples: Salve seus arquivos de áudio no seu diretório assets/Sound. JME3 suporta os formatos de arquivo Ogg Vorbis (.ogg) e Wave (.wav).

Para cada som, você cria um AudioNode. Você pode usar um AudioNode como qualquer nó no grafo de cena JME, e.g. anexe ele para outros Nós (Nodes). Você cria um nó para um som de disparo de arma, e um nó para o som da natureza.

  private AudioNode audio_gun;
  private AudioNode audio_nature;

Olhe no método customizado initAudio(): Aqui você inicializa os objetos de som e configura seus parâmetros.

audio_gun = new AudioNode(assetManager, "Sound/Effects/Gun.wav", false);
    ...
audio_nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", false);

Estas duas linhas criam novos nós de som dos arquivos de áudio dados no AssetManager. A flag false significa que você quer bufferizar estes sons antes de tocar. (Se você configurar esta flag para true, o som será streamed, o que faz sentido para sons realmente longos.)

Você quer que o som do disparo de arma toque uma vez (você não quer que ele repita). Você também especifica seu volume como um fator de ganho (em 0, som é mudo, em 2, ele é duas vezes tão alto, etc.).

    audio_gun.setLooping(false);
    audio_gun.setVolume(2);
    rootNode.attachChild(audio_gun);

O som da natureza é diferente: Você quer repetí-lo continuamente como som de plano de fundo. Isto é porque você configura repetição para true, e imediatamente chama o método play() no nó. Você também escolhe configurar seu volume para 3.

    audio_nature.setLooping(true); // activate continuous playing
    ...
    audio_nature.setVolume(3);
    rootNode.attachChild(audio_nature);
    audio_nature.play(); // play continuously!
  }

Aqui você faz audio_nature um som posicional que vem de um certo lugar. Para isso você dá ao nó uma translação explícita, neste exemplo você escolhe Vector3f.ZERO (que representa as coordenadas 0.0f,0.0f,0.0f, o centro da cena.) Desde que jME suporta áudio 3D, você é agora capaz de ouvir o som vindo desta localização particular. Fazer o som posicional é opcional. Se você não usar estas linhas, o som ambiente vem de todas as direções.

    ...
    audio_nature.setPositional(true);
    audio_nature.setLocalTranslation(Vector3f.ZERO.clone());
    ...

Dica: Anexe AudioNodes dentro do grafo de cena como todos os nós, para fazer certos nós em movimento ficarem atualizados. Se você não anexá-los, eles ainda são audíveis e você não consegue uma mensagem de erro mas som 3D não funcionará como esperado. AudioNodes podem ser ligados diretamente ao nó raiz ou eles podem ser anexados dentro de um nó que está movendo através da cena e tanto o AudioNode quanto a posição 3d do som que ele está gerando se moverão de acordo.

Dica: playInstance sempre reproduz o som da posição do AudioNode então múltiplos disparos de uma arma (por exemplo) podem ser gerados desta maneira, entretanto se múltiplas armas estão atirando de uma vex então um AudioNode é necessário para cada.

Disparando Som

Vamos dar uma olhada mais de perto em initKeys(): Como você aprendeu nos tutoriais anteriores, você usa o inputManager para responder a entrada do usuário. Aqui você adiciona um mapeamento para um clique do botão esquerdo do mouse, e nomeia esta nova ação de Shoot (Disparar).

  /** Declaring "Shoot" action, mapping it to a trigger (mouse click). */
  private void initKeys() {
    inputManager.addMapping("Shoot", new MouseButtonTrigger(0));
    inputManager.addListener(actionListener, "Shoot");
  }

Configurar o ActionListener também deveria ser familiar dos tutoriais anteriores. Você declara que, qaundo o gatilho (o botão do mouse) é pressionado e liberado, você quer reproduzir um som de arma.

  /** Defining the "Shoot" action: Play a gun sound. */
  private ActionListener actionListener = new ActionListener() {
    @Override
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("Shoot") && !keyPressed) {
        audio_gun.playInstance(); // play each instance once!
      }
    }
  };

Desde que você quer ser capaz de disparar rápido repetidamente, então você não quer esperar para o som de disparo anterior terminar para que o próximo possa iniciar. Isto porque você reproduz este som usando o método playInstance(). Isto significa que todo clique inicia uma nova instância do som, então duas instâncias podem se sobrepor. Você configura este som para não repetir, para que cada instância somente toque uma vez. Como você esperaria de um disparo de arma.

Ambiente ou Situacional?

Os dois sons são dois diferentes casos de uso:

  • Um disparo é situacional. Você quer tocá-lo uma vez, imediatamente quando ele é disparado.
    • Isto é porque você setLooping(false).
  • O som da natureza é um ruído ambiente, de plano de fundo. Você quer que ele inicie tocando do início, tanto tempo quanto o jogo execute.
    • Isto é porque você setLooping(true).

Agora todo dom dabe se ele deveria repetir ou não.

Salvo o boolean de repetição, uma outra diferenã é onde play().playInstance() é chamado naqueles nós:

  • Você inicia a reprodução do som da natureza de plano de funo imediatamente após você ter o criado, no método initAudio().
        audio_nature.play(); // play continuously!

O som de disparo, entretanto, é disparado automaticamente, uma vez, somente como parte da entrada de ação Disparar (Shoot) que você definiu no ActionListener.

      /** Defining the "Shoot" action: Play a gun sound. */
      private ActionListener actionListener = new ActionListener() {
        @Override
        public void onAction(String name, boolean keyPressed, float tpf) {
          if (name.equals("Shoot") && !keyPressed) {
            audio_gun.playInstance(); // play each instance once!
          }
        }
      };

Bufferizado ou Streaming?

O Boolean no construdor AudioNode define se o áudio é bufferizado (false) ou streamed (true). Por exemplo:

audio_nature = new AudioNode(assetManager, "Sound/Effects/Gun.wav", false); // buffered
...
audio_nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", true); // streamed

Tipicamente você stream sons longos, e bufferiza sons curtos.

Note que sons streamed não podem repetir (i.e. setLooping não trabalhar como você espera). Cheque o getStatus no nó e se ele tem parado recrie o nó.

Play() or PlayInstance()?

audio.play() audio.playInstance()
Toca sons bufferizados; Toca sons bufferizados.
Toca sons streamed. Não pode tocar sons streamed.
O mesmo som não pode tocar duas vezes ao mesmo tempo. Os mesmos sons podem tocar múltiplas vezes e se sobrepor.

Seu Ouvido na Cena

Para criar um efeito de áudio 3D, JME3 precisa saber a posição da fonte de som, e a posição dos ouvidos do jogador. Os ouvidos são representados por um objeto Ouvinte de Áudio (Audio Listener) 3D. O objeto ouvinte é um objeto padrão em uma SimpleApplication.

Para extrair o máximo do efeito de áudio 3D, você deve usar o método simpleUpdate() para mover e rotacionar o ouvinte (os ouvidos do jogador) junto com a câmera (os olhos do jogador).

  public void simpleUpdate(float tpf) {
    listener.setLocation(cam.getLocation());
    listener.setRotation(cam.getRotation());
  }

Se você não fizer isso, os resultados do áudio 3D serão muito aleatórios.

Global, Direcional, Posicional?

Neste exemplo você definiu o som da natureza como vindo de uma certa posição, mas não o som do disparo. Isto sigmifica que seu disparo é global e pode ser ouvido em todo lugar com o mesmo volume. JME3 também suporta sons direcionais que você pode somente ouvir de uma certa direção.

Faz sentido fazer o som do disparo posicional, e deixar o som ambiente vir de todas as direções. Como você decide qual tipo de som 3D usar de caso a caso?

Em um jogo com inimigos se movendo você pode querer fazer os sons do disparo ou da arma posicionais. Nestes casos você deve mover o AudioNode para a localização do inimigo antes de playInstance()ing ele. Nesta maneira um jogador com alto-falantes estéreo ouve de qual direção o inimigo está vindo.
Similarmente, você pode ter níveis do jogo onde você quer que um som de plano de fundo toque globalmente. Nste caso você faria o AudioNode nem posicional nem drecional (coloque ambos para false).
Se você quer que o som seja "absorvido pelas paredes" e somente transmitido em uma direção, você faria este AudioNode direcional. Este tutorial não discute sons direcionais. Você pode ler sobre Áudio Avançado aqui.

Em resumo, você deve escolher em toda situação se faz sentido para um som ser global, direcional, ou posicional.

Conclusão

Você agora sabe como adicionar os dois tipos mais comuns de sons para seu jogo: Sons globais e sons posicionais. Você pode tocar sons em duas maneiras: Ou continuamente m um loop, ou situacionalmente apenas uma vez. Você sabe a diferença entre bufferizar sons curtos e streaming sons longos. Você sabe a difereça entre tocar instâncias de sons sobrepostos e tocar sons únicos que não podem se sobrepor com eles mesmos. Você também aprendeu a usar arquivos de som que estão em formato .ogg ou .wav.

Dica: A implementação de Áudio da JME também suporta efeitos mais avançados como o efeito Doppler. Use estas características "pro" para fazer o som do áudio diferente dependendo se ele está em um corredor, em uma caverna, nos outdoors, ou em uma sala com carpet. Descubra mais sobre efeitos ambientais da amostra de código inclusa no diretório jme3test e dos documentos de Áudio avançados.

Quer alguns fogos e explosões para acompanhar seus sons? Prossiga a leitura para aprender mais sobre efeitos.

Veja também:

  • Audio
Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-Share Alike 2.5 License.