Tutorial – Jogo da Memória com JavaFX

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{}
	}
}

JavaFX 1.2 Production Suite

About these ads

9 Responses to “Tutorial – Jogo da Memória com JavaFX”


  1. 2 Marcelo Cuin 26/03/2010 às 08:35

    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…

  2. 4 Fabio Silveira 26/03/2010 às 17:19

    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…..

  3. 5 Raphael Marques 26/03/2010 às 17:43

    Oi Fabio,
    Coloquei os links pra baixar os arquivos FXZ no post (antes do código final).
    Bons estudos! o/

  4. 6 Roger 05/04/2010 às 02:01

    Puta rapidez heim. Acho que você comentou antes até de eu soltar o botão para publicar.

  5. 7 Roger 26/04/2010 às 11:59

    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

  6. 8 Clodoaldo Brasilino 21/01/2011 às 11:38

    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!


  1. 1 JavaFX | javafx.com.br | Desenvolvimento de Aplicativos Avançados para Internet | RIAs Java FX | Mobile | Desktop Trackback em 26/03/2010 às 08:27

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s





Seguir

Obtenha todo post novo entregue na sua caixa de entrada.

%d blogueiros gostam disto: