Planetalia - Java Training

Training and Consulting

Home

Tutorial - Writing a Space Invaders game in Java

  Previous Page - Monster respawning Current Page - 27 - Simple sound   Next Page - Fixing the sound
  Return to the index of free Java tutorials  

Simple sound

(c) Alexander Hristov

Adding simple sound to our game is very easy, yet it can have a huge effect on the overall quality on the game. Actually, most games produced currently spend much more on graphics and sound than on programming, which by the way results quite often in games with excelent visuals and sound but terrible gameplay and/or plot.

I'll use several .wav files for the different effects - shooting, getting hit, etc, - as well as a .wav file as an "ambient music loop". The files are the following:

File UsageSource
musica.wav Ambient musicReally can't remember; downloaded somewhere
photon.wav Alien firing his laser here
explosion.wav Alien killed here
missile.wav Player fires a missile here

Sounds are just another resource, and as such we need to code a "Sound cache" class much in the same way we have a "Sprite cache" class. Since they both share a great amount of functions, we can try to refactor that code and extract a common ancestor class, which we'll call ResourceCache. The code will be the following:


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     
10    package version27;
11    
12    import java.net.URL;
13    import java.util.HashMap;
14    
15    public abstract class ResourceCache {
16      protected HashMap resources;
17      
18      public ResourceCache() {
19        resources = new HashMap();
20      }
21      
22      protected Object loadResource(String name) {
23        URL url=null;
24        url = getClass().getClassLoader().getResource(name);
25        return loadResource(url);
26      }
27      
28      protected Object getResource(String name) {
29        Object res = resources.get(name);
30        if (res == null) {
31          res = loadResource("res/"+name);
32          resources.put(name,res);
33        }
34        return res;
35      }
36      
37      protected abstract Object loadResource(URL url);
38    
39    }
40    

Now SpriteCache is rewritten as follows:


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 version27;
10    
11    import java.awt.image.BufferedImage;
12    import java.net.URL;
13    import javax.imageio.ImageIO;
14    
15    public class SpriteCache extends ResourceCache{
16      
17      protected Object loadResource(URL url) {
18        try {
19          return ImageIO.read(url);
20        } catch (Exception e) {
21          System.out.println("No se pudo cargar la imagen de "+url);
22          System.out.println("El error fue : "+e.getClass().getName()+" "+e.getMessage());
23          System.exit(0);
24          return null;
25        }
26      }
27      
28      public BufferedImage getSprite(String name) {
29        return (BufferedImage)getResource(name);
30      }
31    }
32    

And we'll add a new SoundCache class that will be in charge of locating and loading sound files. It's code is very similar to the SpriteCache, but it returns AudioClips instead of BufferedImages. It also implements a pair of additional methods called playSound() and loopSound()


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 version27;
10    
11    import java.applet.Applet;
12    import java.applet.AudioClip;
13    import java.net.URL;
14    
15    public class SoundCache extends ResourceCache{
16      protected Object loadResource(URL url) {
17        return Applet.newAudioClip(url);
18        
19      }
20      public AudioClip getAudioClip(String name) {
21        return (AudioClip)getResource(name);  
22      }
23      
24      public void playSound(final String name) {
25        getAudioClip(name).play();
26      }
27      
28      public void loopSound(final String name) {
29        getAudioClip(name).loop();
30      }
31    
32    }
33    

Since any class can request a sound to be played, any class should be able to obtain a reference to the sound cache. As always, these global requirements are handled through the Stage interface:


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 version27;
10    
11    import java.awt.image.ImageObserver;
12    
13    public interface Stage extends ImageObserver {
14      public static final int WIDTH=640;
15      public static final int HEIGHT=480;
16      public static final int PLAY_HEIGHT = 400; 
17      public static final int SPEED=10;
18      public SpriteCache getSpriteCache();
19 public SoundCache getSoundCache();
20 public void addActor(Actor a); 21 public Player getPlayer(); 22 public void gameOver(); 23 } 24

In the main class, we must construct the sound cache, implement the getSoundCache() just created, and also load and start the ambient music clip:


           . . .  
30    public class Invaders extends Canvas implements Stage, KeyListener {
31      
32      private BufferStrategy strategy;
33      private long usedTime;
34      
35      private SpriteCache spriteCache;
36 private SoundCache soundCache;
37 private ArrayList actors; 38 private Player player; 39 private BufferedImage ocean; 40 private int t; 41 42 private boolean gameEnded=false; 43 44 public Invaders() { 45 spriteCache = new SpriteCache();
46 soundCache = new SoundCache();
47 48 JFrame ventana = new JFrame("Invaders"); 49 JPanel panel = (JPanel)ventana.getContentPane(); 50 setBounds(0,0,Stage.WIDTH,Stage.HEIGHT); 51 panel.setPreferredSize(new Dimension(Stage.WIDTH,Stage.HEIGHT)); 52 panel.setLayout(null); 53 panel.add(this); 54 ventana.setBounds(0,0,Stage.WIDTH,Stage.HEIGHT); 55 ventana.setVisible(true); 56 ventana.addWindowListener( new WindowAdapter() { 57 public void windowClosing(WindowEvent e) { 58 System.exit(0); 59 } 60 }); 61 ventana.setResizable(false); . . . 72 public void initWorld() { 73 actors = new ArrayList(); 74 for (int i = 0; i < 10; i++){ 75 Monster m = new Monster(this); 76 m.setX( (int)(Math.random()*Stage.WIDTH) ); 77 m.setY( i*20 ); 78 m.setVx( (int)(Math.random()*20-10) ); 79 80 actors.add(m); 81 } 82 83 player = new Player(this); 84 player.setX(Stage.WIDTH/2); 85 player.setY(Stage.PLAY_HEIGHT - 2*player.getHeight()); 86
87 soundCache.loopSound("musica.wav");
88 } 89 . . . 203
204 public SoundCache getSoundCache() { 205 return soundCache; 206 }
207 208 public void keyPressed(KeyEvent e) { . . .

Also, we must modify the Player and Monster classes so that sounds are played at the appropriate moments (when firing, when an alien is killed, etc). Actually, the changes are trivial and consist of inserting a call to the playSound() method of the sound cache whenever necessary:


           . . .  
82      
83      public void fire() {
84        Bullet b = new Bullet(stage);
85        b.setX(x);
86        b.setY(y - b.getHeight());
87        stage.addActor(b);
88 stage.getSoundCache().playSound("missile.wav");
89 } 90 . . .


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 version27;
10    
11    public class Monster extends Actor {
12      protected int vx;
13      protected static final double FIRING_FREQUENCY = 0.01;
14      
15      public Monster(Stage stage) {
16        super(stage);
17        setSpriteNames( new String[] {"bicho0.gif","bicho1.gif"});
18        setFrameSpeed(35);
19      }
20      
21      public void act() {
22        super.act();
23        x+=vx;
24        if (x < 0 || x > Stage.WIDTH)
25          vx = -vx;
26        if (Math.random()<FIRING_FREQUENCY)
27          fire();
28      }
29    
30      public int getVx() { return vx; }
31      public void setVx(int i) {vx = i; }
32      
33      public void collision(Actor a) {
34        if (a instanceof Bullet || a instanceof Bomb) {
35          remove();
36 stage.getSoundCache().playSound("explosion.wav");
37 spawn(); 38 stage.getPlayer().addScore(20); 39 } 40 } 41 42 public void spawn() { 43 Monster m = new Monster(stage); 44 m.setX( (int)(Math.random()*Stage.WIDTH) ); 45 m.setY( (int)(Math.random()*Stage.PLAY_HEIGHT/2) ); 46 m.setVx( (int)(Math.random()*20-10) ); 47 stage.addActor(m); 48 } 49 50 public void fire() { 51 Laser m = new Laser(stage); 52 m.setX(x+getWidth()/2); 53 m.setY(y + getHeight()); 54 stage.addActor(m);
55 stage.getSoundCache().playSound("photon.wav");
56 57 } 58 } 59


Do you want to be notified when new tutorials or lessons are published? Press here


Full list of Java source files for this step

Actor.java Bomb.java Bullet.java Invaders.java
Laser.java Monster.java Player.java ResourceCache.java
SoundCache.java SpriteCache.java Stage.java  

Full list of resources

bicho.gif bicho0.gif bicho1.gif bicho2.gif
bombD.gif bombDL.gif bombDR.gif bombL.gif
bombR.gif bombU.gif bombUL.gif bombUR.gif
disparo.gif disparo0.gif disparo1.gif disparo2.gif
explosion.wav misil.gif missile.wav musica.wav
nave.gif oceano.gif photon.wav test.gif
Thumbs.db      

  Previous Page - Monster respawning Current Page - 27 - Simple sound   Next Page - Fixing the sound
  Return to the index of free Java tutorials  

(c) 2004 Planetalia S.L. All rights reserved. Unauthorized reproduction and/or mirroring is not permitted