Joypad no Java com JInput

Dependendo do tipo de jogo, a experiência do jogador só fica completa com um controle de jogo, joypad ou joystick. Sem contar ainda com a necessidade que alguns jogos possuem de controles especiais (guitarras, etc). Neste post técnico veremos como usar a biblioteca JInput para dar suporte a controles de jogo no Java.

Já vimos aqui como criar um game loop básico no post Game Loop com taxa constante de pulsos. Utilizaremos agora uma estrutura muito semelhante, de forma que é interessante dar uma olhada naquele post se você ainda não leu.

Nosso projeto terá duas classes apenas, a Main, que carrega o “jogo” (não é de fato um jogo…) e a classe Jogo, que possui um game loop onde ficamos verificando se alguma tecla, botão do mouse ou do joypad foi pressionada, mostrando a informação correspondente na tela.

A classe Main é simples, como podemos ver abaixo:

1
2
3
4
5
6
public class Main {
    public static void main(String[] args) {
        Jogo j = new Jogo();
        j.run();
    }
}

Basicamente ela cria um objeto do tipo Jogo e executa ele, chamando seu método “run”. A classe Jogo, então, é que contém todo o código interessante. Veremos seu código e a seguir a explicação dos pontos que usam o JInput (os demais pontos são explicados no post citado acima).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import java.awt.Graphics2D;
import java.awt.image.BufferStrategy;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
import net.java.games.input.Component;
import net.java.games.input.Controller;
import net.java.games.input.ControllerEnvironment;
 
public class Jogo extends JFrame {
 
    BufferStrategy bs;
    Controller[] cs;
 
    public Jogo() {
    }
 
    public void initialize() {
        setTitle("Exemplo de uso do JInput");
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setSize(800, 600);
        setIgnoreRepaint(true);
        setVisible(true);
        createBufferStrategy(2);
        bs = getBufferStrategy();
 
        ControllerEnvironment ce = ControllerEnvironment.getDefaultEnvironment();
        cs = ce.getControllers();
    }
 
    public void run() {
        initialize();
        while (true) {
            update();
            render();
        }
    }
 
    public void update() {
        for (int i = 0; i < cs.length; i++) {
            cs[i].poll();
        }
    }
 
    public void render() {
        Graphics2D g = (Graphics2D) bs.getDrawGraphics();
        g.clearRect(0, 0, getWidth(), getHeight());
 
        for (int i = 0; i < cs.length; i++) {
            g.drawString(i + ". " + cs[i].getName() + ", " + cs[i].getType(), 50, 80 + i * 20);
            Component[] cmps = cs[i].getComponents();
            for (int c = 0; c < cmps.length; c++) {
                g.drawString(c + ". " + cmps[c].getName() + " = " + cmps[c].getPollData(), 50 + i * 200, 180 + c * 20);
            }
        }
        g.dispose();
        bs.show();
    }
}

Usando o JInput

Quando incluímos a biblioteca JInput no nosso projeto (veremos como fazer isso no final), temos algumas classes disponíveis para uso. Uma delas é ControllerEnvironment, que permite obter uma lista de todos controles disponíveis no sistema. Isto é feito da seguinte forma:

ControllerEnvironment ce = ControllerEnvironment.getDefaultEnvironment();
Controller[] cs = ce.getControllers();

Interessante notar que a JInput trata o teclado e o mouse como controles, de forma que o vetor acima terá pelo menos estes dois itens. Se você tiver um ou mais controle de jogo conectado ao sistema, então mais itens serão adicionados ao vetor, um para cada controle.

Cada controle é constituído de componentes. No teclado os componentes são as teclas; em um controle de jogo, os componentes são os eixos e os botões. Uma vez que temos um objeto do tipo Coltroller, podemos obter a lista de componentes que ele possui, com o código abaixo:

1
2
3
4
Component[] cmps = controle.getComponents();
for (int c = 0; c < cmps.length; c++) {
    System.out.println(cmps[c].getName() + " = " + cmps[c].getPollData());
}

Este código obtem um vetor de componentes do controle e para cada um deles exibe o nome e os dados lidos no último pooling. Estes dados correspondem ao estado do componente. Por exemplo, se for um botão, pode vir 1 (pressionado) ou 0 (solto); se for um joystick analógico, virá um valor entre -1.0 e 1.0, sendo que 0.0 significa que o joystick está no centro. São estes dados que vamos utilizar para comandar as ações do jogo.

Veja que, seguindo a melhor prática para leitura de dispositivos de entrada, o JInput não trabalha com eventos, mas sim com pooling. A diferença é que com eventos, quando uma tecla do teclado é pressionada, por exemplo, é gerado um evento que o jogo deve anteder – nem sempre será a melhor hora para fazer isso – podemos estar no meio da atualização da tela, por exemplo. Com o pooling a coisa se inverte: é o jogo que lê o estado do teclado quando desejar, verificando, então, se a tecla desejada está pressionada ou não.

Como JInput fazemos a leitura do estado de um objeto do tipo Controller chamando seu método “pool”. Veja dentro do método “update” da classe Jogo onde percorremos o vetor de controles chamando “pool” para cada um deles. Isso faz com que a cada volta do game loop os dados atualizados dos controles estejam disponíveis.

No método “render” percorremos novamente o vetor de controles e para cada um, obtermos o vetor de componentes. Então percorremos todos componentes mostrando na tela seu nome e seu estado atual.

Incluindo JInput no projeto

Finalmente, vejamos como incluir a JInput no nosso projeto. Baixe a biblioteca mais recente no site e dentro do ZIP você vai encontrar o arquivo jinput.jar, que é a biblioteca em Java, e vários arquivos dll e so, que são as partes nativas. Estas partes devem ser distribuídas junto com seu jogo e são a ponte entre o código Java que usamos acima e a manipulação de baixo nível do hardware em cada plataforma (.dll no Windows e .so no Linux).

Digamos que seu projeto esteja armazenado em uma pasta chamada MeuProjeto. Dentro dela deve ter a pasta de códigos fonte (src) e quando você compila e constrói, surgem as pastas build e dist (que é onde está o jogo pronto para ser distribuído). Se não existir, crie dentro de MeuJogo uma pasta lib e dentro dela coloque o jinput.jar. Os arquivos .dll e .so vão diretamente dentro de MeuJogo.

Agora utilize um comando do seu IDE para importar a biblioteca para o projeto. No NetBeans faça assim:

  1. Clique com o botão direito do mouse no projeto;
  2. No menu selecione propriedades;
  3. Na janela de propriedades vá na seção “Bibliotecas” e clique em “Adicionar JAR/pasta”;
  4. Localize o jinput.jar que está dentro da pasta lib e pronto, confirme tudo.

Ao construir (dar build), será criada uma pasta lib dentro de dist, com o jinput lá dentro. Mas será preciso copiar as dll e so manualmente. Então é só distribuir o conteúdo da pasta dist.

Conclusão

Usar um controle de jogo pode melhorar muito a experiência do usuário com certos jogos, principalmente nos gêneros de tiro, luta, corrida e plataforma.

Mas mesmo em jogos onde o teclado e o mouse são a melhor opção, o JInput é uma boa escolha porque facilita também o acesso a estes dispositivos. Criar do zero um pool para teclado e mouse é meio chato – demora pra dar o resultado desejado. O Jinput já tem tudo pronto.

Clique aqui para baixar o código fonte deste post.

Autor: Luiz Nörnberg Ver todos os posts de
Sou Bacharel em Ciência da Computação pela Universidade Católica de Pelotas (UCPel), onde também atuei como professor. Desde a época da faculdade (mais de quinze anos atrás) a paixão por jogos tem sido importante no meu direcionamento profissional. Sou sócio-fundador do Izyplay Game Studio, onde exerço o cargo de Diretor de Tecnologia. Sempre tive grande foco em desenvolvimento em Java, embora tenha migrando para a tecnologia Adobe AIR em função de sua portabilidade. Ah, e é claro, dou meus palpites no game design.

6 Comentários em "Joypad no Java com JInput"

  1. Leandro Vian 18/08/2011 at 15:08 - Reply

    Opa gostei do post galera, mas fui olhar os posts antigos sobre gameloop e vi que os arquivos com o código dos exemplos não existem mais.

    Abraços

  2. everton.vieira 19/08/2011 at 10:11 - Reply

    Olá Leandro,
    Como sempre, obrigado pela observação. Já corrigimos o link ;)

  3. rodrigo souza 10/09/2011 at 14:11 - Reply

    Olá, estava testando o JInput porém quando compilo ele lança um erro INFO: net.java.games.input.DirectAndRawInputEnvironmentPlugin is not supported, eu uso um win64. Procurei por esse erro na net e não obtive muito exito. Grato.

    • luiz.nornberg 12/09/2011 at 18:11 - Reply

      Rodrigo, ouvi falar de problemas com S.O. de 64 bits, mas foi em releases anteriores – infelizmente não sei afirmar com certeza se já está ok, mas me parece que sim. Se tiveres como testar exatamente este projeto em um ambiente de 32 bits poderás tirar a prova. Fora isso, não me ocorre no momento outra causa para o erro.

  4. Leonardo Zimbres 27/10/2011 at 17:31 - Reply

    Bem, não sei se estou sendo útil, mas falta o… “ponteiro” do vetor? Ou se chamaria índice? Não sei. Bem, o seguinte código:
    g.drawString(c + “. ” + cmps.getName() + ” = ” + cmps.getPollData(), 50 + i * 200, 180 + c * 20);
    seria cmps.getName()

    • luiz.nornberg 27/10/2011 at 18:40 - Reply

      Leonardo, o nome seria “índice” mesmo. Obrigado por avisar. Já corrigi o código no post. Pelo menos no projeto exemplo para download já estava correto…

Deixar um Comentário