JavaFX es un conjunto de herramientas GUI de plataforma cruzada para Java y es el sucesor de las bibliotecas Java Swing. En
este tutorial, exploraremos las características de JavaFX que lo hacen
fácil de usar para comenzar a programar juegos en Java.
Si
ya desarrolla aplicaciones con Java, probablemente no necesite
descargar nada en absoluto: JavaFX se ha incluido con el paquete JDK
(Java Development Kit) estándar desde JDK versión 7u6 (agosto de 2012). Si
no ha actualizado su instalación de Java en un tiempo, diríjase al
sitio web de descarga de Java para obtener la versión más reciente.
Clases básicas del marco
La creación de un programa JavaFX comienza con la clase Application, desde la que se extienden todas las aplicaciones JavaFX. Su
clase principal debe llamar al método launch(), que entonces llamará
al método init() ya continuación al método start(), esperará a que
finalice la aplicación y, a continuación, llame al método stop(). De estos métodos, sólo el método start() es abstracto y debe anularse.
La clase Stage es el contenedor JavaFX de nivel superior. Cuando se inicia una aplicación, se crea una fase inicial y se pasa al
método de inicio de la aplicación. Las
etapas controlan las propiedades básicas de la ventana, como título,
icono, visibilidad, resizable, modo de pantalla completa y decoraciones;
El último se configura usando StageStyle. Pueden construirse Stages adicionales según sea necesario. Después de que se ha configurado una
etapa y se agrega el contenido, se llama al método show().
Sabiendo todo
esto, podemos escribir un ejemplo mínimo que lanza una ventana en
JavaFX:
1
importjavafx.application.Application;
2
importjavafx.stage.Stage;
3
4
publicclassExample1extendsApplication
5
{
6
publicstaticvoidmain(String[]args)
7
{
8
launch(args);
9
}
10
11
publicvoidstart(StagetheStage)
12
{
13
theStage.setTitle("Hello, World!");
14
theStage.show();
15
}
16
}
Estructuración del Contenido
El
contenido en JavaFX (como el texto, las imágenes y los controles de
interfaz de usuario) se organiza utilizando una estructura de datos
similar a un árbol, conocida como una gráfico de escena, que agrupa y
organiza los elementos de una gráfico de escena .
Representación de un gráfico de escena JavaFX.
Un elemento general de un gráfico de escena en JavaFX se denomina Nodo. Cada Nodo en un árbol tiene un único nodo "padre", con la excepción de
un nodo especial designado como "raíz". Un Group es un nodo que puede
tener muchos elementos en los nodo "secundario". Las transformaciones
gráficas (traducción, rotación y escala) y los efectos aplicados a un
grupo también se aplican a sus hijos. Los
nodos se pueden diseñar utilizando JavaFX Cascading Style Sheets (CSS),
muy similar al CSS utilizado para formatear documentos HTML.
La
clase Scene contiene todo el contenido de un gráfico de escena y
requiere que se establezca un nodo raíz (en la práctica, éste suele ser
un grupo). Usted puede fijar el tamaño de una escena específicamente; De
lo contrario, el tamaño de una escena se calculará automáticamente en
función de su contenido. Un objeto de escena se debe pasar al escenario
(por el método setScene()) para que se muestre.
Rendimiento de gráficos
¡La renderización de gráficos es particularmente importante para los
programadores de juegos! En
JavaFX, el objeto Canvas es una imagen en la que podemos dibujar texto,
formas e imágenes, utilizando su objeto GraphicsContext asociado. (Para
los desarrolladores familiarizados con el conjunto de
herramientas Java Swing, esto es similar al objeto Graphics pasado al
método paint() en la clase JFrame).
El objeto GraphicsContext contiene
una gran cantidad de poderosas capacidades de personalización. Para
elegir colores para dibujar texto y formas, puede establecer los
colores de relleno (interior) y de trazo (borde), que son objetos Paint: estos pueden ser un solo color sólido, un degradado definido
por el usuario (LinearGradient o RadialGradient) o Incluso un
ImagePattern. También puede aplicar uno o más objetos Effect de estilo,
como
Lighting, Shadow o GaussianBlur y cambiar las fuentes del valor
predeterminado utilizando la clase Font.
La
clase Image hace que sea fácil cargar imágenes de una variedad de
formatos de archivos y dibujarlos a través de la clase GraphicsContext. Es fácil construir imágenes generadas mediante procedimientos usando
la clase WritableImage junto con las clases PixelReader y
PixelWriter.
Usando estas clases, podemos escribir un ejemplo de estilo "Hola, Mundo" mucho más digno con lo siguiente. Para la brevedad, sólo vamos a
incluir el método start() aquí (saltaremos las declaraciones de
importación y el método main()); Sin embargo, el código fuente de
trabajo completo se puede encontrar en el repositorio de GitHub que acompaña a
este tutorial.
1
publicvoidstart(StagetheStage)
2
{
3
theStage.setTitle("Canvas Example");
4
5
Grouproot=newGroup();
6
ScenetheScene=newScene(root);
7
theStage.setScene(theScene);
8
9
Canvascanvas=newCanvas(400,200);
10
root.getChildren().add(canvas);
11
12
GraphicsContextgc=canvas.getGraphicsContext2D();
13
14
gc.setFill(Color.RED);
15
gc.setStroke(Color.BLACK);
16
gc.setLineWidth(2);
17
FonttheFont=Font.font("Times New Roman",FontWeight.BOLD,48);
18
gc.setFont(theFont);
19
gc.fillText("Hello, World!",60,50);
20
gc.strokeText("Hello, World!",60,50);
21
22
Imageearth=newImage("earth.png");
23
gc.drawImage(earth,180,100);
24
25
theStage.show();
26
}
El bucle del juego
A
continuación, necesitamos hacer que nuestros programas sean dinámicos,
lo que significa que el estado del juego cambia con el tiempo. Vamos a
implementar un bucle de juego: un bucle infinito que actualiza
los objetos del juego y hace que la escena a la pantalla, idealmente a
una tasa de 60 veces por segundo.
La
forma más fácil de lograr esto en JavaFX es usar la clase
AnimationTimer, donde se puede escribir un método llamado handle() que
se llamará a una velocidad de 60 veces por segundo, o tan cerca de esa
tasa como sea posible. (Esta clase no tiene que ser usada solamente para
propósitos de animación, sino que es capaz de mucho más.)
Usar
la clase AnimationTimer es un poco complicado: ya que es una clase
abstracta, no se puede crear directamente—la clase debe ampliarse antes
de que se pueda crear una instancia. Sin embargo, para nuestros
ejemplos simples, extenderemos la clase escribiendo una clase interna
anónima. Esta
clase interna debe definir el método abstracto handle(), que se pasará
un argumento único: el tiempo del sistema actual en nanosegundos. Después de definir la clase interna, inmediatamente invocamos el método
start(), que inicia el bucle. (El bucle se puede detener llamando al
método stop().)
Con
estas clases, podemos modificar nuestro ejemplo "Hello, World", creando
una animación que consiste en la Tierra orbitando alrededor del Sol
contra una imagen de fondo estrellada.
Hay formas alternativas de implementar un bucle de juego en JavaFX. Un
enfoque ligeramente más largo (pero más flexible) implica la clase
Timeline, que es una secuencia de animación que consiste en un conjunto
de objetos KeyFrame. Para crear un bucle de juego, la Línea
de tiempo debe establecerse para repetirse indefinidamente y solo se
requiere un único KeyFrame, con su Duration establecida en 0.016
segundos (para alcanzar 60 ciclos por segundo). Esta implementación se
puede encontrar en el archivo Example3T.java en el repo de
GitHub.
Animación basada en cuadros
Otro
componente de programación de juego comúnmente necesario es la
animación basada en cuadros: mostrar una secuencia de imágenes en rápida
sucesión para crear la ilusión de movimiento.
Suponiendo
que todas las animaciones de bucle y todos los cuadros se muestran para
el mismo número de segundos, una implementación básica podría ser tan
simple como sigue:
Para integrar esta clase en el ejemplo anterior, podríamos crear un OVNI animado, inicializando el objeto utilizando el código:
1
AnimatedImageufo=newAnimatedImage();
2
Image[]imageArray=newImage[6];
3
for(inti=0;i<6;i++)
4
imageArray[i]=newImage("ufo_"+i+".png");
5
ufo.frames=imageArray;
6
ufo.duration=0.100;
... y, dentro de AnimationTimer, añadiendo la única línea de código:
1
gc.drawImage(ufo.getFrame(t),450,25);
... en el lugar apropiado. Para obtener un ejemplo completo de código de trabajo, vea el archivo Example3AI.java en el repo de GitHub.
Manejo de la entrada de usuario
Detectar y procesar la entrada de usuario en JavaFX es sencillo. Las
acciones del usuario que pueden ser detectadas por el sistema, como
pulsaciones de teclas y clics del ratón, se denominan eventos. En
JavaFX, estas acciones provocan automáticamente la generación de
objetos (como KeyEvent y MouseEvent) que almacenan los datos asociados
(como la tecla real presionada o la ubicación del puntero del ratón). Cualquier clase JavaFX que implementa la clase EventTarget, como una
Scene, puede "escuchar" eventos y manejarlos; En los ejemplos que
siguen, mostraremos cómo configurar una escena para procesar varios
eventos.
Mirando
a través de la documentación de la clase Scene, hay muchos métodos que
escuchan para manejar diferentes tipos de entrada de diferentes fuentes. Por
ejemplo, el método setOnKeyPressed() puede asignar un EventHandler que
se activará cuando se presione una tecla, el método setOnMouseClicked () puede asignar un EventHandler que se active cuando se presiona un
botón del ratón, etc. La clase EventHandler tiene un propósito:
encapsular un método (llamado
handle()) que se llama cuando se produce el evento correspondiente.
Al
crear un EventHandler, debe especificar el tipo de Evento que maneja:
puede declarar un EventHandler<KeyEvent> o un EventHandler<MouseEvent>, por ejemplo. se vuelve verde. Además, EventHandlers a menudo se crean como
clases internas anónimas,
ya que normalmente sólo se utilizan una vez (cuando se pasan como
argumento a uno de los métodos enumerados anteriormente).
Manejo de
eventos de teclado
La
entrada de usuario se procesa a menudo dentro del bucle del juego
principal y, por lo tanto, se debe mantener un registro de las teclas
que están actualmente activas. Una forma de lograr esto es creando un
ArrayList de objetos String. Cuando una tecla es presionada
inicialmente, agregamos la representación String del KeyCode de KeyEvent
a la lista; Cuando se suelta la llave, la eliminamos de la lista.
En el
ejemplo siguiente, el lienzo contiene dos imágenes de teclas de flecha;
Cuando se pulsa una tecla, la imagen correspondiente
El código fuente está contenido en el archivo Example4K.java en el repo de GitHub.
1
publicvoidstart(StagetheStage)
2
{
3
theStage.setTitle("Keyboard Example");
4
5
Grouproot=newGroup();
6
ScenetheScene=newScene(root);
7
theStage.setScene(theScene);
8
9
Canvascanvas=newCanvas(512-64,256);
10
root.getChildren().add(canvas);
11
12
ArrayList<String>input=newArrayList<String>();
13
14
theScene.setOnKeyPressed(
15
newEventHandler<KeyEvent>()
16
{
17
publicvoidhandle(KeyEvente)
18
{
19
Stringcode=e.getCode().toString();
20
21
// only add once... prevent duplicates
22
if(!input.contains(code))
23
input.add(code);
24
}
25
});
26
27
theScene.setOnKeyReleased(
28
newEventHandler<KeyEvent>()
29
{
30
publicvoidhandle(KeyEvente)
31
{
32
Stringcode=e.getCode().toString();
33
input.remove(code);
34
}
35
});
36
37
GraphicsContextgc=canvas.getGraphicsContext2D();
38
39
Imageleft=newImage("left.png");
40
ImageleftG=newImage("leftG.png");
41
42
Imageright=newImage("right.png");
43
ImagerightG=newImage("rightG.png");
44
45
newAnimationTimer()
46
{
47
publicvoidhandle(longcurrentNanoTime)
48
{
49
// Clear the canvas
50
gc.clearRect(0,0,512,512);
51
52
if(input.contains("LEFT"))
53
gc.drawImage(leftG,64,64);
54
else
55
gc.drawImage(left,64,64);
56
57
if(input.contains("RIGHT"))
58
gc.drawImage(rightG,256,64);
59
else
60
gc.drawImage(right,256,64);
61
}
62
}.start();
63
64
theStage.show();
65
}
Manejo de eventos del mouse.
Ahora veamos un ejemplo que se centra en la clase MouseEvent en lugar de
la clase KeyEvent. En este mini-juego, el jugador gana un punto cada
vez que se hace clic en el objetivo
Dado
que los EventHandlers son clases internas, cualquier variable que
utilice debe ser final o "efectivamente final", lo que significa que las
variables no pueden ser reinicializadas. En el
ejemplo anterior, los datos fueron pasados al EventHandler por medio de
un ArrayList, cuyos valores pueden ser cambiados sin reinicializar (a
través de los métodos add() y remove()).
Sin embargo, en el caso de
tipos de datos básicos, los valores no se pueden cambiar una vez
inicializados. Si
desea que el Administrador de eventos tenga acceso a tipos de datos
básicos que se cambian en otro lugar del programa, puede crear una clase
de contenedor que contenga variables públicas o métodos getter /
setter. (En el ejemplo siguiente, IntValue es una clase que contiene una
variable publicint denominada value.)
El código fuente completo está contenido en el repo de GitHub; La clase principal es Example4M.java.
Creación de una clase básica Sprite con JavaFX
En los videojuegos, un sprite es el término para una sola entidad
visual. A
continuación se muestra un ejemplo de una clase Sprite que almacena una
imagen y posición, así como información de velocidad (para entidades
móviles) e información de ancho / altura que se usan al calcular cuadros
delimitadores para propósitos de detección de colisiones. También
tenemos los métodos getter / setter estándar para la mayoría de estos
datos (omitidos por brevedad), y algunos métodos estándar necesarios en
el desarrollo de juegos:
update(): calcula la nueva posición basada
en la velocidad del Sprite.
render(): dibuja la imagen asociada al
lienzo (a través de la clase GraphicsContext) utilizando la posición
como coordenadas.
getBoundary(): devuelve un objeto JavaFX
Rectangle2D, útil en la detección de colisiones debido a su método de
intersección.
intersects(): determina si el cuadro delimitador de
este Sprite intersecta con el de otro Sprite.
El código fuente completo se incluye en Sprite.java en el repo de GitHub.
Uso de la clase Sprite
Con la ayuda de la clase Sprite, podemos crear fácilmente un simple juego de recolección en JavaFX. En
este juego, usted asume el papel de un maletín sensitivo cuyo objetivo
es recoger los muchos bolsos del dinero que se han dejado el mentir
alrededor por un dueño anterior descuidado. Las teclas de flecha mueven el jugador en la pantalla.
Este código
toma mucho de los ejemplos anteriores: configurar fuentes para mostrar
la partitura, almacenar la entrada de teclado con una ArrayList,
implementar el bucle de juego con un AnimationTimer y crear clases de
contenedor para valores simples que deben modificarse durante el bucle
del juego.
Un
segmento de código de particular interés involucra la creación de un
objeto Sprite para el jugador (maletín) y un ArrayList de objetos Sprite
para los coleccionables (bolsas de dinero):
Otro segmento de código de interés es la creación de AnimationTimer, que está encargado de:
Calcular el tiempo transcurrido desde la última actualización
Ajustando la velocidad del jugador dependiendo de las teclas
presionadas
Realizar
la detección de colisiones entre el jugador y coleccionables y
actualizar los puntos y la lista de objetos coleccionables cuando esto
ocurre (se utiliza un Iterador en lugar de ArrayList directamente para
evitar una excepción de modificación simultánea al eliminar objetos de
la lista)
Puede estar interesado
en aprender cómo usar Scene Builder, un entorno de diseño visual para
diseñar interfaces de usuario. Este
programa genera FXML, que es un lenguaje basado en XML que se puede
utilizar para definir una interfaz de usuario para un programa JavaFX. Para esto, vea JavaFX Scene Builder: Getting Started.
FX
Experience es un excelente blog, actualizado con regularidad, que
contiene información y ejemplos de proyectos de interés para los
desarrolladores de JavaFX. ¡Muchas de las demostraciones enumeradas son
absolutamente inspiradoras!
El proyecto JFxtras consiste en un grupo de desarrolladores
que
han creado componentes JavaFX adicionales que proporcionan la
funcionalidad comúnmente necesaria que falta actualmente en
JavaFX.
El proyecto JavaFXPorts le permite empaquetar su aplicación
JavaFX para su implementación en iOS y Android.
En este tutorial, te he presentado a las clases
JavaFX que son útiles en la programación de juegos. Trabajamos
a través de una serie de ejemplos de creciente complejidad, culminando
en un juego basado en sprite de estilo de colección. Ahora
ya está listo para investigar algunos de los recursos enumerados
anteriormente, o para sumergirse y comenzar a crear su propio juego. ¡La
mejor de las suertes a ti en tus esfuerzos!