Añadir un sonido sencillo al juego es relativamente fácil, sin embargo puede tener un impacto radical sobre el aspecto
final del juego. En realidad, muchos de los juegos que se producen en la actualidad invierten mucho más dinero en
gráficos y sonido que en programación y diseño, con la consecuencia de que a veces se obtienen juegos con unos excelentes
efectos visuales y bandas sonoras pero con escasa jugabilidad.
Utilizaremos diferentes archivos .wav para las distintas situaciones - disparo, impacto contra un monstruo, etc, -
así como un .wav que utilizaremos como "música de ambiente":
Los sonidos son un recurso más para nuestro programa de Java, de forma que lo que necesitamos es una clase
que gestione un "caché de sonidos" de forma muy similar al caché de sprites que ya tenemos. Dado que ambas comparten
gran parte de la funcionalidad, podemos intentar reorganizar el código extrayendo una clase ancestro común, a la que
llamaremos ResourceCache. Su código será el siguiente:
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
Con este ancestro, reescribimos la clase SpriteCache de la siguiente manera:
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
Y finalmente añadimos una nueva clase SoundCache que será la encargada de localizar
y cargar los archivos de sonido. Su código es muy similar a SpriteCache, pero
devuelve AudioClips en lugar de BufferedImages. También proporciona
dos métodos adicionales que son playSound() - para reproducir un sonido una única vez y
loopSound() para reproducir un sonido cíclicamente
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
Puesto que cualquier clase puede solicitar en un momento dado que se toque un sonido, cualquier clase debe ser
capaz de obtener una referencia al caché de sonidos. Como siempre, este tipo de métodos globales los añadimos a
la interfaz Stage
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();
20 public void addActor(Actor a);
21 public Player getPlayer();
22 public void gameOver();
23 }
24
En la clase principal, debemos construir el caché de sonidos, implementar el método getSoundCache() y
hacer que se inicie la música ambiente nada más comenzar el juego:
Invaders.java
. . .
30 public class Invaders extends Canvas implements Stage, KeyListener {
31
32 private BufferStrategy strategy;
33 private long usedTime;
34
35 private SpriteCache spriteCache;
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) {
. . .
Por último, debemos modificar las clases Player y Monster
de forma que en las situaciones apropiadas (disparo, impacto, etc.) se toque el sonido correspondiente. Realmente
en esta ocasión los cambios son triviales ya que lo único que necesitamos es insertar una llamada al método
playSound() con el sonido que necesitemos:
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);