Cursos y Másters | ||
| Inicio Profesores Aulas Testimonios Cursos Masters Tecnologías Cursos Gratuitos Galería de Proyectos Solicitar Información |
| Página Anterior - Corrigiendo los fallos de sonido | Página Actual - 29 - Pequeñas optimizaciones | |
| Índice de cursos Java gratuitos |
Pequeñas optimizacionesEn nuestra última versión, realizaremos algunas pequeñas optimizaciones que nos ayudarán a incrementar nuestros fotogramas por segundo, dañadas desde que implementamos el sonido y las texturas. La primera optimización consiste en utilizar imágenes en formato "compatible". ¿Qué significa esto? Una imagen "compatible" es una imagen en memoria que tiene las mismas características (o al menos muy similares) al modo de vídeo nativo que se está mostrando en ese momento - en términos de bits por pixel, transparencia, etc. Las imágenes compatibles son mucho más rápidas de dibujar que cualquier otro formato. Ahora mismo lo que estamos haciendo es utilizar el formato que a ImageIO le apetezca devolvernos. Podemos optimizar esto de la siguiente forma:
Haremos todo esto en la clase SpriteCache, que es la que centraliza el acceso a las imágenes:
1 /** 2 * Curso B?sico de desarrollo de Juegos en Java - Invaders 3 * 4 * (c) 2004 Planetalia S.L. - Todos los derechos reservados. Prohibida su reproducci?n 5 * 6 * http://www.planetalia.com 7 * 8 */ 9 package version29; 10 11 import java.awt.Graphics; 12 import java.awt.GraphicsConfiguration; 13 import java.awt.GraphicsEnvironment; 14 import java.awt.Image; 15 import java.awt.Transparency; 16 import java.awt.image.BufferedImage; 17 import java.awt.image.ImageObserver; 18 import java.net.URL; 19 import javax.imageio.ImageIO; 20 21 public class SpriteCache extends ResourceCache implements ImageObserver{ 22 23 protected Object loadResource(URL url) { 24 try { 25 return ImageIO.read(url); 26 } catch (Exception e) { 27 System.out.println("No se pudo cargar la imagen de "+url); 28 System.out.println("El error fue : "+e.getClass().getName()+" "+e.getMessage()); 29 System.exit(0); 30 return null; 31 } 32 } 33 34 public BufferedImage createCompatible(int width, int height, int transparency) { 35 GraphicsConfiguration gc = 36 GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration(); 37 BufferedImage compatible = gc.createCompatibleImage(width,height,transparency); 38 return compatible; 39 } 40 41 public BufferedImage getSprite(String name) { 42 BufferedImage loaded = (BufferedImage)getResource(name); 43 BufferedImage compatible = createCompatible(loaded.getWidth(),loaded.getHeight(),Transparency.BITMASK); 44 Graphics g = compatible.getGraphics(); 45 g.drawImage(loaded,0,0,this); 46 return compatible; 47 } 48 49 public boolean imageUpdate(Image img, int infoflags,int x, int y, int w, int h) { 50 return (infoflags & (ALLBITS|ABORT)) == 0; 51 } 52 } 53 La segunda optimización consiste en librarnos del dibujo repetitivo de texturas dentro del bucle principal. En su lugar, procederemos de la siguiente forma: Nada mas arrancar el juego, creamos una imagen en memoria que tiene la misma anchura que la pantalla, pero con una altura igual a la de la pantalla mas el tamaño de la textura. Rellenamos esta imagen con el fondo que queremos utilizar. Durante el juego, simplemente copiamos una sub-imagen de esta imagen en memoria a la pantalla. El efecto de scroll se consigue variando la coordenada Y de inicio de la sub-imagen que pintamos. Esto nos permite limitar el uso de TexturePaint - que siempre es muy lento- a una única vez, y además fuera del bucle principal del juego:
1 package version29; 2 /** 3 * Curso B?sico de desarrollo de Juegos en Java - Invaders 4 * 5 * (c) 2004 Planetalia S.L. - Todos los derechos reservados. Prohibida su reproducci?n 6 * 7 * http://www.planetalia.com 8 * 9 */ 10 11 import java.awt.Canvas; 12 import java.awt.Color; 13 import java.awt.Cursor; 14 import java.awt.Dimension; 15 import java.awt.Font; 16 import java.awt.Graphics2D; 17 import java.awt.Point; 18 import java.awt.Rectangle; 19 import java.awt.TexturePaint; 20 import java.awt.Toolkit; 21 import java.awt.Transparency; 22 import java.awt.event.KeyEvent; 23 import java.awt.event.KeyListener; 24 import java.awt.event.WindowAdapter; 25 import java.awt.event.WindowEvent; 26 import java.awt.image.BufferStrategy; 27 import java.awt.image.BufferedImage; 28 import java.util.ArrayList; 29 30 import javax.swing.JFrame; 31 import javax.swing.JPanel; 32 33 public class Invaders extends Canvas implements Stage, KeyListener { 34 35 private BufferStrategy strategy; 36 private long usedTime; 37 38 private SpriteCache spriteCache; 39 private SoundCache soundCache; 40 private ArrayList actors; 41 private Player player; 44 45 private boolean gameEnded=false; 46 47 public Invaders() { 48 spriteCache = new SpriteCache(); 49 soundCache = new SoundCache(); 50 51 52 JFrame ventana = new JFrame("Invaders"); 53 JPanel panel = (JPanel)ventana.getContentPane(); 54 setBounds(0,0,Stage.WIDTH,Stage.HEIGHT); 55 panel.setPreferredSize(new Dimension(Stage.WIDTH,Stage.HEIGHT)); 56 panel.setLayout(null); 57 panel.add(this); 58 ventana.setBounds(0,0,Stage.WIDTH,Stage.HEIGHT); 59 ventana.setVisible(true); 60 ventana.addWindowListener( new WindowAdapter() { 61 public void windowClosing(WindowEvent e) { 62 System.exit(0); 63 } 64 }); 65 ventana.setResizable(false); 66 createBufferStrategy(2); 67 strategy = getBufferStrategy(); 68 requestFocus(); 69 addKeyListener(this); 70 71 setIgnoreRepaint(true); 72 73 BufferedImage cursor = spriteCache.createCompatible(10,10,Transparency.BITMASK); 74 Toolkit t = Toolkit.getDefaultToolkit(); 75 Cursor c = t.createCustomCursor(cursor,new Point(5,5),"null"); 76 setCursor(c); 77 } 78 79 public void gameOver() { 80 gameEnded = true; 81 } 82 83 public void initWorld() { 84 actors = new ArrayList(); 85 for (int i = 0; i < 10; i++){ 86 Monster m = new Monster(this); 87 m.setX( (int)(Math.random()*Stage.WIDTH) ); 88 m.setY( i*20 ); 89 m.setVx( (int)(Math.random()*20-10) ); 90 91 actors.add(m); 92 } 93 94 player = new Player(this); 95 player.setX(Stage.WIDTH/2); 96 player.setY(Stage.PLAY_HEIGHT - 2*player.getHeight()); 97 98 soundCache.loopSound("musica.wav"); 99 La tercera optimización consiste en librarnos del cursor del ratón:
. . .
47 public Invaders() {
48 spriteCache = new SpriteCache();
49 soundCache = new SoundCache();
50
51
52 JFrame ventana = new JFrame("Invaders");
53 JPanel panel = (JPanel)ventana.getContentPane();
54 setBounds(0,0,Stage.WIDTH,Stage.HEIGHT);
55 panel.setPreferredSize(new Dimension(Stage.WIDTH,Stage.HEIGHT));
56 panel.setLayout(null);
57 panel.add(this);
58 ventana.setBounds(0,0,Stage.WIDTH,Stage.HEIGHT);
59 ventana.setVisible(true);
60 ventana.addWindowListener( new WindowAdapter() {
61 public void windowClosing(WindowEvent e) {
62 System.exit(0);
63 }
64 });
65 ventana.setResizable(false);
66 createBufferStrategy(2);
67 strategy = getBufferStrategy();
68 requestFocus();
69 addKeyListener(this);
70
71 setIgnoreRepaint(true);
72
La siguiente optimización trata con un problema que es posible que nos hayamos encontrado durante el juego. Si movemos la ventana a un segundo plano y la volvemos a poner en primer plano, veremos un parpadeo gris por unos instantes. No es muy problemático, pero es muy poco profesional. Lo que está ocurriendo es que el AWT está lanzando un evento de redibujo desde su propio hilo, mientras nosotros estamos haciendo otra cosa. Dado que AWT no utiliza un doble búfer, por unos instantes vemos una pantalla gris completamente borrada - la misma situación que teníamos nosotros al comenzar. Para evitar esto, debemos indicarle a AWT que ignore las peticiones de redibujo que provengan del sistema operativo, ya que nosotros mismos nos estamos haciendo cargo del dibujo de la pantalla. Esto se consigue llamando al método setIgnoreRepaint()
. . .
47 public Invaders() {
48 spriteCache = new SpriteCache();
49 soundCache = new SoundCache();
50
51
52 JFrame ventana = new JFrame("Invaders");
53 JPanel panel = (JPanel)ventana.getContentPane();
54 setBounds(0,0,Stage.WIDTH,Stage.HEIGHT);
55 panel.setPreferredSize(new Dimension(Stage.WIDTH,Stage.HEIGHT));
56 panel.setLayout(null);
57 panel.add(this);
58 ventana.setBounds(0,0,Stage.WIDTH,Stage.HEIGHT);
59 ventana.setVisible(true);
60 ventana.addWindowListener( new WindowAdapter() {
61 public void windowClosing(WindowEvent e) {
62 System.exit(0);
63 }
64 });
65 ventana.setResizable(false);
66 createBufferStrategy(2);
67 strategy = getBufferStrategy();
68 requestFocus();
69 addKeyListener(this);
70
72
73 BufferedImage cursor = spriteCache.createCompatible(10,10,Transparency.BITMASK);
74 Toolkit t = Toolkit.getDefaultToolkit();
75 Cursor c = t.createCustomCursor(cursor,new Point(5,5),"null");
76 setCursor(c);
77 }
78
. . .
Finalmente, tenemos un problema con el juego y es que su velocidad varía un poco dependiendo de la velocidad del ordenador. Dado que la pausa que hacemos es siempre la misma, si el ordenador es rápido la actualización del escenario será rápida y el juego irá rápido. Sin embargo, si el ordenador es lento, consumirá mucho tiempo en la actualización del escenario y aún así estamos esperando la misma cantidad de tiempo en el bucle principal, con lo cual el juego va más lento. Como esto no es muy agradable lo corregiremos exigiendo que cada turno dure cierta cantidad fija de tiempo. Esta cantidad dependerá del número de fotogramas que queremos alcanzar. Por ejemplo si queremos 60 fts, cada turno debe durar 1000/60 = 16,66 milisegundos. De forma que en lugar de dormir siempre la misma cantidad de tiempo (10ms), en cada turno calcularemos cuánto hemos invertido en la actualización y redibujo de la pantalla y dormiremos el tiempo que nos quede:
. . .
240 public void game() {
241 usedTime=1000;
242 initWorld();
243 while (isVisible() && !gameEnded) {
244 long startTime = System.currentTimeMillis();
245 backgroundY--;
246 if (backgroundY < 0)
247 backgroundY = backgroundTile.getHeight();
248 updateWorld();
249 checkCollisions();
250 paintWorld();
251 usedTime = System.currentTimeMillis()-startTime;
255 }
256 paintGameOver();
257 }
. . .
Bien, hemos llegado al final de este tutorial de Java. Ahora es tu turno para comenzar a hacer juegos. Si te ha gustado este curso de java, puedes poner un enlace a su índice para que otros lo puedan disfrutar también. Si tienes algún comentario o alguna corrección o sugerencia, puedes mandarme una nota desde aquí. Es posible que en el futuro añada más tutoriales y cursos sobre temas similares, en cuyo caso aparecerán en la página de cursos gratuitos de Planetalia. |
||||||||||||||||||||||||||||||||
¿Quieres ser notificado cuando salan nuevos tutoriales y cursos?. Pulsa aquí
| Actor.java | Bomb.java | Bullet.java | Invaders.java |
| Laser.java | Monster.java | Player.java | ResourceCache.java |
| SoundCache.java | SpriteCache.java | Stage.java |
| Página Anterior - Corrigiendo los fallos de sonido | Página Actual - 29 - Pequeñas optimizaciones | |
| Índice de cursos Java gratuitos |
(c) 2004 Planetalia S.L. Todos los derechos reservados. Prohibida su reproducción