Tutorial – Colisão 1D com JavaFX

Hoje vou mostrar uma forma muito facil de programar colisão entre vários objetos com JavaFX (muito útil em jogos)

Nesse tutorial vou fazer um programa com algumas bolas de futebol colidindo em uma dimensão, ou seja, elas só se deslocam na horizontal. Quando uma bola colidir com outra, as duas trocam de velocidade (como na física) e quando batem no canto da janela voltam.

tutocolisao1d-tela

Vamos ao tutorial…

Primeiro vamos definir as variaveis básicas do programa que irão determinar o tamanho das coisas!

//tamanho de uma bola
def TAM = 36;
//largura da janela
def W = 500;
//altura da janela
def H = TAM;

O tamanho da bola é 36 porque esse é o tamanho da imagem da bola.

tutocolisao-bola

Agora vamos criar a classe bola

class Bola{
	//largura, algura e posicao da bola
	var w: Number = TAM;
	var h: Number = TAM;
	var x: Number;
	var y: Number = H - TAM;
	//velocidade da bola
	var vx: Number = 0;
	//outras bolas
	var others: Bola[];
};

A classe bola terá algumas variaveis pra guardar sua posição, tamanho e velocidade, alem de uma lista com as outras bolas.

Adicionamos a variavel rect que servirá para fazer os testes de colisão:

class Bola{
	...
	//retangulo utilizado na colisao
	var rect: Rectangle2D = bind Rectangle2D{
		minX: x
		minY: y
		width: w
		height: h
	}
};

Reparem no bind utilizado para criar o Retangle2D. Dessa forma, assim que algumas das variaveis utilizadas (x,y,w ou h) for alterada, é criado um novo Rectangle2D com as novas coordenadas.

Declaramos a variavel img que vai armazenar a imagem da bola:

class Bola{
	...
	//imagem da bola
	var img: ImageView = ImageView {
		translateX: bind x
		translateY: bind y
		rotate: bind x * 2
		image: Image {
			url: "{__DIR__}images/bola.png"
		}
	};
};

Aqui utilizei bind apenas nas variaveis internas (translateX, translateY e rotate) porque a classe ImageView permite. A classe Rectangle2D nao permite a utilização de bind, por isso tive que colocar o bind fora do Rectangle2D. A diferença eh que colocando dentro, apenas as variaveis internas são atualizadas (cada vez que x ou y mudarem, translateX, translateY e rotate serão atualizados), e colocando fora o objeto todo é recriado (cada vez que x ou y mudarem, um novo objeto Rectangle2D é criado, portanto sempre que puderem, usem o bind dentro). Reparem também na variável rotate, essa variável está relacionada com x para que quando a bola se mova, ela gire também para parecer mais real (esse x*2 foi escolhido ao acaso e deu certo o/).

Agora adicionamos a variavel anim que controlará a animação das bolas.

class Bola{
	...
	//animação que moverá as bolas
	//de a cada 15ms a funçao update será chamada
	var anim: Timeline = Timeline{
		repeatCount: Timeline.INDEFINITE
		keyFrames: [
			KeyFrame{
				time:15ms
				action:update
			}
		]
	}
	init{
		//construtor
		anim.play()
	}
};

A cada 15ms a função update (que ainda vamos criar) será chamada. Essa função é que vai atualizar a posição das bolas na janela. Também criei um construtor (init) que ativa a animação (anim.play()) assim que o objeto é criado.

Vamos à função update e também à função colide:

class Bola{
	...
	function update(): Void{
		//verifica se a bola atual colidiu com uma das outras
		for (other in others){
			colide(other);
		}
	}
	//funçao que verifica a colisao
	function colide(other: Bola) : Void{
		//se os dois retangulos colidiram
		if(this.rect.intersects(other.rect)){
			//troca a velocidade das duas bolas
			var temp = this.vx;
			this.vx = other.vx;
			other.vx = temp;

			//quanto uma bola invadiu a area da outra
			var col = (TAM - Math.abs( this.x - other.x )) / 2;
			if(this.x > other.x){
				this.x += col;
				other.x -= col;
			}else {
				this.x -= col;
				other.x += col;
			}
		}

		//diminui a velocidade (atrito)
		vx *= 0.998;

		//atualiza a posiçao em relaçao a velocidade
		x += vx;

		//verifica se saiu dos limites da janela
		if(x  W){
			x = W - w;
			vx = -Math.abs(vx);
		}
	}
};

A função update simplesmente verifica a colisão da bola atual (this) com todas as outras bolas.

A função colide é que faz o trabalho pesado. Verifica se houve colisão e atualiza a posição das bolas corretamente.

Coloquei um atrito (vx *= 0.998;) para que a aplicação pareça mais real. Então, as bolas vão ter uma velocidade inicial que vai diminuindo aos poucos.

Mas ai não tem graça né! A velocidade vai duminuindo até chegar a zero e as bolas simplesmente param!!! ¬¬

A parte do atrito é interessante! Mas para não estragar o programa vamos criar um meio das bolas ganharem velocidade novamente ao clicarmos nelas:

class Bola{
	...
	//imagem da bola
	var img: ImageView = ImageView {
		...
		//ao clicar com o mouse aumenta a velocidade das bolas
		onMousePressed: function(e: MouseEvent):Void{
			if(e.button == MouseButton.PRIMARY){
				vx = -3;
			}else {
				vx = 3
			}
		}
	};
	...
};

Assim, quando clicamos com o botão esquerdo do mouse, a bola clicada ganha velocidade 3 para a esquerda, quando for outro botão (do meio ou da direita) a bola ganha velocidade 3 para a direita. Dessa forma nosso programa nunca precisa parar =D

A classe Bola está pronta (lembrem que o nome do arquivo não pode ser o mesmo de uma classe interna, portanto, o nome do arquivo não pode ser Bola.fx, no meu caso estou utilizando o arquivo Main.fx)

Agora vamos criar as 3 bolas para nosso programa:

//criaçao das bolas cada uma numa posicao
var bola1 = Bola{
	x:0
	vx: 2
};

var bola2 = Bola{
	x:100
	vx: 0.5
};

var bola3 = Bola{
	x:200
	vx: 1
};

bola1.others = [bola2,bola3];
bola2.others = [bola1,bola3];
bola3.others = [bola1,bola2];
var bolas = [bola1,bola2,bola3];

Bem simples, só preciso atribuir os valores de x e de vx porque as outras variáveis já possuem os valores default corretos. Após criar as bolas, atribuimos a cada uma a variável others, que não poderia ser feito antes porque os others ainda não tinham sido criados.

Adicionamos agora a cena da nossa aplicação com as 3 bolas:

Stage {
	title: "Colisao 1D"
	scene: Scene {
		width: W
		height: H
		content: [
			bola1.img
			bola2.img
			bola3.img
		]
	}
}

Em seguida vejam o código completo com os imports:

package teste1;

import java.lang.Math;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.geometry.Rectangle2D;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.Scene;
import javafx.stage.Stage;

//tamanho de uma bola
def TAM = 36;
//largura da janela
def W = 500;
//altura da janela
def H = TAM;

class Bola{
	//largura, algura e posicao da bola
	var w: Number = TAM;
	var h: Number = TAM;
	var x: Number;
	var y: Number = 0;
	var vx: Number = 0;
	//outras bolas
	var others: Bola[];

	//retangulo utilizado na colisao
	var rect: Rectangle2D = bind Rectangle2D{
		minX: x
		minY: y
		width: w
		height: h
	}

	//imagem da bola
	var img: ImageView = ImageView {
		translateX: bind x
		translateY: bind y
		rotate: bind x * 2
		image: Image {
			url: "{__DIR__}images/bola.png"
		}
		//ao clicar com o mouse aumenta a velocidade das bolas
		onMousePressed: function(e: MouseEvent):Void{
			if(e.button == MouseButton.PRIMARY){
				vx = -3;
			}else {
				vx = 3
			}
		}
	};

	//animação que moverá as bolas
	//de a cada 15ms a funçao update será chamada
	var anim: Timeline = Timeline{
		repeatCount: Timeline.INDEFINITE
		keyFrames: [
			KeyFrame{
				time:15ms
				action:update
			}
		]
	}
	init{
		//construtor
		anim.play()
	}
	function update(): Void{
		//verifica se a bola atual colidiu com uma das outras
		for (other in others){
			colide(other);
		}
	}
	//funçao que verifica a colisao
	function colide(other: Bola) : Void{
		//se os dois retangulos colidiram
		if(this.rect.intersects(other.rect)){
			//troca a velocidade das duas bolas
			var temp = this.vx;
			this.vx = other.vx;
			other.vx = temp;

			//quanto uma bola invadiu a area da outra
			var col = (TAM - Math.abs( this.x - other.x )) / 2;
			if(this.x > other.x){
				this.x += col;
				other.x -= col;
			}else {
				this.x -= col;
				other.x += col;
			}
		}

		//diminui a velocidade (atrito)
		vx *= 0.998;

		//atualiza a posiçao em relaçao a velocidade
		x += vx;

		//verifica se saiu dos limites da janela
		if(x  W){
			x = W - w;
			vx = -Math.abs(vx);
		}
	}
};

//criaçao das bolas cada uma numa posicao
var bola1 = Bola{
	x:0
	vx: 2
};

var bola2 = Bola{
	x:100
	vx: 0.5
};

var bola3 = Bola{
	x:200
	vx: 1
};

bola1.others = [bola2,bola3];
bola2.others = [bola1,bola3];
bola3.others = [bola1,bola2];

Stage {
	title: "Colisao 1D"
	scene: Scene {
		width: W
		height: H
		content: [
			bola1.img
			bola2.img
			bola3.img
		]
	}
}

O applet pode ser visto aqui e o programa pode ser executado por aqui.

Aproveitem =D

3 Responses to “Tutorial – Colisão 1D com JavaFX”


  1. 1 William Antônio 02/07/2009 às 23:17

    Poxa, parabéns! Ótimo tutorial!

  2. 2 William Antônio Siqueira 13/09/2009 às 23:29

    Haha, voltei depois de alguns meses aqui.

    Valeu de novo, vou usar em um projeto, mas será que fazer a colisão com diversos objetos na tela não pode matar a performance?? Só testando pra Saber😀

  3. 3 Raphael Marques 13/09/2009 às 23:44

    Com diversos elementos na tela vc tem que testar colisao com hierarquia! Nao vai testar tudo de uma vez =D


Deixe uma resposta

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





%d blogueiros gostam disto: