2) Double Buffering Drawing
gameRender() dibuja su propio objeto Graphics, que representa una imagen del mismo tamaño que la pantalla (dbImage)
// Variables globales para el renderizado fuera de pantalla private Graphics dbg; private Image dbImage = null; private void gameRender() //dibuja el frame actual en un buffer de imagen { if (dbImage == null){ // crea el buffer dbImage = createImage(PWIDTH, PHEIGHT); if (dbImage == null) { System.out.println("dbImage es nulo"); return; } else dbg = dbImage.getGraphics(); } // limpia el background dbg.setColor(Color.white); dbg.fillRect (0, 0, PWIDTH, PHEIGHT); // dibuja los elementos del juego if (gameOver) gameOverMessage(dbg); } // fin de gameRender() private void gameOverMessage(Graphics g) // centra el mensaje de game-over { g.drawString(msg, x, y); } // fin de gameOverMessage()
El doble buffer, como ya sabemos, lo que hace es dibujar las operaciones requeridas para la renderizacion pero en una imagen alterna, no directamente en la pantalla
paintComponent() es la función que dibuja, es llamado en el bucle de run() y va después de la renderizacion
public void paintComponent(Graphics g) { super.paintComponent(g); if (dbImage != null) g.drawImage(dbImage, 0, 0, null); }
La razón o ventaja de usar el doble buffer es que si se dibuja directamente en la pantalla, se hace lento y el usuario lo va a anotar, drawImage() es lo suficientemente rápido para pasar desapercibido
paintComponent() se debe mantener simple porque muchas veces la maquina virtual de java lo necesita fuera de su funcionamiento en el hilo de la animación, por ejemplo cuando se pone otra ventana(programa) por delante de ella
No cometan el error de poner el comportamiento del juego (IA) dentro de la función paintComponent() porque sino el juego continuaría a pesar de que la ventana no se este utilizando
3) Adding User Interaction
Ahora veremos como se monitorea el teclado y el mouse.
GamePanel() utiliza el teclado para cambiar su variable boolean a falsa, y así termina el bucle de animación y la aplicación, la función testPress() servirá para procesar los eventos del mouse
public GamePanel() { setBackground(Color.white); setPreferredSize( new Dimension(PWIDTH, PHEIGHT)); setFocusable(true); requestFocus(); // JPanel ahora recibe eventos de teclado readyForTermination(); // crear los componentes de un juego // escucha los eventos del mouse addMouseListener( new MouseAdapter() { public void mousePressed(MouseEvent e) { testPress(e.getX(), e.getY()); } }); } // Fin de GamePanel()
readyForTermination() es la función de monitoreo de el teclado y testares() del mouse
private void readyForTermination() { addKeyListener( new KeyAdapter() { // Escucha a esc, q, fin, ctrl + c public void keyPressed(KeyEvent e) { int keyCode = e.getKeyCode(); if ((keyCode == KeyEvent.VK_ESCAPE) || (keyCode == KeyEvent.VK_Q) || (keyCode == KeyEvent.VK_END) || ((keyCode == KeyEvent.VK_C) && e.isControlDown()) ) { running = false; } } }); } // fin de readyForTermination() private void testPress(int x, int y) // es (x,y) importante para el juego? { if (!gameOver) { // hace algo } }
4. Active Rendering
Como repaint() es solo un pedido es difícil saber cuando acaba, era el problema que vimos antes, hasta ahora siempre adivinamos tiempo de la siesta, si lo hacemos muy larga, estamos haciendo el juego lentos por las puras, y si lo hacemos muy corto el juego puede saltera los frames, pero la verdad es que adivinar el tiempo de la siesta no es la mejor opción, ya que el tiempo que toma renderizar y actualizar varia dependiendo de la actividad que haya en el juego, entonces habrá que idear una solución
public void run() /* actualiza, renderiza y duerme constantemente*/ { running = true; while(running) { gameUpdate(); // Actualiza el estado del juego gameRender(); // renderiza en un buffer paintScreen(); // dibuja el buffer en la pantalla try {Thread.sleep(20); // pequeña siesta } catch(InterruptedException ex){} } System.exit(0); // cierra JFrame o JApplet si existen } // fin de run() private void paintScreen() // constantemente renderiza la imagen del buffer en la pantalla { Graphics g; try { g = this.getGraphics(); // trae el contexto grafico del panel if ((g != null) && (dbImage != null)) g.drawImage(dbImage, 0, 0, null); g.dispose(); } catch (Exception e) { System.out.println("Error del contexto grafico: " + e); } } // fin de paintScreen()
Con estos cambios ahora el renderizado pasa a nuestras manos y el tiempo puede ser medido y las preocupaciones de los repaint ya no existirán o serán menores
Explicando un poco el código la razón por la que usamos this.getGraphics(); es porque la maquina virtual cambia el contexto gráfico del panel cuando lo minimizan o ponen un programa por delante, es por eso que debe ser obtenido siempre que se necesite la función, por ejemplo una excepción sería cuando en un applet cierran la pagina web.