JavaFX é um conjunto de ferramentas de interface de usuário multiplataforma para Java e é o sucessor das bibliotecas Swing. Nesse tutorial, nós exploraremos os recursos do JavaFX que o tornam fácil de usar para iniciar em programação de jogos em Java.
Se você já desenvolve aplicativos em Java, provavelmente não precisa baixar nada para começar: O JavaFX é incluído por padrão com no JDK (Kit de Desenvolvimento Java) desde a versão 7u6 de agosto de 2012. Se você não atualiza o Java a algum tempo, visite o site de descarga do Java para obter a última versão.
Classes Básicas do Arcabouço
A criação de um programa JavaFX começa com a implementação da classe Application, que é a superclasse de todos os aplicativos JavaFX. Sua classe principal deve chamar o método launch(), que então chamará init(), então start(), esperará o término do aplicativo e então chamará o método stop(). Dentre estes métodos, apenas start() é abstrato e deve ser sobrescrito.
A classe Stage é o recipiente de nível mais alto no JavaFX. Quando um aplicativo é iniciado, uma instância de Stage é criada e passada para o método start da classe Application. A classe Stage controla propriedades básicas da janela como o título, ícone, visibilidade, redimensionamento, modo de tela inteira e decorações - que são controladas usando a classe StageStyle. Mais instâncias de Stage podem ser criadas se necessário. Depois de configurada a classe Stage, o conteúdo pode ser adicionado à ela e o método show() é chamado.
Sabendo disso, podemos agora criar um exemplo mínimo que mostra uma janela em JavaFX:
1
importjavafx.application.Application;
2
importjavafx.stage.Stage;
3
4
publicclassExample1extendsApplication
5
{
6
publicstaticvoidmain(String[]args)
7
{
8
launch(args);
9
}
10
11
publicvoidstart(StagetheStage)
12
{
13
theStage.setTitle("Hello, World!");
14
theStage.show();
15
}
16
}
Estrutura do Conteúdo
O conteúdo em JavaFX (como texto, imagens e controles) é organizado como uma estrutura de árvore conhecida como grafo de cena, que agrupa e organiza os elementos de uma cena gráfica.
Representação de um grafo de cena do JavaFX.
Um elemento genérico em um grafo de cena no JavaFX é chamado de Node. Cada instância de Node em uma árvore tem apenas um nó pai, com exceção de um nó especial chamado de raíz. A classe Group é uma especialização de Node que pode ter vários nós filhos. Transformações gráficas (translado, rotação, escala etc) e efeitos aplicados à uma instância de Group também serão aplicadas aos nós filhos. Instâncias de Node podem ser estilizadas usando-se as folhas de estilo em cascata do JavaFX (CSS), muito parecido com o CSS usado com documentos HTML.
A classe Scene contém todo o conteúdo de um grafo de cena e precisa de uma instância de Node para servir de raíz (geralmente a raíz é uma instância de Group). Você pode configurar o tamanho de uma cena, caso contrário, seu tamanho será calculado com base em seu conteúdo. Uma instância de Scene deve ser passada à uma instância de Stage (através do método setScene()) para que possa ser exibida.
Renderizando os Gráficos
A renderização de gráficos é particularmente importante para programadores de jogos! Em JavaFX, a instância de Canvas é uma imagem na qual se pode desenhar texto, formas e imagens, usando a instância de GraphicsContext associada. Para aqueles desenvolvedores familiares com Swing, GraphicsContext é similar à classe Graphics que é parâmetro do método paint() de JComponent.
A instância de GraphicsContext é customizável de várias formas. Para escolher a cor de desenho, você pode configurar as cores de fill (preenchimento) e stroke (borda), que são instâncias de Paint: pode ser Color (cor uniforme), um gradiente (LinearGradient ou RadialGradient) ou mesmo uma ImagePattern (padrão de imagem). Você também pode aplicar um ou mais efeitos, como Lighting, Shadow ou GaussianBlur e mudar as fontes usando a classe Font.
A classe Image facilita o carregamento de imagens de vários formatos e desenha através da classe GraphicsContext. É fácil construir proceduralmente imagens usando a classe WritableImage com as classes PixelReader e PixelWriter;
Usando essas classes, podemos criar um exemplo no melhor estilo "Olá mundo!" como o abaixo. Para ser breve, colocaremos apenas a implementação de start() aqui (pularemos algumas declarações importantes e o método main()). De qualquer forma, o exemplo funcional completo pode ser encontrado no repositório do GitHub que acompanha esse turorial.
1
publicvoidstart(StagetheStage)
2
{
3
theStage.setTitle("Canvas Example");
4
5
Grouproot=newGroup();
6
ScenetheScene=newScene(root);
7
theStage.setScene(theScene);
8
9
Canvascanvas=newCanvas(400,200);
10
root.getChildren().add(canvas);
11
12
GraphicsContextgc=canvas.getGraphicsContext2D();
13
14
gc.setFill(Color.RED);
15
gc.setStroke(Color.BLACK);
16
gc.setLineWidth(2);
17
FonttheFont=Font.font("Times New Roman",FontWeight.BOLD,48);
18
gc.setFont(theFont);
19
gc.fillText("Hello, World!",60,50);
20
gc.strokeText("Hello, World!",60,50);
21
22
Imageearth=newImage("earth.png");
23
gc.drawImage(earth,180,100);
24
25
theStage.show();
26
}
O Loop do Jogo
Em seguida tornaremos nossos programas dinâmicos, ou seja, seu estado se alterará em função do tempo. Implementaremos um loop de jogo: um laço infinito que atualiza os objetos do jogo e renderiza a cena na tela, de preferência a 60 quadros por segundo.
A maneira mais fácil de fazê-lo em JavaFX é usando a classe AnimationTimer, onde o método handle() precisa ser implementado e será chamado a taxa de 60 quadros por segundo, ou o mais próximo dessa taxa possível. O que não quer dizer que só possa ser usado para animações, essa classe é capaz de muito mais.
Usar a classe AnimationTimer pode ser um pouco complicado: como ela é uma classe abstrata, ela não pode ser criada diretamente — a classe deve ser estendida antes que uma instância possa ser criada. De qualquer forma, para nossos exemplos simples. iremos estender nossa classe escrevendo uma classe anônima interna. A classe interna deve definir o método abstrato handle(), que reveberá apenas um argumento: o tempo atual do sistema em nanosegundos. Depois de declarar a classe interna, chamaremos imediatamente o método start(). que iniciará o laço. O laço pode ser interrompido chamando o método stop().
Com essas classes, podemos modificar nosso exemplo "Olá mundo!", criando uma animação consistindo da Terra orbitando o Sol sobre uma imagem de fundo.
Há outras maneiras de implementar o loop do jogo em JavaFX. Uma abordagem um pouco maior (mas mais flexível) é usar a classe Timeline, que é uma sequencia de animação consistindo de uma sequencia de instâncias de KeyFrame. Para criar o loop do jogo, a intância de TimeLine deve ser configurada para repetir indefinidamente, e apenas uma instância de KeyFrame é necessária, com Duration de 0,016 segundos (para obtermos 60 ciclos por segundo). Essa implementação pode ser encontrada no arquivo Example3T.java do repositório do GitHub.
Animação Baseada em Quadros
Outro componente geralmente necessário em programação de jogos é a animação baseada em quadros: mostrar uma sequência de imagens em sucessão rapidamente para criar a iusão de movimento.
Assumindo que todas as animações e todos os quadros têm a mesma duração, uma implementação básica pode ser simples como a abaixo:
Para integrar esta classe no exemplo anterior, podemos criar um OVNI animado, iniciando o objeto usando o código:
1
AnimatedImageufo=newAnimatedImage();
2
Image[]imageArray=newImage[6];
3
for(inti=0;i<6;i++)
4
imageArray[i]=newImage("ufo_"+i+".png");
5
ufo.frames=imageArray;
6
ufo.duration=0.100;
E, na instância de AnimationTimer, adicionando uma única linha de código:
1
gc.drawImage(ufo.getFrame(t),450,25);
No lugar apropriado. Para um exemplo funcional completo, confira o arquivo Example3AI.java no repositório do GitHub.
Manipulando a Entrada do Usuário
Detecar e processar a entrada do usuário em JavaFX é simples. Ações do usuário que podem ser detectadas pelo sistema (como uma tecla pressionada ou um clique) são chamadas de eventos. Em JavaFX, essas ações criam automaticamente objetos (como instâncias de KeyEvent ou MouseEvent) que armazenam os dados associados (como a tecla pressionada ou a posição do ponteiro). Qualquer classe JavaFX que implementa a interface EventTarget (como a classe Scene) pode escutar eventos e manipulá-los. Nos exemplos seguintes, nós mostraremos como configurar uma cena para processar vários eventos.
Na documentação da classe Scene há vários métodos que escutam diferentes tipos de entrada de diferentes fontes. Por exemplo, o método setOnKeyPressed() pode atribuir uma instância de EventHandler que será ativado quando uma tecla for pressionada e o método setOnMouseClicked() pode atribuir uma instância de EventHandler que é ativada quando um botão do mouse for pressionado. A classe EventHandler serve um propósito: encapsular um método (chamado handle()) que é chamado quando o evento correpondente ocorre.
Ao criar uma instância de EventHandler, você deve especificar o tipo de evento que ela manipula, por exemplo, você pode declarar um EventHandler<KeyEvent> ou um EventHandler<MouseEvent>. Instancias de EventHandler são geralmente criados como classes anônimas internas já que normalmente são usadas apenas uma vez (quando são passadas como um argumento de um dos métodos listados acima).
Manipulado Eventos do Teclado
Entrada do usuário são geralmente processadas no loop principal do jogo, e assim, um registro das teclas ativas deve ser mantido. Uma maneira de fazer isso é criar uma instância de ArrayList<String>. Quando uma tecla é pressionada, adicionaremos a representação do KeyCode da classe KeyEvent à lista em forma de String e quando for solta, a removeremos.
No exemplo abaixo, a tela contém duas imagens de setas. Quando uma tecla é pressionada, sua imagem correspondente se torna verde.
Agora vamos dar uma olhada em um exemplo que foca na classe MouseEvent em vez de KeyEvent. Nesse minijogo o jogador ganha pontos cada vez que o alvo é clicado.
Como EventHandler é implementado como uma classe anônima interna, qualquer variável usada por ela deve ser final ou "efetivamente final", significando que a variável não pode ser reatribuída. No exemplo anterior, os dados são passados ao EventHandler através do ArrayList, no qual os valores podem ser mudados sem reinicializá-lo (através dos métodos add() e remove()).
Porém para tipos básicos de dados, os valores não podem ser mudados depois de inicializados. Se você gostaria que o EventHandler podesse acessar tipos básicos de dados que mudam, você pode criar uma classe empacotadora que tenha variáveis públicas ou métodos para modificar o valor. No exemplo abaixo a classe IntValue possui uma variável pública do tipo int chamada value.
O código fonte completo está no repositório do GitHub, a classe principal é Example4M.java
Criando uma Classe Básica de Sprite com JavaFX
Em jogos eletrônicos, sprite é o termo que designa uma entidade visual. Abaixo temos um exemplo de uma classe de sprite que armazena uma imagem e uma posição, assim como a velocidade (para entidades móveis) e o tamanho para calcular as caixas delimitantes usadas para detecção de colisão. Nós também temos os métodos para obter e atribuir esses dados (omitidos para manter a brevidade), e alguns métodos necessários para o desenvolvimento de jogos:
update(): calcula a nova posição baseado na velocidade do sprite.
render(): desenha a imagem associada à tela (através da classe GraphicsContext) na posição armazenada no sprite.
getBoundary(): retorna uma instância de Rectangle2D do JavaFX, útil na detecção de colisão através do método intersects.
intersects(): determina se a caixa delimitante desse sprite intersecta com outro sprite.
Com a ajuda da classe Sprite, podemos facilmente criar um jogo de coleta em JavaFX. Nesse jogo, você assume o papel de uma maleta consciente que tem como objetivo coletar sacolas de dinheiro que foram deixadas no chão pelo antigo dono descuidado. As setas controlam a maleta pela tela.
Esse código recebe muita influência dos exemplos anteriores: configurando uma fonte para mostrar a pontuação, armazenando a entrada do teclado em uma ArrayList, implementando o loop do jogo com um AnimationTImer e criando classes empacotadoras para valores simples que precisam ser modificados no loop do jogo.
Um trecho do código interessante envolve criar um objeto para o personagem (maleta) e a ArrayList de instâncias de Sprite para os colecionáveis (sacolas de dinheiro):
Outro trecho interessante é o de criação do AnimationTimer, que tem como função:
calcular o tempo passado desde a última atualização
configurar a velocidade do jogador dependendo das teclas pressionadas
executar a detecção de colisão entre o jogador e os colecionáveis e atualizar a pontuação e a lista de colecionáveis quando ocorrer a colisão (um iterador é usado em vez de acessar o ArrayList diretamente para evitar uma exceção de modificação concorrente ao remover os objetos da lista)
Talvez você esteja interessado em aprender a usar o Scene Builder, um ambiente virtual para criação de interfaces de usuário. Esse programa gera FXML, que é uma linguagem baseada em XML que pode ser usada para definir uma interface para um programa JavaFX. Confira JavaFX SceneBuilder: Getting Started.
FX Experience é um blog excelente, atualizado regularmente, que tem informações e exemplos de projetos interessantes para desenvolvedores JavaFX. Vários das demonstrações listadas são inspiradoras!
O projeto JFxtras consiste em um grupo de desenvolvedores que criaram componentes JavaFX adicionais que oferecem funcionalidades comuns que faltam no JavaFX.
O projeto JavaFXPorts consegue empacotar seu aplicativo JavaFX para iOS e Android.
Nesse tutorial, eu te apresentei à classes JavaFX que são úteis em programação de jogos. Nós passamos por uma série de exemplos de complexidade ascendente, culminando em um jogo de coleta com sprites. Agora você está pronto para investigar alguns recursos listados acima ou mergulhar e começar a criação do seu próprio jogo. Muito boa sorte para você em seu empenho!
Seja o primeiro a saber sobre novas traduções–siga @tutsplus_pt no Twitter!