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:
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:
ResourceCache.java
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:
SpriteCache.java
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()
SoundCache.java
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:
Stage.java
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();
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:
Player.java
. . .
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);