0% encontró este documento útil (0 votos)
443 vistas45 páginas

Programacion de Juegos en Java

Este documento explica cómo crear una ventana y un panel en Java para dibujar formas. Describe la clase JFrame para crear ventanas y la clase JPanel como una superficie de dibujo. Explica cómo dibujar formas básicas como círculos y cuadrados en un panel utilizando el método paint().
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
443 vistas45 páginas

Programacion de Juegos en Java

Este documento explica cómo crear una ventana y un panel en Java para dibujar formas. Describe la clase JFrame para crear ventanas y la clase JPanel como una superficie de dibujo. Explica cómo dibujar formas básicas como círculos y cuadrados en un panel utilizando el método paint().
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 45

1

Informacin obtenida en:


http://edu4java.com/es/game/game0.html
Programacin de juegos: JFrame, JPanel, mtodo paint
Para dibujar algo necesitamos una superficie donde pintar. Esta superficie o lienzo (Canvas
en ingls) donde pintaremos nuestro primer ejemplo es un objeto JPanel. As como un
lienzo necesita un marco para sostenerse, nuestro JPanel estar enmarcado en una ventana
modelada por la clase JFrame.

JFrame: La Ventana

El siguiente cdigo crea una ventana con titulo "Mini Tennis" de 300 pixels por 300 pixels.
La ventana no ser visible hasta que llamemos setVisible(true). Si no incluimos la ltima
lnea "frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)", cuando cerremos la
ventana el programa no terminar y seguir ejecutndose.

package com.edu4java.minitennis1;
import javax.swing.JFrame;

public class Game {


public static void main(String[] args) {
JFrame frame = new JFrame("Mini Tennis");
frame.setSize(300, 300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Si ejecutamos obtendremos:

Con estas pocas instrucciones obtenemos una ventana que se puede


maximizar, minimizar, cambiar de tamao con el ratn, etc. En realidad cuando
creamos un objeto JFrame iniciamos un motor que maneja la interfaz de
2

usuario. Este motor se comunica con el sistema operativo tanto para pintar en
la pantalla como para recibir informacin del teclado o el ratn. Llamaremos a
este motor "Motor AWT" o "Motor Swing" ya que est compuesto por estas dos
libreras. En las primeras versiones de java solo exista AWT y luego se agreg
Swing. Este Motor utiliza varios hilos de ejecucin.

Qu es un hilo o thread en java?


Normalmente un programa se ejecuta lnea tras lnea por un solo procesador en una sola
lnea o hilo de ejecucin. El concepto de hilo (en ingles Thread) permite a un programa
iniciar varias ejecuciones concurrentes. Esto es como si existieran varios procesadores
ejecutando al mismo tiempo sus propias secuencias de instrucciones.

Aunque los hilos y la concurrencia son herramientas muy potentes puede traer muchos
problemas como que dos hilos accedan a las mismas variables de forma conflictiva. Es
interesante considerar que dos hilos pueden estar ejecutando el mismo cdigo de un mtodo
a la vez.

Podemos pensar que un hilo es un cocinero preparando un plato leyendo una receta de
cocina. Dos hilos concurrentes seran como dos cocineros trabajando en la misma cocina,
preparando cada uno un plato leyendo cada uno una receta o tambin podran estar leyendo
la misma receta. Los conflictos surgen por ejemplo cuando los dos intentan usar una sartn
al mismo tiempo.

Motor AWT e Hilo de cola de eventos - Thread AWT-EventQueue-0

El Motor AWT inicia varios Hilos (Threads) que podemos ver en la vista Debug si
iniciamos la aplicacin con debug y vamos a la perspectiva Debug. Cada hilo es como si
fuera un programa independiente ejecutndose al mismo tiempo que los otros hilos. Ms
adelante veremos ms sobre hilos, por lo pronto solo me interesa que recuerden el tercer
hilo que vemos en la vista Debug llamado "Thread [AWT-EventQueue-0]" este hilo es el
encargado de pintar la pantalla y recibir los eventos del teclado y el ratn.
3

JPanel: El lienzo (Canvas en ingls)

Para poder pintar necesitamos donde y el donde es un objeto JPanel que incluiremos en la
ventana. Extenderemos la clase JPanel para poder sobrescribir el mtodo paint que es el
mtodo que llamar el Motor AWT para pintar lo que aparece en la pantalla.

package com.edu4java.minitennis1;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Ellipse2D;
import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Game2 extends JPanel {

@Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.RED);
g2d.fillOval(0, 0, 30, 30);
g2d.drawOval(0, 50, 30, 30);
g2d.fillRect(50, 0, 30, 30);
g2d.drawRect(50, 50, 30, 30);

g2d.draw(new Ellipse2D.Double(0, 100, 30, 30));


}

public static void main(String[] args) {


JFrame frame = new JFrame("Mini Tennis");
frame.add(new Game2());
frame.setSize(300, 300);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
4

El mtodo paint recibe por parmetro un objeto Graphics2D que extiende de Graphics.
Graphics es la vieja clase usada por AWT que ha sido reemplazada por Graphics2D que
tiene ms y mejor funcionalidad. El parmetro sigue siendo de tipo Graphics por
compatibilidad pero nosotros siempre utilizaremos Graphics2D por lo que es necesario
crear una variable g2d: "Graphics2D g2d = (Graphics2D) g;". Una vez que tenemos g2d
podemos utilizar todos los mtodos de Graphics2D para dibujar.

Lo primero que hacemos es elegir el color que utilizamos para dibujar:


"g2d.setColor(Color.RED);". Luego dibujamos unos crculos y cuadrados.

Posicionamiento en el lienzo. Coordenadas x e y

Para dibujar algo dentro del lienzo debemos indicar en que posicin comenzaremos a
pintar. Para esto cada punto del lienzo tiene una posicin (x,y) asociada siendo (0,0) el
punto de la esquina superior izquierda.

El primer circulo rojo se pinta con "g2d.fillOval(0, 0, 30, 30)": los primeros dos parmetros
son la posicin (x,y) y luego se indica el ancho y alto. como resultado tenemos un circulo
de 30 pixeles de dimetro en la posicin (0,0).

El circulo vaco se pinta con "g2d.drawOval(0, 50, 30, 30)": el la posicin x=0 (pegado al
margen izquierdo) y la posicin y=50 (50 pixeles ms abajo del margen superior) pinta un
circulo de 30 pixeles de alto y 30 de ancho.

Los rectngulos se pintan con "g2d.fillRect(50, 0, 30, 30)" y "g2d.drawRect(50, 50, 30,
30)" de forma similar a los crculos.
5

Por ltimo "g2d.draw(new Ellipse2D.Double(0, 100, 30, 30))" pinta el ultimo circulo
usando un objeto Ellipse2D.Double.

Existen muchsimos mtodos en Graphics2D. Algunos los veremos en siguientes tutoriales.

Cundo el motor AWT llama al mtodo paint?

El motor AWT llama al mtodo paint cada vez que el sistema operativo le informa que es
necesario pintar el lienzo. Cuando se carga por primera vez la ventana se llama a paint, si
minimizamos y luego recuperamos la ventana se llama a paint, si modificamos el tamao
de la ventana con el ratn se llama a paint.

Podemos comprobar este comportamiento si ponemos un breakpoint en la primer lnea del


mtodo paint y ejecutamos en modo debug.

Es interesante ver que el mtodo paint es ejecutado por el Hilo de cola de eventos (Thread
AWT-EventQueue) que como indicamos antes es el encargado de pintar la pantalla.

Game loop y Animacin de un objeto

En este tutorial veremos como hacer que un crculo se mueva sobre nuestro lienzo. Esta
animacin se consigue pintando el crculo en una posicin y luego borrando y pintando el
crculo en una posicin cercana. El efecto logrado es un crculo en movimiento.

Posicin del crculo


6

Como mencionamos antes cada vez que pintamos debemos definir la posicin (x,y) donde
dibujaremos en este caso el crculo. Para que el crculo se mueva debemos modificar la
posicin (x,y) cada cierto tiempo y volver a pintar el crculo en la nueva posicin.

En nuestro ejemplo mantendremos en dos propiedades llamadas "x" e "y", la posicin


actual de nuestro crculo. Tambin creamos un mtodo moveBall() que incrementar en 1
tanto a "x" como a "y" cada vez que es llamado. En el mtodo paint dibujamos un circulo
de 30 pixeles de dimetro en la posicin (x,y) dada por las propiedades antes mencionadas
"g2d.fillOval(x, y, 30, 30);".

Game loop

Al final del mtodo main iniciamos un ciclo infinito "while (true)" donde repetidamente
llamamos a moveBall() para cambiar la posicin del circulo y luego llamamos a repaint()
que fuerza al motor AWT a llamar al mtodo paint para repintar el lienzo.

Este ciclo o repeticin se conoce como "Game loop" y se caracteriza por realizar dos
operaciones:

1. Actualizacin (Update): actualizacin de la fsica de nuestro mundo.


En nuestro caso nuestra actualizacin esta dada tan solo por el mtodo
moveBall() que incrementa las propiedades "x" e "y" en 1.

2. Renderizado (Render): aqu se dibuja segn el estado actual de


nuestro mundo reflejando los cambios realizados en el paso anterior. En
nuestro ejemplo este renderizado esta dado por la llamada a repaint() y
la subsecuente llamada a paint realizada por el motor AWT o ms
especficamente por el Hilo de cola de eventos.

3. package com.edu4java.minitennis2;
4.
5. import java.awt.Graphics;
6. import java.awt.Graphics2D;
7. import java.awt.RenderingHints;
8. import javax.swing.JFrame;
9. import javax.swing.JPanel;
10.
11. @SuppressWarnings("serial")
12. public class Game extends JPanel {
13.
14. int x = 0;
15. int y = 0;
16.
17. private void moveBall() {
18. x = x + 1;
19. y = y + 1;
20. }
21.
22. @Override
7

23. public void paint(Graphics g) {


24. super.paint(g);
25. Graphics2D g2d = (Graphics2D) g;
26.
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
27. RenderingHints.VALUE_ANTIALIAS_ON);
28. g2d.fillOval(x, y, 30, 30);
29. }
30.
31. public static void main(String[] args) throws
InterruptedException {
32. JFrame frame = new JFrame("Mini Tennis");
33. Game game = new Game();
34. frame.add(game);
35. frame.setSize(300, 400);
36. frame.setVisible(true);
37.
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
38.
39. while (true) {
40. game.moveBall();
41. game.repaint();
42. Thread.sleep(10);
43. }
44. }
45. }

Analizando nuestro mtodo paint

Como mencionamos en el tutorial anterior este mtodo se ejecuta cada vez que el sistema
operativo le indica a Motor AWT que es necesario pintar el lienzo. Si ejecutamos el mtodo
repaint() de un objeto JPanel lo que estamos haciendo es decirle al Motor AWT que ejecute
8

el mtodo paint tan pronto como pueda. La llamada a paint la realizar el Hilo de cola de
eventos. Llamando a repaint() logramos que se repinte el lienzo y as poder reflejar el
cambio en la posicin del circulo.

@Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.fillOval(x, y, 30, 30);
}
La llamada a "super.paint(g)" limpia la pantalla, si comentamos esta lnea podemos ver el
siguiente efecto:

La instruccin "g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON)" suaviza los bordes de las figuras como se
puede ver en el siguiente grfico. El crculo de la izquierda es sin aplicar ANTIALIAS y el
de la derecha aplicando ANTIALIAS.
9

Analizando la concurrencia y el comportamiento de los hilos

Cuando se inicia la ejecucin del mtodo main slo existe un hilo en ejecucin. Esto se
puede ver colocando un breakpoint en la primera lnea del mtodo main.

Si agregamos un breakpoint en la lnea game.repaint() y en la primera lnea del mtodo


paint y a continuacin oprimimos F8 (Resume: ordena que contine la ejecucin hasta el
final o hasta que encuentre el prximo breakpoint) obtendremos:
10

En la vista de la izquierda podemos ver que se han creado cuatro hilos de los cuales dos
estn detenidos en breakpoints. El Thread main est detenido en la lnea 40 en la
instruccin game.repaint(). El thread AWT-EventQueue est detenido en el mtodo paint en
la lnea 22.

Si seleccionamos el thread AWT-EventQueue en la vista Debug y oprimimos F8


repetidamente (2 veces) veremos que no se detiene ms en el metodo paint. Esto es porque
el sistema operativo no ve motivo para solicitar un repintado del lienzo una vez
inicializado.
11

Si oprimimos F6 (avanza la ejecucin del hilo slo una lnea), esta vez sobre el thread
main, veremos que el mtodo paint es vuelto a llamar por el thread AWT-EventQueue.
Ahora sacamos el breakpoint del mtodo paint, oprimimos F8 y volvemos a tener slo
detenido el thread main.

La siguiente animacin nos muestra que pasa en el lienzo cada vez que oprimimos resume
(F8) repetidamente. Cada llamada a moveBall() incrementa la posicin (x,y) del crculo y la
llamada a repaint() le dice al thread AWT-EventQueue que repinte el lienzo.

Por ltimo analicemos la lnea "Thread.sleep(10)" (la ltima instruccin dentro del "Game
loop"). Para esto comentamos la lnea con // y ejecutamos sin debug. El resultado es que no
se pinta el crculo en el lienzo. Por qu pasa esto? Esto es debido a que el thread main se
apodera del procesador y no lo comparte con el thread AWT-EventQueue que entonces no
puede llamar al mtodo paint.
12

"Thread.sleep(10)" le dice al procesador que el thread que se est ejecutando descanse por
10 milisegundos lo que permite que el procesador ejecute otros threads y en particular el
thread AWT-EventQueue que llama al mtodo paint.

Me gustara aclarar que en este ejemplo la solucin planteada es muy pobre y slo pretende
ilustrar los conceptos de "game loop", threads y concurrencia. Existen mejores formas de
manejar el game loop y la concurrencia en un juego y las veremos en los prximos
tutoriales.

Sprites - Velocidad y direccin

Cada objeto que se mueve en la pantalla tiene caractersticas propias como la posicin
(x,y), la velocidad y la direccin en que se mueve, etc. Todas estas caractersticas se pueden
aislar en un objeto que llamaremos Sprite.

Velocidad y direccin

En el tutorial anterior logramos que la pelota (el crculo) se moviera hacia abajo y a la
derecha a un pxel por vuelta en el Game Loop. Cuando llegaba al limite de la pantalla la
pelota segua su curso desapareciendo del lienzo. Lo que haremos a continuacin es que la
pelota rebote en los limites del lienzo cambiando su direccin.

package com.edu4java.minitennis3;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Game extends JPanel {

int x = 0;
int y = 0;
int xa = 1;
int ya = 1;

private void moveBall() {


if (x + xa < 0)
xa = 1;
if (x + xa > getWidth() - 30)
xa = -1;
if (y + ya < 0)
ya = 1;
if (y + ya > getHeight() - 30)
ya = -1;

x = x + xa;
y = y + ya;
13

@Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.fillOval(x, y, 30, 30);

public static void main(String[] args) throws InterruptedException


{
JFrame frame = new JFrame("Mini Tennis");
Game game = new Game();
frame.add(game);
frame.setSize(300, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

while (true) {
game.moveBall();
game.repaint();
Thread.sleep(10);
}
}
}
En el cdigo anterior se agregaron dos propiedades "xa" y "ya" que representan la
velocidad en que se mueve la pelota. Si xa=1, la pelota se mueve hacia la derecha a un pxel
por vuelta del Game Loop y si xa=-1, la pelota se mueve hacia la izquierda. Similarmente
ya=1 mueve hacia abajo y ya=-1 mueve hacia arriba. Esto lo logramos con las lneas "x = x
+ xa" e "y = y + ya" del mtodo moveBall().

Antes de ejecutar las instrucciones anteriores verificamos que la pelota no salga de los
mrgenes del lienzo. Por ejemplo cuando la pelota alcance el margen derecho o lo que es lo
mismo cuando (x + xa > getWidth() - 30) lo que haremos es cambiar la direccin del
movimiento sobre el eje x o lo que es lo mismo asignar menos uno a xa "xa = -1".

private void moveBall() {


if (x + xa < 0)
xa = 1;
if (x + xa > getWidth() - 30)
xa = -1;
if (y + ya < 0)
ya = 1;
if (y + ya > getHeight() - 30)
ya = -1;

x = x + xa;
y = y + ya;

}
14

Cada sentencia if limita un borde del lienzo.

Crear el Sprite Ball (pelota en ingls)

La idea es crear una clase llamada Ball que aisle todo lo referente a la pelota. En el
siguiente cdigo podemos ver como extraemos todo el cdigo referente a la pelota de la
clase Game2 y lo incorporamos a nuestra nueva clase Ball.

package com.edu4java.minitennis3;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Game2 extends JPanel {

Ball ball = new Ball(this);

private void move() {


ball.move();
}

@Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
ball.paint(g2d);
}

public static void main(String[] args) throws InterruptedException


{
JFrame frame = new JFrame("Mini Tennis");
Game2 game = new Game2();
frame.add(game);
frame.setSize(300, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

while (true) {
game.move();
game.repaint();
Thread.sleep(10);
}
}
}
El Sprite Ball necesita que le enven una referencia al objeto Game para obtener los limites
del lienzo y as saber cuando debe cambiar de direccin. En el mtodo move() de la clase
Ball se llama a game.getWidth() y game.getHeight().
15

package com.edu4java.minitennis3;

import java.awt.Graphics2D;

public class Ball {


int x = 0;
int y = 0;
int xa = 1;
int ya = 1;
private Game2 game;

public Ball(Game2 game) {


this.game= game;
}

void move() {
if (x + xa < 0)
xa = 1;
if (x + xa > game.getWidth() - 30)
xa = -1;
if (y + ya < 0)
ya = 1;
if (y + ya > game.getHeight() - 30)
ya = -1;

x = x + xa;
y = y + ya;
}

public void paint(Graphics2D g) {


g.fillOval(x, y, 30, 30);
}
}
Si ejecutamos Game2 obtendremos el mismo resultado que si ejecutamos la versin
anterior Game. La conveniencia de esta separacin del cdigo referente a la pelota en una
clase de tipo Sprite se vuelve ms obvia cuando incluimos la raqueta mediante un nuevo
Sprite en un prximo tutorial.

Eventos. Capturando las entrada por teclado


En este tutorial veremos como funcionan los eventos y en particular como obtener la
informacin acerca de los eventos producidos en el teclado desde un programa java.
Adems explicaremos el concepto y uso de clases annimas que es el mtodo ms
comnmente usado para manejar eventos en java. Abandonaremos nuestro juego
momentneamente y haremos un simple ejemplo de captura de eventos.

Ejemplo de lectura del teclado

Para leer del teclado es necesario registrar un objeto que se encargue de "escuchar si una
tecla es presionada". Este objeto conocido como "Listener" u "oyente" y tendr mtodos
que sern llamados cuando alguien presione una tecla. En nuestro ejemplo el Listener se
16

registra en el JPanel (o KeyboardExample) usando el mtodo addKeyListener(KeyListener


listener).

package com.edu4java.minitennis4;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class KeyboardExample extends JPanel {

public KeyboardExample() {
KeyListener listener = new MyKeyListener();
addKeyListener(listener);
setFocusable(true);
}

public static void main(String[] args) {


JFrame frame = new JFrame("Mini Tennis");
KeyboardExample keyboardExample = new KeyboardExample();
frame.add(keyboardExample);
frame.setSize(200, 200);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}

public class MyKeyListener implements KeyListener {


@Override
public void keyTyped(KeyEvent e) {
}

@Override
public void keyPressed(KeyEvent e) {

System.out.println("keyPressed="+KeyEvent.getKeyText(e.getKeyCode()));
}

@Override
public void keyReleased(KeyEvent e) {

System.out.println("keyReleased="+KeyEvent.getKeyText(e.getKeyCode()));
}
}
}
En el constructor de la clase KeyboardExample creamos el listener y lo registramos. Para
que un objeto JPanel reciba las notificaciones del teclado es necesario incluir la instruccin
setFocusable(true) que permite que KeyboardExample reciba el foco.

public KeyboardExample() {
KeyListener listener = new MyKeyListener();
17

addKeyListener(listener);
setFocusable(true);
}
La clase MyKeyListener es la que uso para crear el objeto Listener. Este Listener imprimir
en la consola el nombre del mtodo y la tecla afectada por el evento.

public class MyKeyListener implements KeyListener {


@Override
public void keyTyped(KeyEvent e) {
}

@Override
public void keyPressed(KeyEvent e) {

System.out.println("keyPressed="+KeyEvent.getKeyText(e.getKeyCode()));
}

@Override
public void keyReleased(KeyEvent e) {

System.out.println("keyReleased="+KeyEvent.getKeyText(e.getKeyCode()));
}
}
Una vez registrado, cuando KeyboardExample (nuestro JPanel) tenga el foco y alguien
oprima una tecla KeyboardExample informar al objeto listener registrado. El objeto
Listener de nuestro ejemplo implementa la interfaz KeyListener que tiene los mtodos
keyTyped(), keyPressed() y keyReleased(). El mtodo keyPressed ser llamado cada vez
que una tecla sea oprimida (y varias veces si se mantiene oprimida). El mtodo
keyReleased ser llamado cuando solemos una tecla.

Los mtodos antes mencionados reciben como parmetro un objeto KeyEvent que contiene
informacin sobre que tecla se ha oprimido o soltado. Usando e.getKeyCode() podemos
obtener el cdigo de la tecla y si le pasamos un cdigo de tecla a la funcin estatica
KeyEvent.getKeyText(...) podemos obtener el texto asociado a la tecla.

Cmo funcionan los eventos en AWT/Swing?

Lo eventos del ratn y el teclado son controlados por el sistema operativo. El motor AWT,
en particular el thread AWT-Windows se comunica con el sistema operativo y se entera de
si hubo un evento. Cuando encuentra un nuevo evento lo coloca en la "Cola de Eventos"
para que sea atendido cuando le llegue su turno por el Thread AWT-EventQueue.
18

Cuando el Thread AWT-EventQueue atiende a un evento se fija a que componente afecta y


le informa. En nuestro caso el componente es el JPanel que informa a todos los listeners
que se hayan registrado para recibir notificaciones de ese evento.

En el caso del teclado la llamada addKeyListener(KeyListener listener) es la que realiza


este registro. Si queremos registrar un objeto para escuchar los eventos del ratn podemos
usar addMouseListener(MouseListener listener).

Si quieren profundizar en como funcionan los eventos en AWT/Swing les recomiendo el


siguiente artculo.

Clase annima

En el ejemplo anterior la clase MyKeyListener ser solo usada una vez por lo que
podramos reemplazarla por una clase annima. KeyboardExample2 muestra como sera:

package com.edu4java.minitennis4;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class KeyboardExample2 extends JPanel {

public KeyboardExample2() {
KeyListener listener = new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}

@Override
public void keyPressed(KeyEvent e) {

System.out.println("keyPressed="+KeyEvent.getKeyText(e.getKeyCode()));
19

@Override
public void keyReleased(KeyEvent e) {

System.out.println("keyReleased="+KeyEvent.getKeyText(e.getKeyCode()));
}
};
addKeyListener(listener);
setFocusable(true);
}

public static void main(String[] args) {


JFrame frame = new JFrame("Mini Tennis");
KeyboardExample2 keyboardExample = new KeyboardExample2();
frame.add(keyboardExample);
frame.setSize(200, 200);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
En el constructor de la clase KeyboardExample2 podemos ver como se reemplaza

KeyListener listener = new MyKeyListener();


por

KeyListener listener = new KeyListener() {


@Override
public void keyTyped(KeyEvent e) {
}

@Override
public void keyPressed(KeyEvent e) {

System.out.println("keyPressed="+KeyEvent.getKeyText(e.getKeyCode()));
}

@Override
public void keyReleased(KeyEvent e) {

System.out.println("keyReleased="+KeyEvent.getKeyText(e.getKeyCode()));
}
};
Esta instruccin tiene el mismo efecto que la anterior. Reemplaza la definicin de la clase
MyKeyListener por una clase annima que hace exactamente lo mismo.

La forma de crear un objeto desde una clase annima es reemplazar el nombre de la clase a
crear por una definicin que empieza por la interfaz a implementar seguida por () y luego
dentro de {} la definicin de la clase como hacemos normalmente.
20

Aunque parezca un poco extrao esta es la forma ms cmoda de implementar Listeners de


eventos y es la forma que ms encontrarn en cdigo java avanzado.

Agregando el sprite raqueta


En este tutorial agregaremos la raqueta mediante un Sprite llamado Racquet. La raqueta se
mover hacia la izquierda o derecha cuando oprimamos las teclas del cursor por lo que
nuestro programa necesita leer del teclado.

Nuevo Sprite Racquet

Lo primero que hacemos es agregar en la clase Game una nueva propiedad llamada racquet
donde mantendremos el Sprite que maneja la raqueta. En el mtodo move() aadimos una
llamada a racquet.move() y en paint() una llamada a racquet.paint(). Hasta ahora todo es
similar al sprite Ball pero como la posicin de la raqueta responde al teclado tenemos que
hacer algo ms.

package com.edu4java.minitennis5;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Game extends JPanel {

Ball ball = new Ball(this);


Racquet racquet = new Racquet(this);

public Game() {
addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}

@Override
public void keyReleased(KeyEvent e) {
racquet.keyReleased(e);
}

@Override
public void keyPressed(KeyEvent e) {
racquet.keyPressed(e);
}
});
setFocusable(true);
}
21

private void move() {


ball.move();
racquet.move();
}

@Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
ball.paint(g2d);
racquet.paint(g2d);
}

public static void main(String[] args) throws InterruptedException


{
JFrame frame = new JFrame("Mini Tennis");
Game game = new Game();
frame.add(game);
frame.setSize(300, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

while (true) {
game.move();
game.repaint();
Thread.sleep(10);
}
}
}
En el constructor de la clase game se puede ver como se registra un listener para capturar
los eventos del teclado. En el mtodo keyPressed() del listener informamos a la raqueta que
una tecla ha sido oprimida llamando a racquet.keyPressed(e). Lo mismo hacemos para
keyReleased(). Con esto el Sprite racquet se enterar cuando una tecla sea oprimida.
Veamos ahora las clases Ball y Racquet que implementan los sprites

package com.edu4java.minitennis5;

import java.awt.Graphics2D;

public class Ball {


int x = 0;
int y = 0;
int xa = 1;
int ya = 1;
private Game game;

public Ball(Game game) {


this.game= game;
}

void move() {
22

if (x + xa < 0)
xa = 1;
if (x + xa > game.getWidth() - 30)
xa = -1;
if (y + ya < 0)
ya = 1;
if (y + ya > game.getHeight() - 30)
ya = -1;

x = x + xa;
y = y + ya;
}

public void paint(Graphics2D g) {


g.fillOval(x, y, 30, 30);
}
}
La clase Ball no tiene cambios. Comparmosla con la clase Racquet:

package com.edu4java.minitennis5;

import java.awt.Graphics2D;
import java.awt.event.KeyEvent;

public class Racquet {


int x = 0;
int xa = 0;
private Game game;

public Racquet(Game game) {


this.game= game;
}

public void move() {


if (x + xa > 0 && x + xa < game.getWidth()-60)
x = x + xa;
}

public void paint(Graphics2D g) {


g.fillRect(x, 330, 60, 10);
}

public void keyReleased(KeyEvent e) {


xa = 0;
}

public void keyPressed(KeyEvent e) {


if (e.getKeyCode() == KeyEvent.VK_LEFT)
xa = -1;
if (e.getKeyCode() == KeyEvent.VK_RIGHT)
xa = 1;
}
}

A diferencia de Ball, Racquet no tiene propiedades para la posicin "y" ni la velocidad


"ya". Esto es debido a que la raqueta no variar su posicin vertical, solo se mover hacia la
23

izquierda o derecha, nunca hacia arriba o abajo. En el mtodo paint la instruccin


g.fillRect(x, 330, 60, 10) define un rectngulo de 60 por 10 pixeles en la posicin
(x,y)=(x,330). Como vemos "x" puede variar pero "y" est fijada a 330 pixeles del limite
superior del lienzo.

El mtodo move() es similar al de Ball en el sentido de incrementar en "xa" la posicin "x"


y controlar que el sprite no se salga de los limites.

public void move() {


if (x + xa > 0 && x + xa < game.getWidth()-60)
x = x + xa;
}

Inicialmente el valor de "x" es cero lo que indica que la raqueta estar en el limite izquierdo
del lienzo. "xa" tambin est inicializado a cero, lo que hace que en principio la raqueta
aparezca esttica ya que x = x + xa no modificar "x" mientras "xa" sea cero.

Cuando alguien presione una tecla el mtodo keyPressed de Racquet ser llamado y este
pondr "xa" en 1 si la tecla presionada es la de direccin derecha (KeyEvent.VK_RIGHT)
lo que a su vez har que la raqueta se mueva a la derecha la prxima vez que se llame al
mtodo move (recordar x = x + xa). De la misma forma si se presiona la tecla
KeyEvent.VK_LEFT se mover a la izquierda.

public void keyPressed(KeyEvent e) {


if (e.getKeyCode() == KeyEvent.VK_LEFT)
xa = -1;
if (e.getKeyCode() == KeyEvent.VK_RIGHT)
xa = 1;
}

Cuando una tecla deja de ser presionada el mtodo keyReleased es llamado y "xa" pasa a
valer cero lo que hace que el movimiento de la raqueta se detenga.

public void keyReleased(KeyEvent e) {


xa = 0;
}

Si ejecutamos el ejemplo podemos ver como la pelota se mueve rebotando contra los
lmites y la raqueta se mueve cuando presionamos las teclas de direccin correspondientes.
Pero cuando la pelota choca con la raqueta la atraviesa pareciendo como si esta no
existiese. En el prximo tutorial veremos como hacer que la pelota rebote sobre la raqueta.

Deteccin de colisiones
En este tutorial aprenderemos como detectar cuando un sprite choca con otro. En nuestro
juego haremos que la pelota rebote contra la raqueta. Adems haremos que el juego termine
si la pelota alcanza el limite inferior del lienzo mostrando una ventana popup con el clsico
mensaje "Game Over".
24

Game Over

A continuacin vemos nuestra clase Game que es idntica a la anterior con la sola
diferencia de que se ha agregado el mtodo gameOver();

package com.edu4java.minitennis6;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Game extends JPanel {

Ball ball = new Ball(this);


Racquet racquet = new Racquet(this);

public Game() {
addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}

@Override
public void keyReleased(KeyEvent e) {
racquet.keyReleased(e);
}

@Override
public void keyPressed(KeyEvent e) {
racquet.keyPressed(e);
}
});
setFocusable(true);
}

private void move() {


ball.move();
racquet.move();
}

@Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
ball.paint(g2d);
racquet.paint(g2d);
}
25

public void gameOver() {


JOptionPane.showMessageDialog(this, "Game Over", "Game
Over", JOptionPane.YES_NO_OPTION);
System.exit(ABORT);
}

public static void main(String[] args) throws InterruptedException


{
JFrame frame = new JFrame("Mini Tennis");
Game game = new Game();
frame.add(game);
frame.setSize(300, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

while (true) {
game.move();
game.repaint();
Thread.sleep(10);
}
}
}
El mtodo gameOver() lanza un popup usando JOptionPane.showMessageDialog con el
mensaje "Game Over" y un solo botn "Aceptar". Despus del popup,
System.exit(ABORT) hace que se termine el programa. El mtodo gameOver() es pblico
ya que ser llamado desde el sprite Ball cuando detecte que ha llegado al lmite inferior del
lienzo.

Colisin de Sprites

Para detectar la colisin entre la pelota y la raqueta usaremos rectngulos. El caso de la


pelota crearemos un cuadrado alrededor de la pelota como se ve el la figura 2.
26

La clase java.awt.Rectangle tiene un mtodo intersects(Rectangle r) que retorna true


cuando dos rectngulos ocupan el mismo espacio como en el caso de la figura 3 o 4. Cabe
destacar que este mtodo no es exacto ya que en la figura 4 la pelota no toca a la raqueta
pero para nuestro ejemplo ser ms que suficiente.

A continuacin vemos la clase Racquet donde el nico cambio funcional es que se ha


agregado el mtodo getBounds() que retorna un objeto de tipo rectngulo indicando la
posicin de la raqueta. Este mtodo ser usado por el sprite Ball para saber la posicin de la
raqueta y as detectar la colisin.

package com.edu4java.minitennis6;

import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;

public class Racquet {


private static final int Y = 330;
private static final int WITH = 60;
private static final int HEIGHT = 10;
int x = 0;
int xa = 0;
private Game game;

public Racquet(Game game) {


this.game = game;
}

public void move() {


if (x + xa > 0 && x + xa < game.getWidth() - WITH)
x = x + xa;
27

public void paint(Graphics2D g) {


g.fillRect(x, Y, WITH, HEIGHT);
}

public void keyReleased(KeyEvent e) {


xa = 0;
}

public void keyPressed(KeyEvent e) {


if (e.getKeyCode() == KeyEvent.VK_LEFT)
xa = -1;
if (e.getKeyCode() == KeyEvent.VK_RIGHT)
xa = 1;
}

public Rectangle getBounds() {


return new Rectangle(x, Y, WITH, HEIGHT);
}

public int getTopY() {


return Y - HEIGHT;
}
}

Otro cambio que funcionalmente no afecta pero que es una buena prctica de programacin
es la inclusin de constantes:

private static final int Y = 330;


private static final int WITH = 60;
private static final int HEIGH = 20;

Como antes mencionamos el valor de posicin "y" estaba fijo en 330. Este valor es usado
tanto en el mtodo paint como en getBounds. Si queremos cambiarlo ahora slo tenemos
que cambiarlo en un slo lugar evitando el posible error que se producira si lo
cambiramos en un mtodo y en otro no.

La forma de definir una constante en java es declarando una propiedad "static final" y en
maysculas. El compilador permite usar minsculas pero el estndar dice que se deben usar
maysculas para los nombres de las constantes.

Por ltimo la clase Ball:

package com.edu4java.minitennis6;

import java.awt.Graphics2D;
import java.awt.Rectangle;

public class Ball {


private static final int DIAMETER = 30;
int x = 0;
int y = 0;
28

int xa = 1;
int ya = 1;
private Game game;

public Ball(Game game) {


this.game= game;
}

void move() {
if (x + xa < 0)
xa = 1;
if (x + xa > game.getWidth() - DIAMETER)
xa = -1;
if (y + ya < 0)
ya = 1;
if (y + ya > game.getHeight() - DIAMETER)
game.gameOver();
if (collision()){
ya = -1;
y = game.racquet.getTopY() - DIAMETER;
}
x = x + xa;
y = y + ya;
}

private boolean collision() {


return game.racquet.getBounds().intersects(getBounds());
}

public void paint(Graphics2D g) {


g.fillOval(x, y, DIAMETER, DIAMETER);
}

public Rectangle getBounds() {


return new Rectangle(x, y, DIAMETER, DIAMETER);
}
}

De forma similar a la clase Racquet se ha incluido el mtodo getBounds() y la constante


DIAMETER.

Ms interesante es la aparicin de un nuevo mtodo llamado collision() que retorna true


(verdadero) si el rectngulo ocupado por la raqueta "game.racquet.getBounds()" intersecta
al rectngulo que encierra a la pelota "getBounds()".

private boolean collision() {


return game.racquet.getBounds().intersects(getBounds());
}

Si la colisin se produce, adems de cambiar la direccin ajustaremos la posicin de la


pelota. Si la colisin es por el lado (figura 1), la pelota podra estar varios pixeles por
debajo de la cara superior de la raqueta. En el siguiente game loop aunque la pelota se
movera hacia arriba (figura 2) podra todava estar en colisin con la raqueta.
29

Para evitar esto colocamos a la pelota sobre la raqueta (figura 3) mediante:

y = game.racquet.getTopY() - DIAMETER;

El mtodo getTopY() de Racquet nos da la posicin en el eje y de la parte superior de la


raqueta y restando DIAMETER conseguimos la posicin y exacta donde colocar la pelota
para que est sobre la raqueta.

Por ltimo es el mtodo move() de la clase Ball el que usa los nuevos mtodos collision() y
gameOver() de la clase Game. El rebote al alcanzar el lmite inferior ha sido reemplazado
por una llamada a game.gameOver().

if (y + ya > game.getHeight() - DIAMETER)


game.gameOver();

Y poniendo un nuevo condicional usando el mtodo collision() logramos que la pelota


rebote hacia arriba si esta colisiona con la raqueta:

if (collision())
ya = -1;

Agregando sonido a nuestro juego


Un juego sin sonido no est completo. En este tutorial agregaremos msica de fondo, el
ruido del rebote de la pelota y un "Game Over" con voz graciosa al terminar el juego. Para
evitar problemas de copyright vamos a crear nosotros mismos los sonidos.

Creando sonidos

Para crear los sonidos me tom la libertad de buscar en Google "free audio editor" y como
respuesta encontr http://free-audio-editor.com/. Tengo que decir que la versin gratis de
este producto es potente y fcil de manejar.
30

Con este editor he creado los archivos: back.wav, gameover.wav y ball.wav. En el video de
youtube pueden ver como lo hice y crearlos ustedes mismos. Tambin pueden descargar y
usar estos tres que en esta misma lnea los declaro libres de copyright. Lo que tienen que
hacer es copiar estos archivos al paquete com.edu4java.minitennis7.

Reproducir sonidos usando AudioClip

Para reproducir los archivos de sonido usaremos la clase AudioClip. Crearemos objetos
AudioClip usando el mtodo esttico de la clase Applet: Applet.newAudioClip(URL url).
Este mtodo necesita un objeto URL que le indique donde est el archivo de audio que
queremos cargar para luego reproducir. La siguiente instruccin crea un objeto URL
utilizando una ubicacin en Internet:

URL url = new URL("http://www.edu4java.com/es/game/sound/back.wav");


31

La siguiente utiliza un directorio dentro del sistema de archivos local:

URL url = new


URL("file:/C:/eclipseClasic/workspace/minitennis/src/com/edu4java/miniten
nis7/back.wav");

Nosotros buscaremos nuestro archivo utilizando el classpath. Este es el sistema que usa
java para cargar las clases o mejor dicho los archivos *.class que definen las clases del
programa. Para obtener un URL desde el classpath se utiliza el mtodo getResource(String
name) de la clase Class donde name es el nombre del archivo que queremos obtener.

A continuacin vemos dos formas de como conseguir el URL del archivo "back.wav" que
est en el mismo paquete que la clase SoundTest o lo que es lo mismo en el mismo
directorio donde esta el archivo SoundTest.class.

URL url = SoundTest.class.getResource("back.wav");

URL url = new SoundTest().getClass().getResource("back.wav");

Tanto "SoundTest.class" como "new SoundTest().getClass()" nos dan un objeto class que
tiene el mtodo getResource que queremos usar.

He creado la clase SoundTest con el slo propsito de mostrarles como trabaja AudioClip y
no es necesaria para nuestro juego. A continuacin se muestra el cdigo fuente de
SoundTest completo:

package com.edu4java.minitennis7;

import java.applet.Applet;
import java.applet.AudioClip;
import java.net.URL;

public class SoundTest {


public static void main(String[] args) throws Exception {

// System.out.println("1");
// URL url = new
URL("http://www.edu4java.com/es/game/sound/back.wav");
// System.out.println("2");
// AudioClip clip = Applet.newAudioClip(url);
// System.out.println("3");
// clip.play();
// System.out.println("4");
// Thread.sleep(1000);

// URL url = new URL(


// "file:/C:/eclipseClasic/workspace/minitennis/src/c
om/edu4java/minitennis7/back.wav");

URL url = SoundTest.class.getResource("back.wav");


AudioClip clip = Applet.newAudioClip(url);
AudioClip clip2 = Applet.newAudioClip(url);
32

clip.play();
Thread.sleep(1000);
clip2.loop();
Thread.sleep(20000);
clip2.stop();

System.out.println("end");
}
}

De esta forma el archivo back.wav se obtienen desde el classpath. El classpath es el


conjunto de directorios y archivos *.jar desde donde nuestro programa puede leer las clases
(archivos *.class).

Una ventaja de esta metodologa es que slo tenemos que indicar la posicin del archivo
con respecto a la clase que lo usa. En nuestro caso como est en el mismo paquete basta
con el nombre "back.wav". Otra ventaja es que los archivos de sonido se pueden incluir en
un archivo *.jar. Veremos ms sobre archivos *.jar ms adelante. Una ves que tenemos el
objeto URL podemos crear objetos AudioClip usando Applet.newAudioClip(url).

AudioClip clip = Applet.newAudioClip(url);


AudioClip clip2 = Applet.newAudioClip(url);

El objeto AudioClip tiene un mtodo play() que inicia un thread independiente que
reproduce slo una vez el audio contenido en el archivo. Para reproducir el audio en forma
repetitiva podemos usar el mtodo loop() de AudioClip que reproducir el sonido una y otra
vez hasta que se llame al mtodo stop sobre el mismo objeto AudioClip.

Dos audioClips pueden reproducirse al mismo tiempo. En el ejemplo creo dos audioClips
con el mismo audio: clip y clip2. Reproduzco clip con play, espero un segundo
Thread.sleep(1000) y reproduzco clip2 con loop. El resultado es una mezcla de los dos
audios. Por ultimo despus de 20 segundos Thread.sleep(20000) llamo a clip2.stop() y
detengo la repeticin de clip2.

Creando una clase Sound para nuestro juego


Para guardar los audioclips de nuestro juego creamos una clase Sound que tendr una
constante con un audioclip por cada sonido que usemos. Estas constantes son pblicas para
que cualquier objeto que tenga acceso a ellas pueda reproducirlas. Por ejemplo en la clase
Ball podemos reproducir el sonido del rebote de la pelota usando Sound.BALL.play() en el
momento que detectamos que la pelota cambia de direccin.

package com.edu4java.minitennis7;

import java.applet.Applet;
import java.applet.AudioClip;

public class Sound {


33

public static final AudioClip BALL =


Applet.newAudioClip(Sound.class.getResource("ball.wav"));
public static final AudioClip GAMEOVER =
Applet.newAudioClip(Sound.class.getResource("gameover.wav"));
public static final AudioClip BACK =
Applet.newAudioClip(Sound.class.getResource("back.wav"));
}

Los objetos audioclips se crearn al cargarse la clase Sound la primera vez que alguien use
la clase Sound. A partir de este momento sern reutilizados una y otra vez. Ahora veamos
las modificaciones en la clase Game:

package com.edu4java.minitennis7;

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Game extends JPanel {

Ball ball = new Ball(this);


Racquet racquet = new Racquet(this);

public Game() {
addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}

@Override
public void keyReleased(KeyEvent e) {
racquet.keyReleased(e);
}

@Override
public void keyPressed(KeyEvent e) {
racquet.keyPressed(e);
}
});
setFocusable(true);
Sound.BACK.loop();
}

private void move() {


ball.move();
racquet.move();
}

@Override
public void paint(Graphics g) {
34

super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
ball.paint(g2d);
racquet.paint(g2d);
}

public void gameOver() {


Sound.BACK.stop();
Sound.GAMEOVER.play();
JOptionPane.showMessageDialog(this, "Game Over", "Game
Over", JOptionPane.YES_NO_OPTION);
System.exit(ABORT);
}

public static void main(String[] args) throws InterruptedException


{
JFrame frame = new JFrame("Mini Tennis");
Game game = new Game();
frame.add(game);
frame.setSize(300, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

while (true) {
game.move();
game.repaint();
Thread.sleep(10);
}
}
}

En la ltima lnea del constructor de la clase Game aadimos Sound.BACK.loop(), lo que


iniciar la reproduccin de nuestra msica de fondo que se repetir hasta que se alcance el
mtodo gameOver(), donde detenemos la msica de fondo con Sound.BACK.stop(). A
continuacin de Sound.BACK.stop() y antes del popup informamos que se termino la
partida reproduciendo "Game Over" Sound.GAMEOVER.play().

En la clase Ball modificamos el mtodo move() para que se reproduzca Sound.BALL


cuando la pelota rebote.

package com.edu4java.minitennis7;

import java.awt.Graphics2D;
import java.awt.Rectangle;

public class Ball {


private static final int DIAMETER = 30;

int x = 0;
int y = 0;
int xa = 1;
int ya = 1;
35

private Game game;

public Ball(Game game) {


this.game = game;
}

void move() {
boolean changeDirection = true;
if (x + xa < 0)
xa = 1;
else if (x + xa > game.getWidth() - DIAMETER)
xa = -1;
else if (y + ya < 0)
ya = 1;
else if (y + ya > game.getHeight() - DIAMETER)
game.gameOver();
else if (collision()){
ya = -1;
y = game.racquet.getTopY() - DIAMETER;
} else
changeDirection = false;

if (changeDirection)
Sound.BALL.play();
x = x + xa;
y = y + ya;
}

private boolean collision() {


return game.racquet.getBounds().intersects(getBounds());
}

public void paint(Graphics2D g) {


g.fillOval(x, y, DIAMETER, DIAMETER);
}

public Rectangle getBounds() {


return new Rectangle(x, y, DIAMETER, DIAMETER);
}
}

Lo que hice en move() es agregar una variable changeDirection que inicializo a true.
Aadiendo un else a cada if y colocando un changeDirection = false que slo se ejecutar si
ninguna condicin en los if es cumplida, conseguimos enterarnos si la bola ha rebotado. Si
la pelota ha rebotado changeDirection ser verdadero y Sound.BALL.play() ser ejecutado.

Agregando puntuacin y aumentando la velocidad


Todo juego necesita una medida de logro o xito. En nuestro caso incluiremos en el rincn
izquierdo de la pantalla nuestra puntuacin que no ser ms que la cantidad de veces que
logramos pegarle a la pelota con la raqueta. Por otro lado el juego debera ser cada vez ms
difcil para no matar de aburrimiento al jugador. Para esto aumentaremos la velocidad del
juego cada vez que rebote la pelota en la raqueta.
36

Los objetos mviles del juego son la pelota y la raqueta. Modificando la velocidad de
movimiento de estos dos objetos modificaremos la velocidad del juego. Vamos a incluir una
propiedad llamada speed en la clase Game para mantener la velocidad del juego. La
propiedad speed ser inicialmente 1 e ir incrementndose cada vez que le demos a la
pelota con la raqueta.

Para la puntuacin necesitaramos otra propiedad a incrementar cada vez que golpeemos la
pelota. En vez de crear una nueva propiedad se me ocurri reutilizar speed. El nico
inconveniente es que las puntuaciones suelen iniciarse en 0 y no en 1 como speed. La
solucin que se me ocurri fue agregar un mtodo getScore() que retorne el valor de speed
menos uno.

private int getScore() {


return speed - 1;
}

Veamos las modificaciones hechas en la clase Game:

package com.edu4java.minitennis8;

import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;

@SuppressWarnings("serial")
public class Game extends JPanel {

Ball ball = new Ball(this);


Racquet racquet = new Racquet(this);
int speed = 1;

private int getScore() {


return speed - 1;
}

public Game() {
addKeyListener(new KeyListener() {
@Override
public void keyTyped(KeyEvent e) {
}

@Override
public void keyReleased(KeyEvent e) {
racquet.keyReleased(e);
}
37

@Override
public void keyPressed(KeyEvent e) {
racquet.keyPressed(e);
}
});
setFocusable(true);
Sound.BACK.loop();
}

private void move() {


ball.move();
racquet.move();
}

@Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
ball.paint(g2d);
racquet.paint(g2d);

g2d.setColor(Color.GRAY);
g2d.setFont(new Font("Verdana", Font.BOLD, 30));
g2d.drawString(String.valueOf(getScore()), 10, 30);
}

public void gameOver() {


Sound.BACK.stop();
Sound.GAMEOVER.play();
JOptionPane.showMessageDialog(this, "your score is: " +
getScore(),
"Game Over", JOptionPane.YES_NO_OPTION);
System.exit(ABORT);
}

public static void main(String[] args) throws InterruptedException


{
JFrame frame = new JFrame("Mini Tennis");
Game game = new Game();
frame.add(game);
frame.setSize(300, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

while (true) {
game.move();
game.repaint();
Thread.sleep(10);
}
}
}

Para pintar la puntuacin en el rincn superior izquierdo al final del mtodo paint he
agregado:
38

g2d.setColor(Color.GRAY);
g2d.setFont(new Font("Verdana", Font.BOLD, 30));
g2d.drawString(String.valueOf(getScore()), 10, 30);

En la primera lnea elegimos el color gris, en la segunda lnea el tipo de letra Verdana,
negrita de 30 pixeles y finalmente en la posicin (x,y) igual a (10,30) donde dibujamos la
puntuacin.

En el mtodo gameOver() modificamos el segundo parmetro para mostrar la puntuacin


alcanzada:

JOptionPane.showMessageDialog(this, "your score is: " +


getScore(),
"Game Over", JOptionPane.YES_NO_OPTION);

En la clase Ball el mtodo move() ha sido modificado para considerar la nueva propiedad
de velocidad "game.speed". Cuando la pelota cambiaba de direccin las propiedades de
velocidad xa y ya eran modificadas a 1 o -1. Ahora considerando la velocidad estas
propiedades son cambiadas a game.speed o -game.speed. Tambin se ha agregado en el
condicional if(collision()) que la velocidad se incremente "game.speed++".

package com.edu4java.minitennis8;

import java.awt.Graphics2D;
import java.awt.Rectangle;

public class Ball {


private static final int DIAMETER = 30;

int x = 0;
int y = 0;
int xa = 1;
int ya = 1;
private Game game;

public Ball(Game game) {


this.game = game;
}

void move() {
boolean changeDirection = true;
if (x + xa < 0)
xa = game.speed;
else if (x + xa > game.getWidth() - DIAMETER)
xa = -game.speed;
else if (y + ya < 0)
ya = game.speed;
else if (y + ya > game.getHeight() - DIAMETER)
game.gameOver();
else if (collision()){
ya = -game.speed;
y = game.racquet.getTopY() - DIAMETER;
game.speed++;
39

} else
changeDirection = false;

if (changeDirection)
Sound.BALL.play();
x = x + xa;
y = y + ya;
}

private boolean collision() {


return game.racquet.getBounds().intersects(getBounds());
}

public void paint(Graphics2D g) {


g.fillOval(x, y, DIAMETER, DIAMETER);
}

public Rectangle getBounds() {


return new Rectangle(x, y, DIAMETER, DIAMETER);
}
}

A continuacin vemos la clase Racquet:

package com.edu4java.minitennis8;

import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.KeyEvent;

public class Racquet {


private static final int Y = 330;
private static final int WITH = 60;
private static final int HEIGHT = 10;
int x = 0;
int xa = 0;
private Game game;

public Racquet(Game game) {


this.game = game;
}

public void move() {


if (x + xa > 0 && x + xa < game.getWidth() - WITH)
x = x + xa;
}

public void paint(Graphics2D g) {


g.fillRect(x, Y, WITH, HEIGHT);
}

public void keyReleased(KeyEvent e) {


xa = 0;
}

public void keyPressed(KeyEvent e) {


40

if (e.getKeyCode() == KeyEvent.VK_LEFT)
xa = -game.speed;
if (e.getKeyCode() == KeyEvent.VK_RIGHT)
xa = game.speed;
}

public Rectangle getBounds() {


return new Rectangle(x, Y, WITH, HEIGHT);
}

public int getTopY() {


return Y - HEIGHT;
}
}

Aqu la modificacin es similar que en Ball. En el mtodo keyPressed(KeyEvent e) la


modificacin de la velocidad xa pasa de -1 y 1 a -game.speed y game.speed.

Nota: segn el estndar de "Java Beans" el acceso a la propiedad "game.speed" debera


hacerse usando un mtodo de la forma "game.getSpeed()". El accesos directo a una
propiedad es considerado casi un pecado mortal en el mbito de java empresarial.
Curiosamente en el entorno de desarrollo de juegos es muy comn y esta justificado por
eficiencia. Esto es especialmente importante en programacin para mviles donde los
recursos suelen ser ms escasos.

Creando archivo jar ejecutable y qu es la mquina


virtual de java.
En este tutorial veremos como crear un archivo ejecutable para una aplicacin java, en
particular para nuestro juego. Un programa java necesita una maquina virtual para ser
ejecutado. A continuacin tambin explicaremos que es la mquina virtual de java y
brevemente como funciona.

La mquina virtual de java; Java Virtual Machine (JVM)

Antes de java lo ms normal era escribir un programa en un lenguaje de programacin de


alto nivel como C o Pascal y luego traducirlo a lenguaje de mquina con un compilador. El
"lenguaje mquina" o "cdigo mquina" es el lenguaje que entiende la mquina (ordenador
o computadora). Una mquina con Windows y un Mac de Apple hablan distinto lenguaje de
mquina. Luego se necesita un compilador diferente para cada mquina.

En el caso de java cuando usamos el compilador no obtenemos cdigo mquina. Lo que


obtenemos es un cdigo llamado bytecode que no se ejecuta directamente sobre una
mquina real. Este bytecode solo se puede ejecutar en una mquina virtual. Una mquina
virtual es un programa que se hace pasar por una mquina. Para cada sistema operativo
diferente existir un programa de mquina virtual especifico pero el bytecode que ejecutan
ser el mismo.
41

Como el bytecode es el mismo potencialmente puede ser ejecutado es cualquier sistema


operativo siempre y cuando exista una implementacin de JVM para este SO. En esta idea
se basa la famosa frase: "Write once, run anywhere" (WORA) "escribir una vez, ejecutar en
cualquier parte".

Compilacin y ejecucin en java


Existen dos versiones de instalacin de java para cada sistema operativo: JRE y JDK. JRE
Java Runtime Environment, es una versin reducida que contiene la JVM pero que no
incluye el compilador java. JDK Java Development Kit contiene la JVM, el compilador
java y muchas herramientas adicionales para el desarrollo de aplicaciones java. Si no tiene
instalada la versin JDK tendrn que instalarla para poder continuar con este tutorial.

Si tenemos instalado la JDK tendremos un directorio donde estarn todos los archivos que
componen la plataforma java. Este directorio es conocido como java Home o
JAVA_HOME. En mi caso este es "C:\Program Files (x86)\Java\jdk1.6.0_21".

Dentro de JAVA_HOME existe una carpeta bin que contiene los ejecutable entre los que
podemos encontrar: El compilador: javac.exe y la mquina virtual: java.exe.

Para ejemplificar como funcionan estos programas vamos a crear un archivo llamado
HelloWorld.java en un directorio C:\testjava con el siguiente contenido:
42

import javax.swing.JOptionPane;

public class HelloWorld {


public static void main(String[] args) {
System.out.println("Hello World ;)");
JOptionPane.showMessageDialog(null, "Hello World");
}
}
Luego abrimos una ventana de comandos, ejecutamos "cd C:\testjava" para posicionarnos
en el directorio donde esta nuestro archivo java y luego para compilar ejecutamos:

javac HelloWorld.java
o
"C:\Program Files (x86)\Java\jdk1.7.0_05\bin\javac" HelloWorld.java
Como resultado podemos ver que se ha creado un nuevo archivo HellowWorld.class con el
bytecode. Podemos ejecutar este bytecode con la siguiente instruccin:

java HelloWorld
o
"C:\Program Files (x86)\Java\jdk1.7.0_05\bin\java" HelloWorld
Un programa java normalmente esta compuesto por varios archivos java y por consiguiente
muchos archivos *.class. Adems estn los archivos de recursos como los sonidos en
nuestra aplicacin. Java permite empaquetar una aplicacin con todos los archivos antes
mencionados en un archivo *.jar.

Archivo JAR

Un archivo jar no es ms que un archivo comprimido con el algoritmo de compresin ZIP


que puede contener:

1. Los archivos *.class que se generan a partir de compilar los archivos


*.java que componen nuestra aplicacin.

2. Los archivos de recursos que necesita nuestra aplicacin (Por ejemplo


los archivo de sonido *.wav)

3. Opcionalmente se puede incluir los archivos de cdigo fuente *.java

4. Opcionalmente puede existir un archivo de configuracin "META-


INF/MANIFEST.MF".

Crear un archivo JAR ejecutable

Para que el archivo jar sea ejecutable hay que incluir en el archivo MANIFEST.MF una
lnea indicando la clase que contiene el mtodo esttico main() que se usar para iniciar la
aplicacin. En nuestro ejemplo anterior sera:

Main-Class: HelloWorld
43

Es importante destacar que al final de la lnea hay que agregar un retorno de carro para que
funcione. Los invito a crear un archivo testjava.zip que contenga el archivo
HelloWorld.class, el directorio META-INF y dentro el archivo MANIFEST.MF con la linea
Main-Class: HelloWorld. Para esto pueden usar los programas Winzip o WinRAR que
pueden descargar gratuitamente (buscar en Google).

Una vez creado el archivo testjava.zip, lo renombramos a testjava.jar y lo ejecutamos desde


la lnea de comandos:

Tambin podemos ejecutar haciendo doble click sobre el archivo JAR.

Como crear un archivo JAR ejecutable desde eclipse

Para crear un JAR ejecutable basta con ir a File-Export, seleccionar Runnable JAR file
44

Como se ve a continuacin, en "Launch configuration" seleccionamos la que usamos para


ejecutar la versin final de nuestra aplicacin y en "Export destination" indicamos donde
queremos guardar nuestro JAR y con que nombre:

Si java est bien instalado sobre Windows, con un doble click sobre minitennis.jar sera
suficiente para ejecutar nuestra aplicacin.

Examinando minitennis.jar
45

Si descomprimimos nuestro archivo minitennis.jar encontraremos los archivos *.class que


componen nuestro juego. Estos archivos estn dentro del rbol de directorios con los
nombres de los paquetes java que contienen a las clases.

Adems dentro de META-INF/MANIFEST.MF podemos ver en la ltima lnea como se


indica que el juego debe iniciarse con el mtodo main() de la clase Game que esta en el
paquete com.edu4java.minitennis8.

Eclipse realiza un excelente trabajo compilando, ejecutando y creando archivos JAR pero
es bueno entender que por debajo eclipse usa la instalacin de java de forma similar a
nuestro ejemplo HelloWorld.

También podría gustarte