Curso - Programación de un Space Invaders en Java

  Página Anterior - Reaparición de monstruos Página Actual - 27 - Sonido sencillo   Página Siguiente - Corrigiendo los fallos de sonido
  Índice del curso  

Sonido sencillo

(c) Alexander Hristov

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":

Archivo UsoOrigen
musica.wav Música ambienteRealmente no recuerdo; descargada de algún sitio
photon.wav Alien disparando aquí
explosion.wav Alien destruido aquí
missile.wav Juegador disparando aquí

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:


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:


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


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


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

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:


           . . .  
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) { . . .

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:


           . . .  
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


Lista de archivos Java del programa en este paso

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

Lista de recursos

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 Thumbs.db

  Página Anterior - Reaparición de monstruos Página Actual - 27 - Sonido sencillo   Página Siguiente - Corrigiendo los fallos de sonido
  Índice del curso  

(c) 2004 Planetalia S.L. Todos los derechos reservados. Prohibida su reproducción