Programación de juegos: JFrame, JPanel, método paint

Para dibujar algo necesitamos una superficie donde pintar. Esta superficie o lienzo (Canvas en inglés) 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 código 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 línea "frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE)", cuando cerremos la ventana el programa no terminará y seguirá ejecutándose.

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 tamaño con el ratón, etc. En realidad cuando creamos un objeto JFrame iniciamos un motor que maneja la interfaz de usuario. Este motor se comunica con el sistema operativo tanto para pintar en la pantalla como para recibir información del teclado o el ratón. Llamaremos a este motor "Motor AWT" o "Motor Swing" ya que está compuesto por estas dos librerías. En las primeras versiones de java solo existía AWT y luego se agregó Swing. Este Motor utiliza varios hilos de ejecución.

¿Qué es un hilo o thread en java?

Normalmente un programa se ejecuta línea tras línea por un solo procesador en una sola línea o hilo de ejecución. El concepto de hilo (en ingles Thread) permite a un programa iniciar varias ejecuciones concurrentes. Esto es como si existieran varios procesadores ejecutándo 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 código de un método a la vez.

Podemos pensar que un hilo es un cocinero preparando un plato leyendo una receta de cocina. Dos hilos concurrentes serían como dos cocineros trabajando en la misma cocina, preparando cada uno un plato leyendo cada uno una receta o también podrían estar leyendo la misma receta. Los conflictos surgen por ejemplo cuando los dos intentan usar una sartén 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 aplicación con debug y vamos a la perspectiva Debug. Cada hilo es como si fuera un programa independiente ejecutándose al mismo tiempo que los otros hilos. Más adelante veremos más 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 ratón.

 

JPanel: El lienzo (Canvas en inglés)

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 método paint que es el método 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);
	}
}

El método paint recibe por parámetro un objeto Graphics2D que extiende de Graphics. Graphics es la vieja clase usada por AWT que ha sido reemplazada por Graphics2D que tiene más y mejor funcionalidad. El parámetro 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 métodos de Graphics2D para dibujar.

Lo primero que hacemos es elegir el color que utilizamos para dibujar: "g2d.setColor(Color.RED);". Luego dibujamos unos círculos y cuadrados.

Posicionamiento en el lienzo. Coordenadas x e y

Para dibujar algo dentro del lienzo debemos indicar en que posición comenzaremos a pintar. Para esto cada punto del lienzo tiene una posición (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 parámetros son la posición (x,y) y luego se indica el ancho y alto. como resultado tenemos un circulo de 30 pixeles de diámetro en la posición (0,0).

El circulo vacío se pinta con "g2d.drawOval(0, 50, 30, 30)": el la posición x=0 (pegado al margen izquierdo) y la posición y=50 (50 pixeles más abajo del margen superior) pinta un circulo de 30 pixeles de alto y 30 de ancho.

Los rectángulos se pintan con "g2d.fillRect(50, 0, 30, 30)" y "g2d.drawRect(50, 50, 30, 30)" de forma similar a los círculos.

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

Existen muchísimos métodos en Graphics2D. Algunos los veremos en siguientes tutoriales.

¿Cuándo el motor AWT llama al método paint?

El motor AWT llama al método 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 tamaño de la ventana con el ratón se llama a paint.

Podemos comprobar este comportamiento si ponemos un breakpoint en la primer línea del método paint y ejecutamos en modo debug.

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

<< Indice Siguiente >>