En este artículo explicamos cómo programar en Java la estructura básica de un videojuego.

Nota: Este artículo asume que ya tienes unos conocimientos básicos de Java y quieres entender cómo se puede usar para hacer un videojuego. No es un tutorial de introducción a Java. Por otra parte, las explicaciones son bastante detalladas y probablemente las puedas seguir sin problema si ya conoces algún otro lenguaje de programación orientado a objetos.

Ciclo principal de un videojuego
La estructura básica de un videojuego es muy sencilla. Después de inicializar el estado del juego simplemente ejecuta un ciclo infinito con los tres pasos siguientes:

Lee los controles
Ejecuta la lógica del juego
Redibuja la pantalla
En el primer paso --Lee los Controles-- checa si el jugador presionó algún botón del control y en que posición están los joysticks.

En el segundo paso --Ejecuta la lógica del juego-- es donde interpreta lo que leyó de los controles para mover el personaje o vehículo del jugador, ejecuta los algoritmos de inteligencia artificial de los oponentes y calcula la física del juego.

En el tercer paso --Redibuja la pantalla-- vuelve a dibujar la pantalla para que todos los objetos del juego (personaje del jugador, oponentes, items, etc.) aparezcan en sus nuevas posiciones.

Al ejecutar estos tres pasos repeditamente y con suficiente velocidad, por lo menos unas decenas de veces por segundo, el programa simula objetos en movimiento que reaccionan a las acciones del jugador.

La estructura básica de un videojuego con Java
Aunque esta estructura básica es muy sencilla, no es obvio cómo programarla en Java porque primero hay que saber cómo escribir un programa que habra una ventana y actualice su contenido periodicamente. Para hacer esto se emplea Swing, la biblioteca estándar de Java para hacer aplicaciones que manejan ventanas.

Swing es un sistema muy flexible para manejo de ventanas y todos sus componentes como botones, menús y listas. Pero esta flexibilidad tiene un precio: Swing es muy extenso y toma algo de tiempo aprender cómo usarlo correctamente. Por suerte, para hacer un videojuego sólo es necesario conocer una pequeña parte de Swing.

En este artículo vamos a ver lo mínimo que se necesita hacer un programa que abre una ventana en la cual despliega una animación. Para mantener el ejemplo lo más sencillo posible no vamos a permitir ninguna interacción, es decir que nuestro ciclo principal únicamente va ejecutar los pasos Ejecuta lógica del juego y Redibuja la pantalla.

Aunque este ejemplo es muy sencillo está escrito con mucho cuidado de seguir estríctamente todas las reglas para el uso correcto de Swing. Es posible escribir un programa que rompa estas reglas y de todas maneras funcione, pero existe el peligro que al hacerle algún cambio empiece a mostrar comportamientos extraños muy difíciles de entender.


Una animación sencilla
A continuación está el fuente completo de un programa en Java que abre una ventana en la cual muestra una bola roja moviendose.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class Demo1 extends JComponent {

private final static int ANCHO = 512;

private final static int ALTO = 384;

private final static int DIAMETRO = 20;

private float x, y;

private float vx, vy;

public Demo1() {
setPreferredSize(new Dimension(ANCHO, ALTO));
x = 10;
y = 20;
vx = 300;
vy = 400;
}

private void fisica(float dt) {
x += vx * dt;
y += vy * dt;
if (vx < 0 && x <= 0 || vx > 0 && x + DIAMETRO >= ANCHO)
vx = -vx;
if (vy < 0 && y < 0 || vy > 0 && y + DIAMETRO >= ALTO)
vy = -vy;
}

public void paint(Graphics g) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, ANCHO, ALTO);
g.setColor(Color.RED);
g.fillOval(Math.round(x), Math.round(y), DIAMETRO, DIAMETRO);
}

private void dibuja() throws Exception {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
paintImmediately(0, 0, ANCHO, ALTO);
}
});
}

public void cicloPrincipalJuego() throws Exception {
long tiempoViejo = System.nanoTime();
while (true) {
long tiempoNuevo = System.nanoTime();
float dt = (tiempoNuevo - tiempoViejo) / 1000000000f;
tiempoViejo = tiempoNuevo;
fisica(dt);
dibuja();
}
}

public static void main(String[] args) throws Exception {
JFrame jf = new JFrame("Demo1");
jf.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
jf.setResizable(false);
Demo1 demo1 = new Demo1();
jf.getContentPane().add(demo1);
jf.pack();
jf.setVisible(true);
demo1.cicloPrincipalJuego();
}
}

Para compilar y ejecutar el programa
Te vamos a indicar los pasos a seguir para que puedas compilar y ejecutar este programa en tu computadora desde la linea de comandos (si prefieres emplear algún IDE como Eclipse o NetBeans no deberías tener ningún problema en adecuar estas instrucciones para la herramienta que estás empleando).

Sigue estos pasos:

Copia el fuente del programa a tu editor favorito y almacenalo (sálvalo) con el nombre Demo1.java. Es indispensable que el archivo en el cual esté almacenado el fuente se llame así, no se puede llamar de ninguna otra manera. Ese fuente contiene la definición de una clase que se llama Demo1 y por lo tanto Java requiere que esté almacenado en un archivo llamado Demo1.java, no puede tener otro nombre. Inclusive, es importante que respetes las minúsculas y mayúsculas en el nombre: empieza con una 'D' mayúscula y lo demás está en minúsculas.
Ahora tienes que compilar el programa. Para eso ejecuta el comando siguiente:
javac Demo1.java
El programa javac es el compilador de Java. Lee el programa Demo1.java y lo traduce a bytecodes que almacena en tres archivos: Demo1.class, Demo1$1.class y Demo1$2.class.

Ejecuta el programa con este comando:
java Demo1
El programa java es la máquina virtual de Java (JVM). Lee los bytecodes del archivo Demo1.class y los ejecuta (al ejecutarlos lee también los bytecodes de Demo1$1.class y Demo1$2.class). Al ejecutar el programa Demo1, en la pantalla de tu computadora aparece una ventana con una bola roja que se mueve y rebota contra los bordes.

Cómo funciona el programa
Veamos ahora el programa parte por parte para entender cómo funciona.

Packages empleados
Empieza con estas tres lineas:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
que indican los packages en los cuales se encuentran las clases e interfaces que vamos a usar.

En Java las clases se pueden agrupar dentro de packages. Dentro de un package el nombre de una clase debe ser único, no puede existir otra clase en ese package con el mismo nombre. No hay ningún problema si tenemos dos o más clases con el mismo nombre siempre y cuando estén dentro de packages diferentes. Esto es para que sea más sencillo seleccionar el nombre para una clase sin tener que preocuparse de si otro programador ya empleó ese mismo nombre para otra clase.

Cuando deseamos emplear clases de otro package es necesario indicarselo al compilador, por medio de un import, para que sepa donde buscarlas. En este caso le estamos indicando que las busque en los packages java.awt, java.awt.event y javax.swing.

Nombre de la clase, constantes y variables de instancia
La linea siguiente:

public class Demo1 extends JComponent {
indica el inicio de la definición de la clase Demo1. Dice que Demo1 extiende JComponent, eso indica que hereda de la clase JComponent. Un JComponent es un componente gráfico de Swing que se puede incluir dentro de una ventana, sabe cómo desplegarse y cómo interactuar con el jugador. En este caso el componente gráfico es el area de la ventana en donde vamos a mostrar la animación y no tiene ninguna interacción con el "jugador".

Las lineas siguientes declaran algunas constantes:

private final static int ANCHO = 512;

private final static int ALTO = 384;

private final static int DIAMETRO = 20;
Las constantes ANCHO y ALTO indican el ancho y alto del componente, mientras que DIAMETRO es el diametro de la bola que vamos a dibujar. Todas estas dimensiones están en unidades de pixeles.

Después vienen las declaraciones de la variables de instancia empleadas para almacenar la posición y velocidad de la bola:

private float x, y;

private float vx, vy;
Las variables x y y son para almacenar la coordenadas de la esquina superior izquierda de la bola. Sí, ya sé que una bola no tiene esquinas. Imagínate que la bola está dentro de un cuadrado; estamos hablando de la esquina superior izquierda de ese cuadrado:


Al dibujar dentro de un componente de Swing, el origen del sistema de coordenadas está en la esquina superior izquierda del componente (en este caso es el componente Demo1); los valores en el eje x se incrementan hacia la derecha y los valores en el eje y se incrementan hacia abajo:




Las coordenadas se miden en pixeles y deben ser números enteros. Si estás leyendo esta explicación cuidadosamente, seguramente te preguntas en este momento: ¿Si las coordenadas tienen que ser números enteros, entonces porqué estamos usando variables de tipo float para almacenarlas? La respuesta es simple, las almacenamos como float porque así es más sencillo hacer los cálculos para el movimiento de la bola y basta con redondear los valores a enteros cuando queremos dibujarla.
La velocidad de la bola es un vector. Tiene una magnitud --qué tán rápido va la bola-- y una dirección --hacia donde va la bola. Podríamos almacenar esta información en dos variables, una llamada magnitud y otra llamada dirección, pero resulta más práctico para los cálculos de movimiento representar el vector velocidad separado en sus componentes horizontal y vertical dentro de las variables vx y vy respectivamente:




En otras palabras, vx y vy representan la velocidad horizontal y la velocidad vertical de la bola. Si vx es positivo la bola se está moviendo hacia la derecha, y si es negativo se está moviendo hacia la izquierda. De la misma manera, si vy es positivo la bola se esta moviendo hacia abajo, y si es negativo se está moviendo hacia arriba. El movimiento exacto de la bola es la suma de su movimiento horizontal y su movimiento vertical.


El constructor
La parte siguiente del programa:

public Demo1() {
setPreferredSize(new Dimension(ANCHO, ALTO));
x = 10;
y = 20;
vx = 300;
vy = 400;
}
Es el constructor que se ejecuta cuando creamos la instancia de la clase Demo1 (el componente) donde se muestra la animación de la bola.

Lo primero que hace es llamar al método setPreferredSize() para definir de que tamaño debe ser el componente al desplegarse en la pantalla. Los componentes de Swing tienen un tamaño máximo, un tamaño mínimo y un tamaño preferido. Swing emplea esta información para acomodar los componentes dentro de una ventana y asignarles su tamaño. En este programa, el único componente que contiene la ventana es una instancia de Demo1 y, por lo tanto, basta con definir su tamaño preferido ya que no tiene que compartir la ventana con ningún otro componente. Este método espera como argumento un objeto de tipo Dimension, así que creamos uno con el ancho y alto que debe tener el componente y se lo pasamos a setPreferredSize().

Después, le damos una posición inicial a la bola --en las variables x y y--, y también una velocidad inicial --en las variables vx y vy. Las velocidades están en unidades de pixeles/segundo, es decir que empieza con una velocidad horizontal de 300 pixeles/segundo y una velocidad vertical de 400 pixeles/segundo.

La física: movimiento y colisiones
Después viene la definición del método física():

private void fisica(float dt) {
x += vx * dt;
y += vy * dt;
if (vx < 0 && x <= 0 || vx > 0 && x + DIAMETRO >= ANCHO)
vx = -vx;
if (vy < 0 && y < 0 || vy > 0 && y + DIAMETRO >= ALTO)
vy = -vy;
}
En este método es donde se calcula la nueva posición de la bola y si es que hay que modificar su velocidad porque chocó contra alguno de los bordes. Nota: cuando hablamos aquí de modificar su velocidad hay que recordar que estamos tratando con un vector y hay dos cosas que se pueden modificar: su magnitud y su dirección. En este caso, cuando la bola choca contra un borde, no modificamos su magnitud, la bola sigue yendo igual de rápido; lo que modificamos es su dirección, ahora va hacia otro lado.

El método fisica() recibe un parámetro dt que le indica el tiempo transcurrido, en segundos, desde la última vez que se movió la bola. Usamos ese tiempo transcurrido para calcular cual debe ser su nueva posición en las dos lineas siguientes:

x += vx * dt;
y += vy * dt;
Supongamos que vx contiene en ese momento un -300, la bola se está moviendo hacia la izquierda a una velocidad de 300 pixeles/segundo, y que dt es 0.1, ha transcurrido una décima de segundo desde la última vez que se movimos la bola. Al multiplicar -300 pixeles/segundo por 0.1 segundos nos da un resultado de -30 pixeles. Al emplear el operador +=, le sumamos un -30 a x y la bola ahora queda 30 pixeles más a la izquierda.

Lo que sigue es checar si la bola choco contra alguno de los bordes y, en caso de que esto ocurra, modificar su velocidad. Si la pelota está moviendose hacia la izquierda y choca contra el borde izquierdo entonces ahora se tiene que mover hacia la derecha. Lo mismo pasa cuando se está moviendo hacia la derecha y choca contra el borde derecho, ahora se tiene que mover hacia la izquierda.

Saber si la bola se está moviendo hacia la izquierda es muy sencillo, basta con ver si el valor de vx es negativo. Para checar si chocó contra el borde izquierdo simplemente vemos si el valor de x es inferior o igual a cero. Por lo tanto, la expresión vx < 0 && x <= 0 es verdadera si la bola se está moviendo hacia la izquierda y chocó contra el borde izquierdo. Si el valor de vx es mayor a cero entonces la bola se está moviendo hacia la derecha. Para ver si la bola chocó contra el borde derecho tenemos que saber donde está el lado derecho de la bola. El lado derecho de la bola lo podemos calcular sumándole el ancho de la bola (DIAMETRO) a la coordenada horizontal del lado izquierdo de la bola (x). Es decir que el lado derecho de la bola está en x + DIAMETRO. Por lo tanto, la expresión vx > 0 && x + DIAMETRO >= ANCHO es verdadera si la bola se está moviendo hacia la derecha y chocó contra el borde derecho.

Por lo tanto, la expresión vx < 0 && x <= 0 || vx > 0 && x + DIAMETRO >= ANCHO es verdadera si la bola se está moviendo hacia la izquierda y chocó contra el borde izquierdo o si se está moviendo hacia la derecha y chocó contra el borde derecho. Cuando eso ocurre, ejecutamos vx = -vx para cambiar el signo de vx, si era negativo ahora es positivo, es decir que si se estaba moviendo hacia la izquierda ahora se va a mover hacia la derecha. Y lo mismo ocurre cuando cuando se estaba moviendo hacia la derecha (vx positivo), ahora se mueve hacia la izquierda (vx negativo).

Hacemos lo mismo con el componente vertical de la velocidad (vy) y con eso ya estamos detectando las colisiones con los cuatro bordes de la pantalla y modificando apropiadamente la velocidad de la bola.

Nota: Este sistema de detección de colisiones no es muy bueno pero funciona bastante bien para este ejemplo sencillo. Para entender porqué no es muy bueno veamos un ejemplo. Imagínate que el valor de x es 10, el valor de vx es -300 y el valor de dt es 0.1. Al ejecutar la linea donde dice x += vx * dt; el nuevo valor de x es -20. Obviamente, eso está mal. El valor correcto de x debería ser 20: si la bola se está moviendo a una velocidad de 300 pixeles por segundo entonces en una décima de segundo se mueve de 30 pixeles, primero 10 pixeles hacia la izquierda y choca contra el borde, cambia su dirección y se mueve 20 pixeles hacia la derecha. En este ejemplo el error no es muy grave porque, al ser tan sencillo lo que estamos haciendo, tu computadora calcula la nueva posición de la bola y la dibuja en la pantalla cientos de veces por segundo. Por lo tanto el valor de dt es inferior a 0.01 (una centésima de segundo) y en ese tiempo la bola se mueve de muy pocos pixeles. Lo peor que puede ocurrir es que la bola se salga de la venta por un par de pixeles, y eso ocurre tan rápido que ni se nota.

La parte gráfica
Lo que sigue en el programa es el método para dibujar el componente en la pantalla:

public void paint(Graphics g) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, ANCHO, ALTO);
g.setColor(Color.RED);
g.fillOval(Math.round(x), Math.round(y), DIAMETRO, DIAMETRO);
}
El método paint() es un método que se hereda de JComponent y que Swing llama para dibujar el componente en la pantalla. Hay varias situaciones en las cuales se ejecuta este método, algunas de ellas son: cuando se despliega por primera vez en la pantalla la ventana que contiene este componente, si había otra ventana delante de esta y ahora ya no está, si la ventana estaba minimizada y se abre. En nuestro ejemplo este método también se va a ejecutar cuando nosotros le pidamos a Swing que se redibuje el componente para se vea la bola en su nueva posición.

El método paint() recibe un parámetro g que es de tipo Graphics. Este es el contexto gráfico que se emplea para dibujar. El contexto gráfico es un objeto que contiene la información necesaria para dijujar algo, como: en donde hay que dibujar, el color a emplear, el grueso de las lineas, el tipo de font para los textos y varias otras cosas.

Lo primero que hacemos ahí es dibujar el fondo. Empleamos g.setColor(Color.WHITE); para seleccionar "pintura" blanca y llenamos un rectangulo que cubre toda el area del componente con g.fillRect(0, 0, ANCHO, ALTO);. El método fillRect() llena un rectangulo con la pintura actual del contexto gráfico; espera estos argumentos: fillRect(int x, int y, int width, int height). Donde x y y indican la posición de la esquina superior izquierda del rectángulo, mientras que width y height son el ancho y alto del rectángulo.

Después dibujamos la bola. Seleccionamos pintura roja con g.setColor(Color.RED); y después dibujamos la bola con g.fillOval(Math.round(x), Math.round(y), DIAMETRO, DIAMETRO);. El método fillOval() llena un óvalo con la pintura actual del contexto gráfico; espera estos argumentos: fillOval(int x, int y, int width, int height). Donde x y y indican la posición de la esquina superior izquierda del rectángulo que contiene al óvalo, mientras que width y height son el ancho y alto del óvalo (si el ancho es igual al alto entonces tenemos un caso particular de óvalo: un círculo). Fijate cómo estamos empleando el método Math.round() para redondear los valores de x y y a enteros.

Ya tenemos nuestro método paint() para dibujar la bola en la pantalla, lo que nos falta es una manera de decirle a Swing que queremos hacerlo. Una manera sencilla es llamando a otro método que heredamos de JComponent: paintImmediately(). Este método espera los siguientes argumentos: paintImmediately(int x, int y, int width, int height). Estos argumentos le indican a Swing el área rectangular dentro de nuestro componente que hay que dibujar. Se especifíca esta area para optimizar la aplicación y no perder tiempo redibujando partes del componente que no han cambiado. Para no hacer más complicado nuestro ejemplo vamos a pedirle así que redibuje todo el componente: paintImmediately(0, 0, ANCHO, ALTO);.

Nota: Al llamar a paintImmediately() le estamos especificando un área rectangular que hay que redibujar, pero en nuestro método paint() no hay nada que tome en cuenta explícitamente esa especificación de un área. Esto se debe a que una de las cosas que se almacenan dentro del contexto gráfico es el clip area que especifíca el área dentro de la cual se puede pintar algo. Al emplear el contexto gráfico para pintar con métodos como fillRectangle() y fillOval() únicamente se pinta dentro de la intersección de esa figura (rectángulo u óvalo) con el clip area, esta es una optimización que, en ciertos casos, puede hacer mucho más rápido el redibujado. De hecho, nuestro ejemplo es bastante ineficiente en cuanto a eso: para dibujar la bola en su nueva posición estamos primero pintando de blanco todo el fondo del componente, un area de 512 por 384 pixeles (196,608 pixeles).

Al principio de este artículo dijimos que: "Aunque este ejemplo es muy sencillo está escrito con mucho cuidado de seguir estríctamente todas las reglas para el uso correcto de Swing". Una de las reglas más importantes para el uso correcto de Swing es que no se deben modificar sus componentes o redibujarlos en un thread que no sea el Swing event thread. Java está diseñado para poder escribir aplicaciones que son multithreaded, es decir que pueden estar ejecutando simulatáneamente varios hilos (secuencias, threads) de instrucciones. La arquitectura de Swing aprovecha esto y tiene un hilo de ejecucción, el Swing event thread, dedicado a atender todos los eventos del sistema de ventanas y el redibujado de sus componentes. Los eventos del sistema de ventanas son cosas que ocurren cuando una persona está interactuando con la aplicación, por ejemplo: un click del mouse sobre algún componente o un cambio en el tamaño de la ventana.

Para nuestro ejemplo esta regla de Swing implica que tenemos que encontrar algún mecanismo para que sea el Swing event thread el que ejecute el llamado al método paintImmediately(). Por suerte, Swing cuenta con el mecanismo apropiado para hacer justamente esto dentro de su clase SwingUtilities: los métodos estáticos invokeLater() e invokeAndWait(). Con cualquiera de esos dos métodos le podemos pedir al Swing event thread que ejecute algo por nosotros. La diferencia entre los dos es que con invokeLater() le pedimos que lo ejecute y seguimos con la ejecución de las instrucciones en nuestro propio thread, sabiendo que pronto el Swing event thread ejecutará lo que le pedimos. Al emplear invokeAndWait(), la ejecución de instrucciones en nuestro thread se detiene hasta que el Swing event thread haya terminado de ejecutar lo que le pedimos.

En este caso el método apropiado es invokeAndWait(), porque no debemos seguir con la ejecución de nuestro ciclo de animación mientras que no se haya redibujado la bola en su nueva posición. En nuestro método dibuja() encapsulamos la solicitud al Swing event thread de ejecutar el llamado a paintImmediately():

private void dibuja() throws Exception {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
paintImmediately(0, 0, ANCHO, ALTO);
}
});
}
El método invokeAndWait() espera como argumento un objeto de una clase que implemente la interfaz Runnable. Esta interfaz es muy sencilla, simplemente exige que las clases que la implementen tengan un método llamado run(). Este método run() no espera ningún argumento ni devuelve ningún resultado. En el caso de invokeAndWait() ese método run() es el que contiene las instrucciones que se deben ejecutar dentro del Swing event thread.

En el método dibuja() estamos empleando una notación de Java que nos permite, directamente en esa parte de nuestro programa, especificar una clase anónima que implementa alguna interfaz y, ahí mismo, crear una instancia de esa clase.

Lo único que queda por explicar del método dibuja() es la parte donde dice: throws Exception. La definición del método invokeAndWait() especifíca que puede mandar varios tipos de excepciones, eso nos obliga a hacer una de dos cosas:

Cachar esa excepción con un try/catch
También nosotros mandar esa excepción
El manejo correcto de excepciones en Java es un tema bastante amplio, aquí nos vamos a limitar a hacerte un par de recomendaciones.

La primera recomendación es: si no sabes que hacer con una excepción mandasela al que te llamó.

Y la segunda recomendación es: nunca, absolutamente por ningún motivo, debes escribir un try con un catch vacio. Si lo haces, algún dia tendrás la oportunidad de arrepentirte (y si no eres tú será alguien más, pero alguien tarde o temprano tendrá que pagar por eso).

Como nosotros somos buenos niños, seguimos nuestras propias recomendaciones. No tenemos la menor idea de algo que sea apropiado hacer con las excepciones que manda invokeAndWait(), por lo tanto se las mandamos a quien nos llamó. Eso se traduce en el throws Exception del método dibuja().

El ciclo principal del juego
Ya que vimos cómo esta implementada la física y la parte gráfico de nuestro programa, al fín llegamos al ciclo principal del "juego" (y, en este caso, el único ciclo que hay en todo el programa):

public void cicloPrincipalJuego() throws Exception {
long tiempoViejo = System.nanoTime();
while (true) {
long tiempoNuevo = System.nanoTime();
float dt = (tiempoNuevo - tiempoViejo) / 1000000000f;
tiempoViejo = tiempoNuevo;
fisica(dt);
dibuja();
}
}
El ciclo principal está encapsulado dentro del método cicloPrincipalJuego(). Es un ciclo infinito que llama repetidamente a los métodos fisica() y dibuja, que ya vimos anteriormente. El único detalle interesante es que tenemos que calcular el tiempo transcurrido en cada iteración y pasarselo al método fisica() para que pueda calcular de cuanto se tiene que mover la bola.

Para medir el tiempo transcurrido empleamos el método estático nanoTime() de la clase System. Este método nos devuelve el tiempo transcurrido, en nanosegundos, desde algún momento arbitrario (y desconocido) en el tiempo. Almacenamos ese valor dentro de la variable tiempoViejo y, con una simple resta podemos averiguar el tiempo transcurrido. Como el resultado de esa resta está en nanosegundos y el método fisica() espera un valor en segundos, lo dividimos entre 1,000,000,000 para hacer la conversión.

Nota: al crear la variable tiempoViejo la inicializamos con el valor que devuelve nanoTime() e, inmediatamente después, volvemos a llamarlo y hacemos la resta. Lo más problable es que el resultado sea cero (nanoTime() devuelve un resultado en unidades de nanosegundos, pero ¡no mide algo tan pequeño como un nanosegundo!) y que en el primer llamado a fisica() la bola no se mueva. No es grave, en la siguiente iteración del ciclo, menos de una centésima de segundo más tarde, ya empieza a moverse. Tenemos que hacer eso porque no podemos emplear una variable que no ha sido inicializada y ese es el único valor inicial razonable que le podemos asignar (¿Que pasaría si le damos un valor inicial de cero?).

Este método también tiene una declaración throws Exception. Esto se debe que llama a nuestro método dibuja() que manda una excepción y, como tampoco sabemos aquí que hacer con ella, se la mandamos a quien nos llamó

.

El programa "principal"
Ya está listo nuestro componente, lo único que falta es colocarlo dentro de una ventana y llamar a su método cicloPrincipalJuego(). Para encargarse de eso definimos un método estático main():

public static void main(String[] args) throws Exception {
JFrame jf = new JFrame("Demo1");
jf.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
jf.setResizable(false);
Demo1 demo1 = new Demo1();
jf.getContentPane().add(demo1);
jf.pack();
jf.setVisible(true);
demo1.cicloPrincipalJuego();
}
Este método también tiene una declaración throws Exception porque llama a cicloPrincipalJuego(), que manda una excepción con la cual no sabemos que hacer. Cuando el método main() de una aplicación en Java manda una excepción, se interrumpe la ejecución de la aplicación y se despliega un stack trace indicando la parte exacta del programa donde ocurrió el problema. Para un juego que tengamos la intención de vender ese no es un comportamiento muy apropiado. Pero, como no creo que alguien esté dispuesto a comprar este programa, ese comportamiento es bastante aceptable (y permite que este programa de ejemplo sea más sencillo).

Lo primero que hacemos es crear la ventana. En Swing las ventanas son instancias de la clase JFrame. Creamos una instancia de esa clase y la almacenamos en la variable jf (en realidad se almacena una referencia a esa instancia):

JFrame jf = new JFrame("Demo1");
El string, "Demo1", que le pasamos como argumento al constructor de la claseJFrame es el título que va a aparecer en la parte superior de nuestra ventana.

Nota: Al crear una instancia de JFrame ya tenemos un objeto que representa una ventana, pero esa ventana todavía no es visible en la pantalla.

Cuando un jugador cierra una ventana, espera que en ese instante se detenga la ejecución del programa. En Swing eso no ocurre automáticamente, es necesario programar ese comportamiento. Una manera de terminar la ejecución de un programa en Java es llamando al método estático exit() de la clase System. Ese método espera que le pasemos un número entero indicanco la razón por la cual se terminó la ejecución del programa. En muchos sistemas operativos, un cero como razón indica una terminación normal del programa.

Ahora tenemos que encontrar una manera de enterarnos que el jugador cerró la ventana. Para eso (y varias otras cosas más) las ventanas de Swing permiten que se registren con ellas unos objetos listeners (escuchadores) a los que les avisan cuando ocurren ciertos eventos en la ventana con la cual están registrados. Estos listeners tienen que implementar la interfaz WindowListener. Esta interfaz define varios métodos que deben estar existir en cualquier clase que la implemente. A nosotros únicamente nos interesa el método windowClosing(), que se llama cuando el jugador cierra la ventana. Para no tener que implementar una clase con todos los otros métodos que no nos interesan, existe una clase llamada WindowAdapter. Esta clase implementa WindowListener definiendo todos los métodos especificados por esa interfaz como métodos vacios que no hacen nada. Lo único que tenemos que hacer es definir una clase que herede de WindowAdapter, en la cual redefinimos únicamente los métodos que nos interesan, crear una instancia de esa clase y registrarla como listener de la ventana. Usando una vez más la notación de Java para clases anónimas, eso queda así:

jf.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
La linea siguiente es para indicarle a la ventana (JFrame) que el jugador no puede modificar su tamaño (obviamente estamos hablando del tamaño de la ventana, no sabemos si el jugador puede cambiar su propio tamaño y tampoco tiene nada que ver con nuestro programa):

jf.setResizable(false);
Después creamos una instancia de nuestra clase Demo1 y lo colocamos dentro de la ventana (recuerda que Demo1 hereda de JComponent y por lo tanto es un componente gráfico de Swing):

Demo1 demo1 = new Demo1();
jf.getContentPane().add(demo1);
Ahora le decimos a nuestro JFrame (ventana) que acomode todos los componentes que contiene y defina su tamaño:

jf.pack();
En este caso, la venta contiene un solo componente (nuestra instancia de Demo1), lo único que tiene que hacer es darle su tamaño preferido a ese componente (512 por 384 pixeles) y ajustar su propio tamaño alrededor de él.

Nuestra ventana ya está lista para aparecer en la pantalla. Y eso hacemos con la linea siguiente:

jf.setVisible(true);
Lo único que falta ahora es ejecutar el ciclo principal de nuestro "juego":

demo1.cicloPrincipalJuego();
Conclusión
Ya viste cómo programar la infraestructura más básica que se necesita para hacer un videojuego con Java. Es un programa bastante corto y sencillo (menos de 100 lineas). En el próximo artículo veremos cómo agregarle la parte de interacción con el jugador.



DESCARGAR JUEGOS JAVA PARA CELULAR
http://www.argim.net/descargar/juegos/

NO TE OLVIDES DE COMENTAR!!!