|
No artigo passado, vimos como definir o loop principal de nosso jogo de maneira consistente. Neste, veremos como desenhar uma bolinha, que rebate se movimenta pela tela, rebatendo em seus cantos.
A classe Ball
Vamos começar pensando na própria bola. Como já falamos em artigos anteriores, a bola terá duas fases:
- Atualização da sua lógica: Que faz a movimentação da bola, e testa se ela colidiu com os cantos da tela;
- Pintura: O desenho da tela.
Analisemos então cada uma dessas fases.
A atualização da bola
A bola deve se movimentar numa velocidade constante. Como explicado no artigo Animação Baseada em Tempo, a fórmula física que define uma velocidade, seja ela angular ou linear é a seguinte:
velocidade = distância / tempo
Essa fórmula é bem natural. Dizemos que um carro se locomove a 60 quilômetros (distância) por hora (tempo). Também devemos definir uma velocidade para nossa bola, mas vamos adotar uma unidade mais conveniente que os km/h: os pixels / s.
Para utilizarmos a fórmula, seria necessário conhecermos o tempo entre dois updates. Esse calculo não é uma tarefa da bola (já que esse tempo deverá ser o mesmo para todos os sprites durante um update), portanto, iremos apenas recebê-lo como parâmetro.
E qual é a lógica de atualização da bola?
a) Somamos a velocidade do deslocamento lateral no eixo x;
b) Somamos a velocidade de deslocamento vertical no eixo y.
Se a bola colidir com um dos cantos da tela, invertemos a velocidade, e corrigimos a posição da bola (já que ela pode ter saído da tela).
Percebemos então que a bola tem os seguintes atributos:
- x: a posição no eixo x;
- y: A posição da bola no eixo y.
- vx: Uma velocidade no eixo x;
- vy: Uma velocidade no eixo y;
- SIZE: Um tamanho. Como é uma bola, a altura e a largura serão iguais;
- screenWidth: A informação da largura da tela
- screenHeight: A informação sobre a altura da tela.
A lógica do movimento da bola em código, fica assim:
@Override
public void update(long time) {
//O tempo é em milis. Para obter em segundos, precisamos dividi-lo por 1000.
x += (time * vx) / 1000;
y += (time * vy) / 1000;
checkCollision();
}
Note que multiplicamos a velocidade pelo tempo transcorrido em segundos. Assim, se nossa velocidade é 20, mas se apenas meio segundo se passou, o deslocamento nesse update será de apenas 10. A divisão por 1000 ocorre porque o tempo na variável time é dado em milisegundos, não segundos.
E o método checkCollision? Vamos pensar em como funciona a colisão da bola. Existem apenas 4 pontos na tela onde a bola pode colidir, veja no diagrama:
Esses pontos são:
a) No lado esquerdo da tela, quando x <= 0;
b) No lado direito da tela, quando x, somado ao largura da bola, for maior que a largura da tela;
c) No topo da tela, quando y <= 0;
d) Na base da tela, quando y, somado a altura da bola, for maior que a altura da tela.
Quando essa colisão ocorrer, devemos reposicionar a bola no interior da tela, e então inverter a velocidade em x ou y, de acordo com o local onde a bola colidiu. Em código, temos o seguinte:
private void checkCollision()
{
//Testamos se a bola saiu da tela
//Se sair, recolocamos na tela e invertemos a velocidade do eixo
//Isso fará a bola "quicar".
if (x < 0) { //Lateral esquerda
vx = -vx;
x = 0;
} else if ((x+SIZE) > screenWidth) { //Lateral direita
vx = -vx;
x = screenWidth - SIZE;
}
if (y < 0) { //topo
vy = -vy;
y = 0;
} else if (((y+SIZE) > screenHeight)) { //baixo
vy = -vy;
y = screenHeight - SIZE;
}
}
E esta é a fase de atualização da bola.
A pintura da bola
Vamos apenas pintar uma bola vermelha na tela, usando antialiasing. O código é bem parecido com o que já vimos em artigos anteriores, vejam:
@Override
public void draw(Graphics2D g2d) {
Graphics2D g = (Graphics2D) g2d.create();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.RED);
g.fill(new Ellipse2D.Float(x, y, SIZE, SIZE));
g.dispose();
}
Ou seja, ele simplesmente pinta a bola em sua posição x e y.
Criando o local onde a bola será desenhada
Vamos criar um JFrame, que será a nossa área de pintura principal. É ele que conterá o loop principal, portanto, faremos que ele implemente a interface LoopSteps. No seu construtor, definimos o seu tamanho, a operação padrão de fechamento e adicionamos a ele um WindowAdapter, que fará o loop principal parar, caso o JFrame seja fechado.
public class BallFrame1 extends JFrame implements LoopSteps {
private MainLoop loop = new MainLoop(this, 60);
private long previous = System.currentTimeMillis();
private Ball ball;
public BallFrame1() {
super("Bouncing ball");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(400, 400);
setResizable(false);
addWindowListener(new WindowAdapter()
{
@Override
public void windowClosing(WindowEvent e) {
//Se apertar o x, paramos o loop.
loop.stop();
}
});
}
Note que a inicialização da bola ainda não foi feita. Deixamos essa atividade para a fase de setup(). No caso dessa aplicação, não faria muita diferença em que fase usar. Mas o setup() ocorre após o frame visível, e geralmente no início de cada fase do jogo. Por isso, ele é o local ideal para inicializarmos nossos objetos do jogo.
Nosso método de setup cria uma nova bola, e fornece a ela o tamanho da área de pintura. Esse tamanho é igual ao tamanho do frame, descontado a largura da decoração de sua lateral (insets). Portanto, nosso método de setup fica assim:
@Override
public void setup() {
//Subtrai a decoração da janela da largura e altura máximas
//percorridas pela bola.
ball = new Ball(getWidth() - getInsets().left - getInsets().right,
getHeight() - getInsets().top - getInsets().bottom);
}
No processamento da lógica, calcularemos quanto tempo transcorreu entre duas chamadas ao update. Note que na criação do JFrame definimos uma variável chamada time. Ela será usada para representar o momento em que o método update() foi chamado pela ultima vez..
Podemos então calcular o tempo o transcorrido subtraindo o tempo atual dessa variável. O resultado será em milissegundos. Em seguida, fazemos a atualização dos sprites do nosso jogo, e atualizamos a variável tempo, para que ela saiba o momento que nosso update terminou. Nosso método processLogics() fica assim:
@Override
public void processLogics() {
//Calcula o tempo entre dois updates
long time = System.currentTimeMillis() - previous;
//Chama o update dos sprites, no caso, só a bola
ball.update(time);
//Grava o tempo na saída do método
previous = System.currentTimeMillis();
}
E o método renderGraphics()? Ainda iremos utilizar a pintura da forma como vimos no Swing, ou seja, através do método paint. Portanto, esse método fica vazio. Veremos no próximo artigo uma técnica chamada pintura direta, que dispensa o método paint(), e então o renderGraphics() será utilizado.
Nosso método paint é muito parecido com o do fantasma do Pacman. Nele, criamos um contexto gráfico, definimos um novo contexto, relativo ao interior da janela, limpamos o fundo, e então chamamos a pintura de todos os sprites do nosso jogo:
@Override
public void paint(Graphics g) {
//Criamos um contexto gráfico que não leva em conta as bordas
g = g.create(getInsets().right,
getInsets().top,
getWidth() - getInsets().left,
getHeight() - getInsets().bottom);
//Limpamos a tela
g.setColor(Color.BLACK);
g.fillRect(0, 0, getWidth(), getHeight());
if (ball != null)
ball.draw((Graphics2D) g); //Desenhamos a bola
g.dispose(); //Liberamos o contexto criado.
}
Finalmente, o método paintScreen(), do nosso gameLoop(), apenas chama repaint(), para que o paint() seja chamado.
Iniciando o loop principal
Está tudo pronto para inicializarmos o loop principal da nossa animação. Para isso, vamos criar no JFrame um método chamado startMainLoop() responsável por essa tarefa:
public void startMainLoop()
{
//Iniciamos o main loop
new Thread(loop, "Main loop").start();
}
Então, no nosso método main, basta criamos o JFrame, deixa-lo visível e chamarmos o método startMainLoop():
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
BallFrame1 bf = new BallFrame1();
bf.setVisible(true);
bf.startMainLoop();
}
});
}
Você pode baixar o código fonte de toda essa aplicação aqui. Deixei a velocidade no eixo y igual a metade dela no eixo x, para que a bola não fique simplesmente percorrendo a diagonal da tela, e faça um movimento mais interessante.
Na sua máquina, a bola poderá não se mover tão suavemente quanto deveria. Não fique muito nervoso! Ainda estamos usando a pintura controlada pelo swing, não por nós mesmos. O Swing está autorizado a ignorar alguns repaints, caso eles cheguem muito rapidamente, como é o nosso caso. E esse problema é especialmente sensível em JApplets. No próximo artigo, varemos como controlar essa pintura por nós mesmos.
-
14/02/2010 19:51:00 |201.81.4.xxx| Marco Biscaro - Impossível

Esse sistema não gosta do "menor ou igual" ele simplesmente trunca o comentário só até essa parte.
Apenas reveja aquele trecho com calma.
-
14/02/2010 22:41:13 | Vinícius Godoy de Mendonça - Caramba

Realmente, eu devia estar dormindo ou bêbado quando fiz aquele trechinho.
Ainda bem que você revisou, obrigado pelo comentário. Já está corrigido.

-
28/09/2010 23:23:16 |201.88.241.xxx| pedro - frame

eu queria saber, se voce pode me ensinar como receber os campos digitados em uma frame(obs.: frame com radio, textfield, combox), para outra frame em uma tabela, e com o botao imprimir..imprimindo os dados digitados, tipo um cadastro
eu ja tenho a frame criada e a outra frame com a tabela.
valew...
-
29/09/2010 00:12:15 | Vinícius Godoy de Mendonça

Sim, mas não aqui. Poste sua dúvida no GUJ.
-
05/05/2011 17:55:26 |187.76.176.xxx| Rodrigo - adicionar canvas em applet

olá pessoal...
tô com uma dúvida aki: como faço pra adicionar uma canvas tipo essa com a animação das bolas dentro de um JDesktopPane que está dentro de um Applet?
é que dentro do JDesktopPane vai ficar ums botões que eu vou implementar ações depois, e a tela que vai correr a animação vai ficar dentro do DesktopPane, desde já agradeço a ajuda, abraços.












Novamente, parabéns pelo belo tutorial.
Só que tem uma coisa... na explicação da colisão da bola a coisa está meio confusa...
A imagem está errada. Deveria ser:
View image
E o texto está bagunçado. Deveria ser:
Esses pontos são:
a) No topo da tela, quando y