Tutorial 7 - Hello Animation

jMonkeyEngine 3 Tutorial (7) - Hello Animation

Anterior: Hello Material, Próximo: Hello Picking

Este tutorial mostra como adicionar um controlador de animação e canais, e como responder a entrada do usuário por disparar uma animação em um modelo carregado.

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, selecione "Propriedades" ("Properties"), vá para "Bibliotecas" ("Libraries"), pressione "Adicionar Biblioteca" ("Add Library") e adicione a biblioteca "jme3-test-data".

Código da Amostra

package jme3test.helloworld;

import com.jme3.animation.AnimChannel;
import com.jme3.animation.AnimControl;
import com.jme3.animation.AnimEventListener;
import com.jme3.animation.LoopMode;
import com.jme3.app.SimpleApplication;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.DirectionalLight;
import com.jme3.math.ColorRGBA;
import com.jme3.math.Vector3f;
import com.jme3.scene.Node;

/** Sample 7 - how to load an OgreXML model and play an animation,
 * using channels, a controller, and an AnimEventListener. */
public class HelloAnimation extends SimpleApplication
  implements AnimEventListener {
  private AnimChannel channel;
  private AnimControl control;
  Node player;
  public static void main(String[] args) {
    HelloAnimation app = new HelloAnimation();
    app.start();
  }

  @Override
  public void simpleInitApp() {
    viewPort.setBackgroundColor(ColorRGBA.LightGray);
    initKeys();
    DirectionalLight dl = new DirectionalLight();
    dl.setDirection(new Vector3f(-0.1f, -1f, -1).normalizeLocal());
    rootNode.addLight(dl);
    player = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml");
    player.setLocalScale(0.5f);
    rootNode.attachChild(player);
    control = player.getControl(AnimControl.class);
    control.addListener(this);
    channel = control.createChannel();
    channel.setAnim("stand");
  }

  public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) {
    if (animName.equals("Walk")) {
      channel.setAnim("stand", 0.50f);
      channel.setLoopMode(LoopMode.DontLoop);
      channel.setSpeed(1f);
    }
  }

  public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
    // unused
  }

  /** Custom Keybinding: Map named actions to inputs. */
  private void initKeys() {
    inputManager.addMapping("Walk", new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addListener(actionListener, "Walk");
  }
  private ActionListener actionListener = new ActionListener() {
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("Walk") && !keyPressed) {
        if (!channel.getAnimationName().equals("Walk")) {
          channel.setAnim("Walk", 0.50f);
          channel.setLoopMode(LoopMode.Loop);
        }
      }
    }
  };
}

Criando e Carregando Modelos Animados

Você cria modelos animados com uma ferramenta tal como o Blender. Tome algum tempo e aprenda a como criar seus próprios modelos nestes Tutoriais de Animação do Blender. Por enquanto, baixe e use um modelo gratuito, tal como o incluso aqui como exemplo (Oto Golem, and Ninja).

Carregar um modelo animado é muito simples, como você aprendeu nos capítulos anteriores. Modelos Ogre Animados vêm com um conjunto de arquivos: O modelo está em Oto.mesh.xml, e os detalhes de animação estão em Oto.skeleton.xml, mais os arquivos usuais para materiais e texturas. Cheque que todos os arquivos estão juntos no mesmo subdiretório Model.

  public void simpleInitApp() {
    /* Displaying the model requires a light source */
    DirectionalLight dl = new DirectionalLight();
    dl.setDirection(new Vector3f(-0.1f, -1f, -1).normalizeLocal());
    rootNode.addLight(dl);
    /* load and attach the model as usual */
    player = assetManager.loadModel("Models/Oto/Oto.mesh.xml");
    player.setLocalScale(0.5f); // resize
    rootNode.attachChild(player);
    ...
    }

Não esqueça de adicionar uma fonte de luz para fazer o material visível.

Controlador de Animação e Canal

Depois de carregar o modelo animado, você o registra para o Controlador de Animação (Animation Controller).

  • o objeto controlador dá a você acesso às sequências de animação disponíveis.
  • O controlador pode ter vários canais, cada canal pode executar uma sequência de animação por vez.
  • Para executar várias sequências, você cria vários canais, e configura cada um deles para a animação deles.
  private AnimChannel channel;
  private AnimControl control;

  public void simpleInitApp() {
    ...
    /* Load the animation controls, listen to animation events,
     * create an animation channel, and bring the model in its default position.  */
    control = player.getControl(AnimControl.class);
    control.addListener(this);
    channel = control.createChannel();
    channel.setAnim("stand");
    ...

Em resposta a uma questão sobre animações em diferentes canais interferindo uma com a outra, , Nehon no fórum da jME escreveu,

"Você tem de considerar canais como parte do esqueleto que é animado. O comportamento padrão é o uso do esqueleto inteiro para um canal. No seu exemplo o primeiro canal toca a animação de caminhar, então o segundo canal toca a animação de se esquivar. Braços e pés não são provavelmente afetados pela animação de aresta então você pode ver a animação de caminhar para eles, mas o teso do cor reproduz a animação de se esquivar.

Normalmente múltiplos canais são usados para animar diferentes partes do corpo. Por exemplo, você cria um canal para a parte inferior do corpo e um para a parte superior. Isto permite você reproduzir uma animação de caminhar com a parte inferior e, por exemplo, uma animação de atirar com a parte superior. Desta maneira seu personagem pode caminhar enquanto atirando.

No seu caso, onde você quer animações encadearem o esqueleto inteiro, você tem de usar um canal."

Respondendo para Eventos de Animação

Adicione implements AnimEventListener para a declaração de classe. Esta interface dá a você acesso aos eventos que notificam quando uma sequência termina, ou quando você muda de uma sequência para outra, então você pode responder para isso. Neste exemplo, você reinicia o personagem para uma posição de permanecer em pé depois que um ciclo de Caminhar (Walk) termina.

public class HelloAnimation extends SimpleApplication
                         implements AnimEventListener {
  ...

  public void onAnimCycleDone(AnimControl control, 
                              AnimChannel channel, String animName) {
    if (animName.equals("Walk")) {
      channel.setAnim("stand", 0.50f);
      channel.setLoopMode(LoopMode.DontLoop);
      channel.setSpeed(1f);
    }
  }
  public void onAnimChange(AnimControl control, AnimChannel channel, String animName) {
    // não usado
  }

Dispare Animações Depois da Entrada do Usuário

Há animações ambiente como animais ou árvores que você quer ativar no loop de evento principal. Em outros casos, animações são ativadas por interação do usuário, como por exemplo uma entrada advinda de uma tecla. Você quer reproduzir a animação de Caminhar (Walk) quando o jogador pressiona uma tecla determinada 9aqui a barra de espaço), ao mesmo tempo que o avatar realiza a ação de caminhar e muda sua localização.

  • Inicialize um novo controlador de entrada (em simpleInitApp()).
    • Escreva o método de conveniência initKey() e chame ele de simpleInitApp().
  • Adicione um mapeamento de tecla com o nome da ação que você quer ativar.
    • Aqui, por exemplo, você mapeia Caminhar (Walk) para a tecla Barra de Espaço.
  • Adicione um ouvinte de entrada (input listener) para a ação Caminhar (Walk).
  private void initKeys() {
    inputManager.addMapping("Walk", new KeyTrigger(KeyInput.KEY_SPACE));
    inputManager.addListener(actionListener, "Walk");
  }

Para usar o controlador de entrada, você precisa implementar o ouvinte de ação (actionListener): Teste por cada ação pelo nome, e configure o canal para que a animação correspondente execute.

  • O segundo parâmetro de setAnim() é o tempo de mistura (blendTime) (quanto tempo a animação atual deveria se sobrepor com a última).
  • Modo de Repetição (LoopMode) pode ser Loop (repetir), Ciclo (Cycle) (para frente e então para trás), e Não repita (DontLoop) (somente uma vez).
  • Se necessário use channel.setSpeed() para configurar a velocidade desta animação.
  • Opcionalmente use channel.setTime() para Avançar rápido (Fast-forward) ou retroceder (rewind) para um certomomento no tempo desta animação.
  private ActionListener actionListener = new ActionListener() {
    public void onAction(String name, boolean keyPressed, float tpf) {
        if (name.equals("Walk") && !keyPressed) {
            if (!channel.getAnimationName().equals("Walk")){
                channel.setAnim("Walk", 0.50f);
                channel.setLoopMode(LoopMode.Cycle);
            }
        }
    }
  };

Exercícios

Exercício 1: Duas Animações

Faça um clique de mouse disparar uma outra sequência de animação!

  • Crie um segundo canal no controlador
  • Crie um novo mapeamento de disparo de tecla e ação (veja: Hello Input)
  • Dica: Você quer descobrir que sequências de animação estão disponíveis no modelo? Use:
    for (String anim : control.getAnimationNames()) { System.out.println(anim); }

Exercício 2: Revelando o Esqueleto (Skeleton) (1)

Abra o arquivo skeleton.xml em um editor de texto de sua escolha. Você não tem de ser capaz de ler ou escrever estes arquivos xml (Blender faz isto por você) – mas é bom saber como esqueletos funcionam. "Não há mágica para isso!"

  • Note como os ossos são numerados e nomeados. Todos os nomes de um modelo aniamdo seguem um esquema de nomeação.
  • Note que a hierarquia de osso especifica como os ossos são conectados.
  • Note a lista de animações: Cada animação tem um nome, e várias trilhas. Cada trilha diz a ossos individuais como e quando transformar. Estes passos de animação são chamados quadros-chave.

Exercício 3: Revelando o Esqueleto (Skeleton) (2)

Adicione as seguintes declarações import para as classes SkeletonDebugger e Material:

     import com.jme3.scene.debug.SkeletonDebugger;
     import com.jme3.material.Material;

Adicione o seguinte trecho de código para simpleInitApp() para fazer os ossos (que você acabou de ler) visíveis!

     SkeletonDebugger skeletonDebug = 
         new SkeletonDebugger("skeleton", control.getSkeleton());
     Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
     mat.setColor("Color", ColorRGBA.Green);
     mat.getAdditionalRenderState().setDepthTest(false);
     skeletonDebug.setMaterial(mat);
     player.attachChild(skeletonDebug);

Você pode identificar ossos individuais no esqueleto?

Conclusão

Agora você pode carregar modelos animados, identificar animações armazenadas, e disparar animações por usar onAnimCycleDone() e onAnimChange(). Você também aprendeu que você pode reproduzir várias animações simultaneamente, por iniciar cada em um canal de si própria. Isto poderia ser útil se você mesmo quer animar as partes superioras e inferioras dos personagens independentemente, por exemplo, as pernas correrem, enquanto os braços usam uma arma.

Agora que seu personagem pode caminhar, não seria legal se ele também pudesse pegar coisas, ou mirasse uma arma em coisas, ou abrisse portas? Tempo para revelar os secretos de pega com o mouse!

Veja também: Ctiando modelos OgreXML animados no Blender

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