Tutorial 5 - Hello Input System

jMonkeyEngine 3 Tutorial (5) - Hello Input System

Anteior: Hello Update Loop, Próximo: Hello Material

por padrão, SimpleApplication configura um controle de câmera que permite a você dirigir a camâmera com as teclas WASD, as teclas de seta, e o mouse. Você pode usá-lo cini uma câmera voadora de primeira pessoa imediatamente. Mas e se você precisa de uma câmera de terceira pessoa, ou você deseja que teclas disparem ações de jogo especiais?

Todo jogo tem suas ligações de tecla personalizadas, e este tutorial explica como definí-las. Nós primeiro definimos os pressionamentos de tecla e eventos de mouse, e então nós definimos as ações que elas deveriam disparar.

Código de Amostra

package jme3test.helloworld;

import com.jme3.app.SimpleApplication;
import com.jme3.material.Material;
import com.jme3.math.Vector3f;
import com.jme3.scene.Geometry;
import com.jme3.scene.shape.Box;
import com.jme3.math.ColorRGBA;
import com.jme3.input.KeyInput;
import com.jme3.input.MouseInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger;

/** Sample 5 - how to map keys and mousebuttons to actions */
public class HelloInput extends SimpleApplication {

  public static void main(String[] args) {
    HelloInput app = new HelloInput();
    app.start();
  }
  protected Geometry player;
  Boolean isRunning=true;

  @Override
  public void simpleInitApp() {
    Box b = new Box(Vector3f.ZERO, 1, 1, 1);
    player = new Geometry("Player", b);
    Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
    mat.setColor("Color", ColorRGBA.Blue);
    player.setMaterial(mat);
    rootNode.attachChild(player);
    initKeys(); // load my custom keybinding
  }

  /** Custom Keybinding: Map named actions to inputs. */
  private void initKeys() {
    // You can map one or several inputs to one named action
    inputManager.addMapping("Pause",  new KeyTrigger(KeyInput.KEY_P));
    inputManager.addMapping("Left",   new KeyTrigger(KeyInput.KEY_J));
    inputManager.addMapping("Right",  new KeyTrigger(KeyInput.KEY_K));
    inputManager.addMapping("Rotate", new KeyTrigger(KeyInput.KEY_SPACE),
                                      new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
    // Add the names to the action listener.
    inputManager.addListener(actionListener, new String[]{"Pause"});
    inputManager.addListener(analogListener, new String[]{"Left", "Right", "Rotate"});

  }

  private ActionListener actionListener = new ActionListener() {
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("Pause") && !keyPressed) {
        isRunning = !isRunning;
      }
    }
  };

  private AnalogListener analogListener = new AnalogListener() {
    public void onAnalog(String name, float value, float tpf) {
      if (isRunning) {
        if (name.equals("Rotate")) {
          player.rotate(0, value*speed, 0);
        }
        if (name.equals("Right")) {
          Vector3f v = player.getLocalTranslation();
          player.setLocalTranslation(v.x + value*speed, v.y, v.z);
        }
        if (name.equals("Left")) {
          Vector3f v = player.getLocalTranslation();
          player.setLocalTranslation(v.x - value*speed, v.y, v.z);
        }
      } else {
        System.out.println("Press P to unpause.");
      }
    }
  };
}

Construa e execute a amostra.

  • Pressiona a barra de espaço ou clique para rotacionar o cubo
  • Pressione as teclas J e K para mover o cubo.
  • Pressione P para pausar e despausar o jogo. Enquanto pausado, o jogo não deveria responder a qualquer entrada, diferente de P.

Definindo Mapeamentos e Gatilhos

Primeiro você registra cada nome de mapeamento com seu(s) gatilho(s). Lembre-se do seguinte:

  • Um gatilho de ação pode ser um ação de mouse ou um pressionamento de tecla.
  • Por exemplo, um movimento de mouse, um clique do mouse, ou pressionando a letra "P".
  • O nome do mapeamento é uma string que você pode escolher.
  • O nome deveria descrever a ação (e.g. "Rotacionar"), e não o gatilho. Porque o gatilho pode mudar.
  • Um mapeamento nomeado pode ter vários gatilhos.
  • Por exemplo, a ação "Rotacionar" ("Rotate") pode ser disparada por um clique e por pressionar a barra de espaço.

Dê uma olhada no código

  • Você registra o mapeamento nomeado "Rotacionar" ("Rotate") para a tecla de gatilho barra de espaço.
    new KeyTrigger(KeyInput.KEY_SPACE)).
  • Na mesma linha, você também registra "Rotacionar" ("Rotate") para um gatilho alternativo de clique de mouse.
    new MouseButtonTrigger(MouseInput.BUTTON_LEFT)
  • Voce atribui os mapeamentos Pause, Esquerda (Left), Direita (Right) para as teclas P, J, K, respectivamente.
    // You can map one or several inputs to one named action
    inputManager.addMapping("Pause",  new KeyTrigger(KeyInput.KEY_P));
    inputManager.addMapping("Left",   new KeyTrigger(KeyInput.KEY_J));
    inputManager.addMapping("Right",  new KeyTrigger(KeyInput.KEY_K));
    inputManager.addMapping("Rotate", new KeyTrigger(KeyInput.KEY_SPACE),
                                      new MouseButtonTrigger(MouseInput.BUTTON_LEFT));

Agora você precisa registrar seus mapeamentos de gatilho.

  • Você registra a ação pause para o ActionListener, porque ela é uma ação "ligado/desligado" ("on/off").
  • Você registra as ações de movimento para o AnalogListener, porque elas são ações graduais.
    // Add the names to the action listener.
    inputManager.addListener(actionListener, new String[]{"Pause"});
    inputManager.addListener(analogListener, new String[]{"Left", "Right", "Rotate"});

Este código vai dentro do método simpleInitApp(). Mas, desde que nós provavelmente adicionaremos muitas ligações de tecla, nós extrairemos estas linhas e as envolveremos no método auxiliar, initKeys(). O método initKeys() não é parte da interface Controles de Entrada (Input Controls) – você pode nomeá-lo da maneira que quiser. Apenas não se esqueça de chamar seu método do método initSimpleApp().

Implementando as Ações

Você tem mapeado nomes de ações para gatilhos de ação. Agora você especifica as ações por elas mesmas.

Os dois importantes métodos aqui são ActionListener com seu método onAction(), e o AnalogListener com seu método onAnalog(). Nestes dois métodos, você testa por cada mapeamento nomeado, e chama a ação do jogo que você quer ativar.

Neste exemplo, nós ativamos as seguintes ações:

  • Os mapeamento Rotacionar (Rotate) dispara a ação player.rotate(0, valor, 0).
  • Os mapeamentos Esquerda (Left) e Direita (Right) aumentam e diminuem a coordenada x do jogador.
  • O mapeamento Pause mude o valor de um booleano nomeado isRunning.
  • Nós também queremos checar o booleano isRunning antes de qualquer ação (diferente de despausar) seja executada.
  private ActionListener actionListener = new ActionListener() {
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("Pause") && !keyPressed) {
        isRunning = !isRunning;
      }
    }
  };

  private AnalogListener analogListener = new AnalogListener() {
    public void onAnalog(String name, float value, float tpf) {
      if (isRunning) {
        if (name.equals("Rotate")) {
          player.rotate(0, value*speed, 0);
        }
        if (name.equals("Right")) {
          Vector3f v = player.getLocalTranslation();
          player.setLocalTranslation(v.x + value*speed, v.y, v.z);
        }
        if (name.equals("Left")) {
          Vector3f v = player.getLocalTranslation();
          player.setLocalTranslation(v.x - value*speed, v.y, v.z);
        }
      } else {
        System.out.println("Press P to unpause.");
      }
    }
  };

Você tambpem pode combinar ambos os listeners em um, a engine enviará os eventos apropriados para cada método (onAction ou onAnalog). Por exemplo:

  private MyCombinedListener combinedListener = new MyCombinedListener();

  private static class MyCombinedListener implements AnalogListener, ActionListener {
    public void onAction(String name, boolean keyPressed, float tpf) {
      if (name.equals("Pause") && !keyPressed) {
        isRunning = !isRunning;
      }
    }

    public void onAnalog(String name, float value, float tpf) {
      if (isRunning) {
        if (name.equals("Rotate")) {
          player.rotate(0, value*speed, 0);
        }
        if (name.equals("Right")) {
          Vector3f v = player.getLocalTranslation();
          player.setLocalTranslation(v.x + value*speed, v.y, v.z);
        }
        if (name.equals("Left")) {
          Vector3f v = player.getLocalTranslation();
          player.setLocalTranslation(v.x - value*speed, v.y, v.z);
        }
      } else {
        System.out.println("Press P to unpause.");
      }
    }
  }
// ...
inputManager.addListener(combinedListener, new String[]{"Pause", "Left", "Right", "Rotate"});

Está OK usar somente um dos dois Listeners, e não implementar o outrio, se você não está usando este tipo de interação. A seguir, nós damos uma olhada mais de perto para decidir qual dos dois listeners é melhor adequado para qual situação.

Analog, Pressed, ou Released?

Técnicamente, toda entrada pode ser uma ação "analógica" ("analog") ou "digital". Aqui é como você descobre qual listener é o certo para qual tipo de entrada.

Mapeamentos register com o AnalogListener são ativados repetidamente e gradualmente.

  • Parâmetros:
    1. JME dá a você acesso ao nome da ação disparada.
    2. JME dá a você acesso ao valor gradual exibindo a força daquela entrada. No caso de um pressionamento de tec;a isto será o valor tpf que ela foi pressionada desde o último quadro. Para outras entradas tais como um joystick que dá controle analógico contudo então o valor também indicará a força da entrada multiplicada por tpf. Para um exemplo nisto vá para jMonkeyEngine 3 Tutorial (5) - Hello Input System - Variação sobre o tempo que a tecla é pressionada.

Para poder ver o tempo total que uma tecla foi pressionada então o valor de entrada pode ser acumulado. O ouvinte analógico pode também ser combinado com um ouvinte de ação (ActionListener) para que você seja notificado quando a tecla é liberada.

  • Exemplo: Eventos navegacionais (e.g. Esquerda (Left), Direita (Right), Rotacionar (Rotate), Correr (Run), Bombardeios (Strafe)), situações onde você interage continuamente.

Mapeamentos registrados para o ActionListener são tanto digital ou a;óes – "Pressionada ou liberada? Ligado ou desligado?"

  • Parâmetros
    1. JME dá a você acesso ao nome da ação disparada
    2. JME dá a você acesso ao booleano se a tecla está pressionada ou não.
  • Exemplo: Botáo de pausar, disparro, selecionar, pular, interações de clique de "um tempo" (one-time).

Dica: é muito comum que vocë queira que uma ação seja disparada uma vez, no momento quando a tecla é liberada. Por exemplo, quando abrindo uma porta, alterando o estado de um booleanom ou pegando um item. Para atingir isso, você usa um ActionListener e testa por … && !keyPressed. Por exemplo, olhe no código do botão Pause:
[[cide]]
if (name.equals("Pause") && !keyPressed) {
isRunning = !isRunning;
}
[[/code]]

Tabela de gatilhos

Você pode achar a lista de constantes de entrada nos arquivos src/core/com/jme3/input/KeyInput.java, JoyInput.java, and MouseInput.java. Aqui está uma visão geral das constantes de gatilho mais comuns:

Gatilho Código
Botão do mouse: Clique do botão esquerdo MouseButtonTrigger(MouseInput.BUTTON_LEFT)
Botão do mouse: Clique do botão direito MouseButtonTrigger(MouseInput.BUTTON_RIGHT)
Teclado: Caracteres e Números KeyTrigger(KeyInput.KEY_X)
Teclado: Barra de espaço KeyTrigger(KeyInput.KEY_SPACE)
Teclado: Return, Enter KeyTrigger(KeyInput.KEY_RETURN), KeyTrigger(KeyInput.KEY_NUMPADENTER)
Teclado: Escape (ESC) KeyTrigger(KeyInput.KEY_ESCAPE)
Teclado: Setas KeyTrigger(KeyInput.KEY_UP), KeyTrigger(KeyInput.KEY_DOWN), KeyTrigger(KeyInput.KEY_LEFT), KeyTrigger(KeyInput.KEY_RIGHT)

Dica: Se você não lembra uma constante de entrada durante o desenvolvimento, você se beneficia da funcionalidade de completar código de um IDE: Coloque o cursor (|) depois e.g. KeyInput.| e ative o completamento de código para selecionar possíveis identificadores de entrada.

Exercícios

  • Adicione mapeamentos para mover o jogador (caixa) para cima e para baixo com as teclas H e L!
  • Modifique os mapeamentos para que você também dispare o movimento para cima e para baixo com o rolar do eixo do mouse!
    • Dica: Use o novo MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)
  • Em qual situação seria melhor usar variáveis ao invés de literais para as definições de MouseInput/KeyInput?
    int usersPauseKey = KeyInput.KEY_P; 
    ...
    inputManager.addMapping("Pause",  new KeyTrigger(usersPauseKey));

Link para soluções propostas pelos usuários: http://jmonkeyengine.org/wiki/doku.php/jm3:solutions Tenha certeza de tentar resolvê-las por si mesmo antes!

Conclusão

Você é agora caoaz de adicionar interações customizadas para seu jogo: Você sabe que você primeiro tem de definir mapeamentos de tecla, e então as ações para cada mapeamento. Você aprendeu a responder a eventos de mouse e ao teclado. Você entende a diferença entre entrada "análogica" (gradualmente repetida) e "digital" (ligado/desligado).

Agora você já pode escrever um pequeno jogo interativo! Mas não seria mais legal se estas velhas caixas fossem um pouco mais luxuosas? Vamos continuar aprendendo sobre materiais.

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