Training and Consulting | ||
| Home |
| Previous Page - Fixing the sound | Current Page - 29 - Small optimizations | |
| Return to the index of free Java tutorials |
Small optimizationsIn our last version, we will perform some optimizations that will help improve our framerate, greatly affected by now because of the sounds and textures we are using. The first optimization consists in using images in a compatible format. What does this mean? A compatible immage is an image in memory that has the same (or at least very similar) characteristics as the native video mode we are using - in terms of bits per pixel, transparency, etc. Compatible images are much faster to draw than images in any other format. Right now we are using the images in whatever format ImageIO chooses to return them. We can optimize this by :
We will do all this in the SpriteCache class :
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 The second optimization consists in getting rid of the repetitive texture painting in the main loop. How? When we start the game, we create an off-screen image that has the same width as the screen, but is higher than the screen in exactly the size of the background tile. We fill this image with our background texture. During the gameplay, we simply draw a sub-image of this background to the screen. The scrolling effect is achieved varying the Y coordinate of the sub-image we choose to draw. This allows us to use TexturePaint - which is very slow - just once, and outside the main loop:
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 Our third optimization will be getting rid of the mouse cursor:
. . .
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
Our next optimization deals with a problem you may have encountered while playing the game. If you move the game to the background, and then to the foreground again during play, you will see a gray flicker for a moment. It isn't very problematic, but it is rather unprofessional. What is happening here is that the AWT is launching a repaint event from its thread while we are doing something else. And since the AWT is not using our double buffer strategy but is rather drawing directly on the screen, for a moment we see a completely erased gray screen. To prevent this, we must tell the AWT to ignore repaint requests coming from the OS since it's we ourselves who are doing the repainting. This is achieved by calling the setIgnoreRepaint() method:
. . .
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
. . .
And finally, we have a problem with the current game and it is that the speed of the game is a bit dependant on the speed of the computer. If a faster computer completes a turn in less time, the game will run faster. This is not very pleasant, so we are going to correct it by requiring that each turn takes a fixed amount of time. This amount of time depends on the number of frames per second we want to get. For example, if we want 60 fps, each turn must take exactly 16,66 ms. So instead of sleeping always a fixed amount of time (10ms), each turn we will calculate how much time we have invested in updating and redrawing the world, and we will sleep the remaining time:
. . .
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 }
. . .
So we have finally arrived at the end... Now is your turn to make games. If you like this tutorial and develop a game, link to the index page for others to enjoy too. If you have any comments, or want to correct my poor english :-), or want to suggest improvements, drop me a note here. |
||||||||||||||||||||||||||||||||
Do you want to be notified when new tutorials or lessons are published? Press here
| Bomb.java | Bullet.java | Laser.java | Stage.java |
| ResourceCache.java | Monster.java | SpriteCache.java | Invaders.java |
| Actor.java | SoundCache.java | Player.java |
| Previous Page - Fixing the sound | Current Page - 29 - Small optimizations | |
| Return to the index of free Java tutorials |
(c) 2004 Planetalia S.L. All rights reserved. Unauthorized reproduction and/or mirroring is not permitted