Finalmente outro tutorial! =D
Dessa vez vou mostrar como fazer um simples jogo da memória em JavaFX.
O jogo vai ter duas telas (tela inicial, e tela do jogo em si) e toda a parte visual será carregada de arquivos FXZ (em vez de configurar todos os componentes pelo código).
O resultado é esse:
Vocês podem executar o jogo clicando aqui.
Vamos começar então…
Nesse tutorial vou usar os seguintes programas:
- Corel Draw X4 (pode ser qualquer editor de sua preferencia que gere arquivos no formato SVG)
- JavaFX 1.2
- JavaFX Prodution Suite (SVG to JavaFX Graphics Converter) baixe aqui
- NetBeans 6.8 com o plugin para JavaFX 1.2
O primeiro passo é criar os elementos visuais do jogo (telas, botoes, cartas, etc). Fiz isso utilizando o Corel Draw X4. Fiz cada tela com a proporção 4×3 para poder encaixar em uma tela 640×480 ou 800×600.
Você pode utilizar o editor gráfico que vc quiser, o importante é agrupar os elementos e dar nomes a eles. Neste caso, agrupei os elementos das telas em dois grupos principais: “titulo” e “jogo”. Dentro de cada tela, agrupei os botoes com suas sombras: “iniciar”, “sair” e “misturar”. Também dei um nome diferente para cada carta. O outros elementos que não terão funcionalidades agrupei nomeando de “resto”.
Depois de tudo criado e nomeado, exportei cada tela para o formato SVG. Ajustei a largura pra 640 pixels e ativei a opção de converter os textos em curvas (isso é importante pra você não precisar se preocupar com a fonte utilizada).
Com os arquivos SVG’s criados (com o editor que você preferir), você os converte para FXZ utilizando o “SVG to JavaFX Graphics Converter”, ferramenta que vem no JavaFX Production Suite. Lembre de desmarcar a opção “Preserve “JFX:” IDs only”.
Agora só precisamos copiar os 2 arquivos gerados (titulo.fxz e jogo.fxz) para o projeto do NetBeans e começar a programar! =D
Vamos começar criando o arquivo JogoDaMemoria.fx. Criamos a função procurarNode(…) que procura por um Node (elemento visual) dentro de outros Nodes. Vou usar essa função para procurar os botoes e as cartas dentro de cada tela usando o nome que dei a cada elemento no Corel Draw.
function procurarNode(root:Node, id:String):Node{
if(root.id == id)
return root;
if(root instanceof Group){
var group = root as Group;
for(node in group.content){
var n = procurarNode(node,id);
if(n != null)
return n;
}
}
return null;
}
A função procurarNode(…) recebe um Node raiz, e um nome para procurar os elementos recursivamente dentro do Node raiz. Quando você agrupa os elementos como fiz no Corel, eles são carregados pelo JavaFX como objetos da classe Group, e cada Group tem um atributo ‘content’ que pode ter outros Nodes dentro.
Agora que já sei procurar um elemento visual dentro de uma tela, vou criar a classe do jogo carregando todos os elementos necessários:
class Game extends CustomNode{
//telas do jogo
var telaTitulo = FXDLoader.load("{__DIR__}titulo.fxz");
var telaJogo = FXDLoader.load("{__DIR__}jogo.fxz");
//botoes da titleScreen
var tituloIniciar = procurarNode(telaTitulo,"iniciar");
var tituloSair = procurarNode(telaTitulo,"sair");
//botoes da gameScreen
var jogoMisturar = procurarNode(telaJogo,"misturar");
var jogoSair = procurarNode(telaJogo,"sair");
//cartas da gameScreen
var cartas = for(i in [1..18]) procurarNode(telaJogo,"carta{i}") as Rectangle;
//cores das cartas
var cores = [
Color.GREEN,Color.GREEN,Color.GREEN,
Color.RED,Color.RED,Color.RED,
Color.BLUE,Color.BLUE,Color.BLUE,
Color.CYAN,Color.CYAN,Color.CYAN,
Color.MAGENTA,Color.MAGENTA,Color.MAGENTA,
Color.YELLOW,Color.YELLOW,Color.YELLOW,
];
//cartas abertas pelo jogador
var cartasAbertas:Rectangle[] = [];
//tela que esta sendo exibida
var telaAtual = telaTitulo;
}
A classe Game é subclasse de CustomNode e portanto se torna um elemento visual.
Para carregar um arquivo FXZ para uma variavel (do tipo Node) é só usar a seguinte comando:
var <variavel> = FXDLoader.load(<caminho para o arquivo>);
Com o Node de uma tela carregado, para encontrar um elemento dentro desse Node é só usar a função criada:
var <elemento> = procurarNode(<variavel Node>, <nome do elemento procurado>);
Com esses comandos a classe carrega as duas telas e seus botoes.
Para buscar as cartas utilisamos um for resultando em uma sequência de todas as cartas (da “carta1″ até a “carta18″):
var cartas = for(i in [1..18]) procurarNode(telaJogo,”carta{i}”) as Rectangle;
Pra quem não conhece muito, o for em JavaFX retorna uma sequência (array/lista) com os elementos processados em cada passo do for. Nesse caso, retorno uma carta “procurarNode(…) as Rectangle;” convertida para Rectangle em cada passo do for.
E como eu sei que as cartas são do tipo Rectangle? E como sei que os Nodes são do tipo Group? Pelo NetBeans você pode abrir o arquivo FXZ, clicar no botão “Source”, ver o texto contido no arquivo FXZ e procurar pelos IDs das cartas:
Rectangle {
id: "carta1"
fill: Color.WHITE
...
}
Os elementos que você desenha podem ser de vários tipos, é preciso olhar no arquivo FXZ pra saber o tipo, ou então utilize os elementos como Node, mas ai você não terá acesso a muitos atributos do objeto. No caso do jogo da memória, foi preciso saber o tipo das cartas para poder alterar a cor de preenchimento já que Node não possui esse atributo.
Depois crio uma sequência com as cores que vou utilizar. Nesse jogo, utilizei 3 objetos de cada cor, então, tenho que abrir três cartas iguais a cada jogada.
A variável ‘cartasAbertas’ serve pra guardar quais cartas foram abertas na tentativa de abrir cartas iguais. E a variável ‘telaAtual’ guarda, OBVIAMENTE, a referência para a tela que está sendo mostrada! =O
Esses são todos os atributos que precisamos criar! Agora vamos às funções!
A primeira função é a que define o que será exibido na tela. Neste caso, a ‘telaAtual’ é exibida e atualizada automaticamente se a variável for alterada (por causa do bind):
override function create():Node{
Group{
content: bind telaAtual
}
}
Criamos agora a função ‘config’ que define o que vai ocorrer quando clicar em cada botao e carta:
function config(){
tituloIniciar.onMousePressed = function(event){
changeScreen(telaJogo);//mudar de tela
}
tituloSair.onMousePressed = function(event){
FX.exit();//sair da aplicação
}
jogoMisturar.onMousePressed = function(event){
randomCards();//misturar as cores das cartas
}
jogoSair.onMousePressed = function(event){
changeScreen(telaTitulo);//mudar de tela
}
for(carta in cartas){
carta.onMousePressed = function(event){
//função com a lógica de qdo clicar em uma carta
selecionarCarta(carta);
}
}
}
Chamamos a função ‘config’ no construtor da classe:
init{
config();
}
Agora a função de muda de uma tela para outra (quando muda de tela, as cartas são embaralhadas):
function changeScreen(screen:Node){
telaAtual = screen;
if(telaAtual == telaJogo)
randomCards();
}
A função que embaralha a sequência de cores das cartas e as deixa escondidas (pintadas de cinza):
function randomCards():Void{
//embaralha as cartas
cores = Sequences.shuffle(cores) as Color[];
//esconde todas as cartas
for(carta in cartas){
carta.fill = Color.GRAY;
}
//fecha todas as cartas abertas
delete cartasAbertas;
}
E finalmente a parte lógica do jogo que define o que acontece quando clico em uma carta:
function selecionarCarta(carta:Rectangle):Void{
//se abriu 3 cartas e nao eram iguais
if(sizeof cartasAbertas == 3){
//esconde as 3 cartas abertas
for(c in cartasAbertas){
c.fill = Color.GRAY;
}
delete cartasAbertas;
}else{
//se a carta esta escondida
if(carta.fill == Color.GRAY){
//pinta a carta com sua cor especifica
carta.fill = cores[Sequences.indexOf(cartas, carta)];
//insere nas cartasAbertas
insert carta into cartasAbertas;
//se abriu 3 e eram iguais
if(sizeof cartasAbertas == 3 and
cartasAbertas[0].fill == cartasAbertas[1].fill and
cartasAbertas[0].fill == cartasAbertas[2].fill){
//continuam abertas
delete cartasAbertas;
}
}
}
}
Essa função ‘selecionarCarta’ tenta abrir a carta clicada e adioná-la na sequência ‘cartasAbertas’, se forem abertas 3 cartas diferentes, então no próximo clique as 3 cartas erradas são fechadas, já se forem abertas 3 cartas iguais, elas permanecem abertas. Simples assim! =D
Classe finalizada! \o/ \o/ \o/ \o/
Ué? Só isso? =P
Ainda falta exibir um objeto da classe que criamos, pra isso criamos um Stage:
Stage {
title: "Jogo da Memoria"
scene: Scene {
content: Game{}
}
}
Agora sim, só executar!
Esse foi apenas um exemplo de um jogo simples que envolve apenas cliques com o mouse, alteração de atributos de alguns elementos e mudança entre telas. Quantos jogos você conhece que só precisam disso???
Quem quiser os arquivos FXZ podem baixar aqui e aqui.
E pra finalizar, o código completo:
import javafx.scene.CustomNode;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.fxd.FXDLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;
import javafx.scene.paint.Color;
import javafx.util.Sequences;
import javafx.scene.Cursor;
import javafx.scene.shape.Rectangle;
//procura um Node com o id especificado
function procurarNode(root:Node, id:String):Node{
if(root.id == id)
return root;
if(root instanceof Group){
var group = root as Group;
for(node in group.content){
var n = procurarNode(node,id);
if(n != null)
return n;
}
}
return null;
}
class Game extends CustomNode{
//telas do jogo
var telaTitulo = FXDLoader.load("{__DIR__}titulo.fxz");
var telaJogo = FXDLoader.load("{__DIR__}jogo.fxz");
//botoes da titleScreen
var tituloIniciar = procurarNode(telaTitulo,"iniciar");
var tituloSair = procurarNode(telaTitulo,"sair");
//botoes da gameScreen
var jogoMisturar = procurarNode(telaJogo,"misturar");
var jogoSair = procurarNode(telaJogo,"sair");
//cartas da gameScreen
var cartas = for(i in [1..18]) procurarNode(telaJogo,"carta{i}") as Rectangle;
//cores das cartas
var cores = [
Color.GREEN,Color.GREEN,Color.GREEN,
Color.RED,Color.RED,Color.RED,
Color.BLUE,Color.BLUE,Color.BLUE,
Color.CYAN,Color.CYAN,Color.CYAN,
Color.MAGENTA,Color.MAGENTA,Color.MAGENTA,
Color.YELLOW,Color.YELLOW,Color.YELLOW,
];
//cartas abertas pelo jogador
var cartasAbertas:Rectangle[] = [];
//tela que esta sendo exibida
var telaAtual = telaTitulo;
init{
config();
}
override function create():Node{
Group{
content: bind telaAtual
}
}
//configura os eventos de click dos botoes e cardas
//configura o cursor do mouse sobre os componentes
function config(){
tituloIniciar.onMousePressed = function(event){
changeScreen(telaJogo);
}
tituloSair.onMousePressed = function(event){
FX.exit();
}
jogoMisturar.onMousePressed = function(event){
randomCards();
}
jogoSair.onMousePressed = function(event){
changeScreen(telaTitulo)
}
for(carta in cartas){
carta.cursor = Cursor.HAND;
carta.onMousePressed = function(event){
selecionarCarta(carta);
}
}
tituloIniciar.cursor = Cursor.HAND;
tituloSair.cursor = Cursor.HAND;
jogoMisturar.cursor = Cursor.HAND;
jogoSair.cursor = Cursor.HAND;
}
//muda de uma tela para outra
function changeScreen(screen:Node){
telaAtual = screen;
if(telaAtual == telaJogo)
randomCards();
}
function randomCards():Void{
//embaralha as cartas
cores = Sequences.shuffle(cores) as Color[];
//esconde todas as cartas
for(carta in cartas){
carta.fill = Color.GRAY;
}
//fecha todas as cartas abertas
delete cartasAbertas;
}
//call when click on a card
function selecionarCarta(carta:Rectangle):Void{
//se abriu 3 cartas e nao eram iguais
if(sizeof cartasAbertas == 3){
//esconde as 3 cartas abertas
for(c in cartasAbertas){
c.fill = Color.GRAY;
}
delete cartasAbertas;
}else{
//se a carta esta escondida
if(carta.fill == Color.GRAY){
//pinta a carta com sua cor especifica
carta.fill = cores[Sequences.indexOf(cartas, carta)];
//insere nas cartasAbertas
insert carta into cartasAbertas;
//se abriu 3 e eram iguais
if(sizeof cartasAbertas == 3 and
cartasAbertas[0].fill == cartasAbertas[1].fill and
cartasAbertas[0].fill == cartasAbertas[2].fill){
//continuam abertas
delete cartasAbertas;
}
}
}
}
}
Stage {
title: "Jogo da Memoria"
scene: Scene {
content: Game{}
}
}




Ficou show, cara!
Olá Rafael, tudo bem ??
coloquei um post no meu site mostrando esse seu belo tutorial, e linkando para o seu site… ok…
duas dúvidas: o meu site também é wordpress, e como vc colocar na home somente as primeiras linhas do post ? pois o meu fica o post todo.
outra: como vc coloca o seu código nessa “folha” que tem numeração e linhas….
obrigado e forte abraço…
@Marcelo
Olhe este post sobre o código:
http://flaviowd.wordpress.com/2010/02/21/postando-codigo-fonte-no-wordpress-com/
Estou estudando sobre JAVAFX, gostei muito dos seus exemplos, onde consigo baixar os exemplos ?? No caso do jogo da memória não tenho as imagens (fxz)…
Obrigado…..
Oi Fabio,
Coloquei os links pra baixar os arquivos FXZ no post (antes do código final).
Bons estudos! o/
Puta rapidez heim. Acho que você comentou antes até de eu soltar o botão para publicar.
Invejo sua capacidade de fazer um mestrado, ser produtivo na escrita de artigos e ainda ficar brincando com javaFx. =~
Eu não consigo nem respirar e mascar chiclete ao mesmo tempo.
Ps.: esqueci do fato de que você assiste trocentas séries enquanto faz tudo isso
Legal ver você botando a idéia do JavaFX pra frente cara.
Agora mesmo eu to trabalhando em um projeto de educação, e to pensando em usar o JavaFX para criar Objetos de Aprendizagem (exercícios, animações) usando ele.
Assim que eu tiver alguma novidade sobre o primeiro exercício que to fazendo (um drag-and-drop de classificação de animais em vertebrados e invertebrados) eu entro em contato contigo para trocarmos experiências!
Um grande abraço cara!