sábado, 18 de agosto de 2012

Música y efectos de sonido en AndEngine.


Primeros pasos en la creación de Vídeos Juego



Hola a todos, este es un nuevo post, donde voy a hacer una muy breve reseña, sobre un tema extremadamente importante, pero que a veces a nosotros los programadores se nos pasa un poco por alto. Estos son la música y los efectos de sonido en un juego, que aunque parezca mentira, para que uno se concentre,  preste una real atención a un juego y ademas no aburra, son un factor fundamental. Quien no recuerda la musica y efecto de sonidos de juegos como PacMan, Mario Bros. y demás juegos legendarios? Por este motivo, ahora vamos a ver un poco como manejar y agregarlos a nuestro juego con las herramientas que nos brinda AndEngine.
Pues entonces comencemos :D

Cual es la diferencia?


Imagino que ya saben cual es la diferencia, pero vamos a aclararla por las dudas, en pocas palabras, la música es ese sonido que tenemos de fondo en el juego, en cambio los efectos de sonido son aquellos que se reproducen al realizar una acción determinada (por ejemplo un golpe ) y normalmente no duran mas de 3 segundos.

Algunas cosas a tener en cuenta es que lo ideal, sería que la música acompañe un poco la historia del juego, música un poco más tensa en momentos de acción, más alegre en partes donde no hay peligro y demás. Y otra cosa a tener en cuenta, es que no debe molestar, no debe provocar que la gente deje el juego, así que en mi humilde opinión, es importante que en las opciones, estè la de deshabilitar la música de fondo :D (igual con los efectos de sonido, a mi gusto personal, debe separarse, la música de los efectos de sonido). En cuanto a los efectos de sonido, creo que dependerá bastante del estilo del juego, en que tipo de efectos deseamos, por ejemplo podemos pretender que sean más reales, o graciosos. ;)

De donde obtengo los efectos de sonido?

Este es un tema importante a tener en cuenta, y se preguntaran el porque lo digo. Pues es sencillo, no podemos poner cualquier sonido que encontremos por ahí, porque por un tema de licencias podríamos tener problemas legales :P
Así que tenemos algunas opciones, una es buscar por internet con cuidado de que tenga una licencia que no nos restrinjan, o que por ejemplo debemos agregar la fuente de donde lo obtuvimos en los créditos de nuestro juego. Otra opcion seria crear nuestros sonidos ( aunque yo allá estudiado música, podría crearlos con Cubase, a veces por cuestiones de tiempo, o de creatividad no podría hacerlo.  :P ), si uno no tiene esta habilidad, no estaría mal, invitar a participar a algún conocido o amigo musico, creo que esto le daria un plus bastante importante a nuestro juego. De todas maneras como dije antes, hay bastantes recursos por internet de donde descargarlos.
Estos son un par de sitios que nos recomienda el libro de Rick Rogers:


- http://www.mymusicsource.com
- http://www.5alarmmusic.com/
- http://www.partnersinrhyme.com/


Algunas cosas que vamos a tener, que tener en cuenta ;)

Bueno una de las principales cosas que debemos tener en cuenta, es que tipo de archivos de sonido soporta Android, los agregaría aquí, pero para que la lista no esté desactualizada, les dejo el enlace oficial de los formatos multimedia que soporta Android, por si alguno más, es soportado en próximas actualizaciones :D

- http://developer.android.com/guide/appendix/media-formats.html

También hay que mirar, el peso de estos archivos, no tenemos que olvidarnos que esta programando sobre teléfonos móviles, y aunque ya existen versiones muy potentes. No queremos consumir toda la memoria, ni demasiados ciclos de procesamiento sólo en el sonido.  Asi que ahi que poner bien en la balanza, cuál es la calidad de sonido, en comparación del costo en reproducirlo y la memoria que deberemos utilizar.

Vamos a lo bueno, como manejamos esto en AndEngine.

En AndEngine podemos diferenciar las interfaces music, para la música que vamos a manejar y sounds, para los efectos de sonido que vamos a utilizar. Muchos podemos elegir utilizar por una cuestión de sencillez, un tema musical por activity, pero eso no lo vamos a poder hacer con los sonidos del juegos, ya que los efectos van a ser varios , así que para ello vamos a tener que utilizar SoundPool para poder gestionar esos clips de sonido. Esta es la docu oficial:
-http://developer.android.com/reference/android/media/SoundPool.html








Algunas Clases que nos ayudaran:

Music: Esta representa un stream de música.
Sound: Lo mismo pero con los efectos de sonido.
MusicFactory y SoundFactory: Estas clases nos ayudaran a manejar los sonidos con un patrón de tipo Singleton.

Nota: Si has manejados sonidos en Android, pues el manejo en sí, va a serte muy familiar ;)

La clase Music:


Normalmente no la vamos a utilizar instanciando directamente, para esto nosotros vamos a utilizar la clase MusicFactory para crear nuestros objetos de tipo Music, una vez que lo hayamos creado, estos objetos nos proveerán algunos metodos para poder manejarlos:
void play() ,  void stop() , void pause() , void resume() , void release() , void setLooping(final boolean pLooping) , void setVolume(final float pLeftVolume, final float pRightVolume) , void seekTo(final int pMilliseconds) , boolean isPlaying() , por sus nombre no creo que necesitan demasiada explicación :D

Y por ultimo, con este método vamos a poder registrar un Listener para que nos avise cuando termino el tema:  
void setOnCompletionListener(final OnCompletionListener pOnCompletionListener)

La clase Sound:


Esta clase, de la misma manera que hablamos de Music, normalmente no la instanciamos, sino que utilizaremos SoundFactory para ello. Algunas pequeñas diferencias es que no tenemos los metodos: seekTo(), isPlaying(), ni  setOnCompletionListener().

Pero adicionalmente tenemos algunos otros metodos:
void setLoopCount(final int pLoopCount) : Este método trabaja junto a setLooping(). Si este esta seteado en true, el efecto se repetirá hasta que llamemos al método stop(). En cambio si este esta seteado en false y setLoopCount() no esta en cero, el efecto se repetirá el número de veces que nosotros hayamos seteado con este método. (cuidado con que  esta basado en cero,así que si los ponernos en 4, el sonido se repetirá 5 veces).
Otro método es void setRate(final float pRate), con el podremos cambiar la velocidad de reproducción del sonido, el rango va de 0.5f o 2.0f.

La clase MusicFactory:
Como otras clases factory, en AndEngine, esta provee varios metodos createfrom....  para poder cargarlos de diferentes lugares a nuestros recursos:

Music createMusicFromFile(final MusicManager pMusicManager, final Context pContext, final File pFile)
Music createMusicFromAsset(final MusicManager pMusicManager, final Context pContext, final String pAssetPath)
Music createMusicFromResource(final MusicManager pMusicManager, final Context pContext, final int pMusicResID)

La clase SoundFactory:

De las misma manera que MusicFactory, podemos cargar los recursos de diferentes lugares con estos metodos:
Sound createSoundFromPath(final SoundManager pSoundManager, final Context pContext, final String pPath)
Sound createSoundFromAsset(final SoundManager pSoundManager, final Context pContext, final String pAssetPath)
Sound createSoundFromResource(final SoundManager pSoundManager, final Context pContext, final int pSoundResID)
Sound createSoundFromFileDescriptor(final SoundManager pSoundManager, final FileDescriptor pFileDescriptor, final long pOffset, final long pLength)

En ambos casos, SoundFactory y MusicFactory, tenemos el método setAssetBasePath(), por si necesitamos cargar recursos desde otro lado ;)

Y el código?

Estos son algunos de los ejemplos oficiales si asi se podria decir ;)

http://code.google.com/p/andengineexamples/source/browse/src/org/anddev/andengine/examples/SoundExample.java
http://code.google.com/p/andengineexamples/source/browse/src/org/anddev/andengine/examples/MusicExample.java


Creo que no vale la pena realizar un copy paste :D y estos son muy claros y sencillos de entender.

Fuentes de referencia:

http://www.andengine.org/blog/

http://www.andengine.org/forums/

https://github.com/nicolasgramlich/AndEngine


Este recurso es uno de los mas importantes:
http://code.google.com/p/andengineexamples/

Y una muy buena lectura el libro Learnin Android Game Programing:

http://www.amazon.com/Learning-Android-Game-Programming-Hands-On/dp/0321769627



Esto es todo por ahora, como podran ver, no es un tema tan complicado, como por ejemplo el anterior, pero sí muy importante si queremos que nuestro juego sea realmente agradable. Código no es tan complicado, y si han manejado este tipo de recursos en Android les resultara como comente anteriormente, muy familiar. Espero que les alla gustado el post, y también que tengan algún amigo músico que los ayude a crear sus sonidos jejeje



Saludos a todos, Gabriel E. Pozo

lunes, 6 de agosto de 2012

Sistema de partículas en AndEngine parte 2




Primeros pasos en la creación de Vídeos Juego


Hola a todos nuevamente, espero que se encuentren bien, en este post vamos a terminar de hablar de los sistemas de partículas en AndEngine, por lo menos por ahora, este post también va a seguir siendo un tanto teórico, pero es importante para que comprendamos los fuentes que andan dando vuelta por ahi. De esta manera poder usarlos, y modificarlos a nuestro gusto. O poder darnos ideas, para poder armar los efectos que nosotros deseemos.

Creando nuestro primer sistema de partículas.

En AndEngine para crear un ParticleSystem desde cero, requiere la utilización de las herramientas mencionadas en el anterior post ( usando iniciadores y modificadores). Esto puede ser un poco complicado. El crear una buena combinación de texturas, modificadores e iniciadores, puede no resultarnos obvia de ninguna manera. Además puede ser un poco complicado usar el mismo ParticleSystem en todos nuestros proyecto. Hay que copiar y pegar código y al más mínimo cambio, para realizar una prueba, hay que recompilar, volver a instalar, etc, etc.  Una solución que nos propone Rick Rogers en su libro Learning Android Game Programming, fue la introducción de archivos XML, basados en los efectos para la partículas. ( Depende de la versión que uses, podes llegar a necesitar un plugin para usarlos) Estos archivos, tienen la extensión .px  eh incluyen todo el código necesario. Así que vamos a poder elegir que método usar para resolver nuestro diseño.

Formas para crear nuestro sistema de partículas.

Estos serian los pasos para crearlo de la manera tradicional:

  1. Debemos crear un TextureRegion, donde vamos a contener la textura que vamos a usar en nuestro sistema, y un TextureManager para cargarla. (esto es lo mismo como para crear cualquier otro TextureRegion)
  2. Debemos crear un ParticleEmitter nuevo, usando el constructor que necesitemos para nuestro emisor.
  3. Crear un ParticleSystem, pasandole el ParticleEmitter, la textura, la velocidad máxima y mínima, y el número máximo de partículas.
  4. De manera opcional podemos establecer algunas funciones de OpenGL.
  5. Añadir los inicializadores.
  6. Añadir los modificadores.
  7. Y ahora si, agregar nuestro ParticleSystem a Scene o escena actual.

Ahora vamos a ver con XML, que en esencia es lo mismo, simplemente que algunos de estos pasos ya están en nuestro archivo .px, lo que nos va a resultar de manera parecida a cuando vimos los TileMap cargando los archivos TMX.
  1. Tenemos que crear un objeto del tipo PXLoader.
  2. Ahora usamos este objeto para crear el ParticleSystem desde el archivo .px, que va a describir a nuestro sistema.
  3. Opcionalmente, como de la manera tradicional, podemos establecer algunas funciones en OpenGL.
  4. Y como antes, agregar nuestro ParticleSystem a Scene o escena actual

Como pueden ver, el XML nos ahorra unos cuantos pasos,esto tiene la ventaja, de que al tener un efecto armado que queremos utilizar en otros proyectos, pues nos ahorraremos estos pasos. Además de ser un archivo que se puede leer de manera muy sencilla, sin dificultades para comprenderlo.Tambien esta bueno que tenemos, muchos ejemplos ya armados, así que podemos utilizarlos, modificando los pequeños detalles que nosotros deseemos. Este es un ejemplo:


<ParticleConfig>
  <emitter
     shape="circle"
     center_x="0.0"
     center_y="0.0"
     radius_x="40.0"
     radius_y="40.0">
  </emitter>
  <system
     texture="particle_fire.png"
     min_rate="100"
max_rate="100"
     max_particles="500">
     <init_color
        min_red="1"
        max_red="1"
        min_green="0"
        max_green="0"
        min_blue="0"
        max_blue="0">
     </init_color>
     <init_alpha
        min_alpha="0"
        max_alpha="0">
     </init_alpha>
     <init_velocity
        min_velocity_x="-2"
        max_velocity_x="2"
        min_velocity_y="-2"
        max_velocity_y="-2">
     </init_velocity>
     <init_rotation
        min_rotation="0.0"
        max_rotation="360.0">
     </init_rotation>
     <mod_scale
        from_scale_x="1.0"
        to_scale_x="2.0"
        from_scale_y="1.0"
        to_scale_y="2.0"
        from_time="0"
        to_time="5">
     </mod_scale>
     <mod_color
        from_red="1"
        to_red="1"
        from_green="0"
        to_green="0.5"
        from_blue="0"
        to_blue="0"
        from_time="0"
        to_time="2">
     </mod_color>
     <mod_color
        from_red="1"
        to_red="1"
        from_green="0.5"

to_green="1"
        from_blue="0"
        to_blue="1"
        from_time="2"
        to_time="4">
     </mod_color>
     <mod_alpha
        from_alpha="0"
        to_alpha="1"
        from_time="0"
        to_time="1">
     </mod_alpha>
     <mod_alpha
        from_alpha="1.0"
        to_alpha="0"
        from_time="3"
        to_time="4">
     </mod_alpha>
     <mod_expire
        min_lifetime="2"
        max_lifetime="4">
     </mod_expire>
  </system>
</ParticleConfig>





Por ejemplo para cargar nuestro archivo .px, deberíamos usar una string similar a esta en nuestro onLoadScene().

String ArchivoPX = "gfx/particles/ejemplo.px";
Debemos meter las imágenes y archivo PX dentro de la carpeta assets y decirle a nuestro ParticlePlayer como encontrarlos. Si llega a haber algo mal en nuestro .px, el LogCat nos dirá que linea del mismo nos esta dando problemas ;)




Ahora como dijimos antes, deberemos usar la clase PXLoader para poder cargar nuestro archivo, que va a describir a nuestro sistema de partículas. Esto es similar a usar la clase TMXLoader:

PXLoader(final Context pContext, final TextureManager pTextureManager)
PXLoader(final Context pContext, final TextureManager pTextureManager,
final TextureOptions pTextureOptions)

Si no llegamos a incluir ninguna TextureOption, utilizaremos TextureOpcion.DEFAULT
Esta clase tiene dos metodos que nos van a servir para cargar algunas definiciones de nuestro sistema:

ParticleSystem createFromAsset(final Context pContext, final String pAssetPath)
ParticleSystem load(final InputStream pInputStream)


El primer metodo nos va a servir para cargar las definiciones desde una sub carpeta de assets. Y el segundo para cargarlas desde algún otro tipo de InputStream, los dos metodos nos devuelven un ParticleSystem completo. Si llega a ocurrir un error, esto nos puede devolver uno de estos dos tipos de excepciones : PXLoadException o un XParseException.

Ahora si, un ejemplo de uso de la manera tradicional.( lo saque del libro :D )


package com.pearson.lagp.v3;
. . .
public class Level1Activity extends BaseGameActivity {
. . .
  private TextureRegion mParticleTextureRegion;
  private ParticleSystem particleSystem;
  private CircleParticleEmitter particleEmitter;
. . .
  @Override
  public void onLoadResources() {
     /* Load Textures. */
. . .
     TextureRegionFactory.setAssetBasePath("gfx/particles/");
     mParticleTexture = new Texture(32, 32,
        TextureOptions.BILINEAR_PREMULTIPLYALPHA);
     mParticleTextureRegion =
        TextureRegionFactory.createFromAsset(
           this.mParticleTexture, this,
           "particle_fire.png",
        0, 0);
     mEngine.getTextureManager().loadTexture(
        this.mParticleTexture);
  }
  @Override
  public Scene onLoadScene() {
. . .
     particleEmitter = new CircleParticleEmitter(
       CAMERA_WIDTH * 0.5f, CAMERA_HEIGHT * 0.5f + 20, 40);
     particleSystem = new ParticleSystem(particleEmitter,
        100, 100, 500, this.mParticleTextureRegion);
     particleSystem.addParticleInitializer(
        new ColorInitializer(1, 0, 0));
     particleSystem.addParticleInitializer(
        new AlphaInitializer(0));
     particleSystem.setBlendFunction(GL10.GL_SRC_ALPHA,
        GL10.GL_ONE);
     particleSystem.addParticleInitializer(
        new VelocityInitializer(-2, 2, -2, -2));
     particleSystem.addParticleInitializer(
        new RotationInitializer(0.0f, 360.0f));
     particleSystem.addParticleModifier(
        new org.anddev.andengine.entity.particle.modifier-            .
ScaleModifier(1.0f, 2.0f, 0, 5));

particleSystem.addParticleModifier(
        new org.anddev.andengine.entity.particle.modifier-
           .ColorModifier(1, 1, 0, 0.5f, 0, 0, 0, 3));
     particleSystem.addParticleModifier(
        new org.anddev.andengine.entity.particle.modifier-
           .ColorModifier(1, 1, 0.5f, 1, 0, 1, 2, 4));
     particleSystem.addParticleModifier(
        new org.anddev.andengine.entity.particle.modifier-            .
AlphaModifier(0, 1, 0, 1));
     particleSystem.addParticleModifier(
        new org.anddev.andengine.entity.particle.modifier-
           .AlphaModifier(1, 0, 3, 4));
     particleSystem.addParticleModifier(
        new ExpireModifier(2, 4));
     particleSystem.setParticlesSpawnEnabled(false);
     scene.getLastChild().attachChild(particleSystem);
     return scene;
  }
  @Override
  public void onLoadComplete() {
  }
   private Runnable mStartVamp = new Runnable() {
       public void run() {
          int i = nVamp++;
          Scene scene = Level1Activity.this.mEngine.getScene();
             float startY = gen.nextFloat()*(CAMERA_HEIGHT - 50.0f);
             asprVamp[i] = new AnimatedSprite(CAMERA_WIDTH - 30.0f,
        startY, mScrumTextureRegion.clone()) {
                @Override
                public boolean onAreaTouched(
           final TouchEvent pAreaTouchEvent,
           final float pTouchAreaLocalX,
           final float pTouchAreaLocalY) {
                   switch(pAreaTouchEvent.getAction()) {
                   case TouchEvent.ACTION_DOWN:
                      /* Is there a vampire close by? */
                      for (int j=0; j<nVamp; j++){
                         if (
(Math.abs(asprVamp[j].getX() + (asprVamp[j].getWidth()/2) -
pAreaTouchEvent.getX()) < 10.0f) &&
                               (Math.abs(asprVamp[j].getY() +
(asprVamp[j].getHeight()/2) - pAreaTouchEvent.getY()) < 10.0f)) {
                   particleEmitter.setCenter(
              pAreaTouchEvent.getX(),
pAreaTouchEvent.getY());
                   particleSystem.setParticlesSpawnEnabled(
              true);
                       mHandler.postDelayed(mEndPESpawn,3000);
                       asprVamp[j].clearEntityModifiers();
asprVamp[j].registerEntityModifier(
                         new AlphaModifier(1.0f, 1.0f, 0.0f));
                              asprVamp[j].setPosition(
                 CAMERA_WIDTH,
                 gen.nextFloat()*
                 CAMERA_HEIGHT);
                         }
                      }
                      break;
                 }
                   return true;
         }
             };
         scene.registerTouchArea(asprVamp[i]);
. . .
    private Runnable mEndPESpawn = new Runnable() {
        public void run() {
          particleSystem.setParticlesSpawnEnabled(false);
        }
    };
}


Ahora vamos a ver algunos de los metodos como funcionan, recuerden que esta es la manera tradicional.

  • En el onLoadResourses(): aca vamos a cargar las texturas, donde previamente deberemos haber cargado las imágenes en la carpeta assets/gfx/particles.
  • onLoadScene() : creamos un CircleParticleEmitter con el que se va a crear el ParticleSystem, donde se va a asignar un número de partículas y refresco.Se añaden los modificadores e inicializadores, se setea  setSpawnEnabled(false) y tambien se ve el tema de la deteccion de colisiones, como el area de deteccion del toque, que veremos en otros post.
  • Con Runnable mStartVamp, y Runnable mEndPESpawn se enciende y apagan el efecto de la generación de las partículas.

Ahora este código representa el mismo ejemplo pero con XML.

package com.pearson.lagp.v3;
. . .
public class Level1Activity extends BaseGameActivity {
. . .
  private ParticleSystem particleSystem;
  private BaseParticleEmitter particleEmitter;
. . .
  @Override
  public Scene onLoadScene() {
. . .
     try {
        final PXLoader pxLoader = new PXLoader(this,
           this.mEngine.getTextureManager(),
           TextureOptions.BILINEAR_PREMULTIPLYALPHA);
        particleSystem = pxLoader.createFromAsset(this,
           "gfx/particles/explo.px");
     } catch (final PXLoadException pxle) {

     Debug.e(pxle);
     }
     particleSystem.setBlendFunction(GL10.GL_SRC_ALPHA,
        GL10.GL_ONE);
     particleSystem.setParticlesSpawnEnabled(false);
     particleEmitter =
      (BaseParticleEmitter) particleSystem.getParticleEmitter();
     scene.getLastChild().attachChild(particleSystem);


Esta es la parte del código que nos interesa, donde están las diferencias con el XML, ahora vamos a explicarlas:

Acá no vamos a necesitar declarar una Texture o un TextureRegionni cargarlos en onLoadResources(), ya que esto lo realiza nuestro PXLoader.Lo unico que necesitamos es importar la imagen donde diga el archivo .px. Declaramos nuestro ParticleEmitter como BaseParticleEmitter porque no sabemos que tipo de emisor es todavía. En el onLoadScene() debemos crear el PXLoader y usaremos el metodo createFromAsset(). El método para crear el ParticleSystem se encuentra dentro del archivo .px.
Previamente hemos importado los archivos en assets/gfx/particles. Recuperamos el ParticleEmitter, de nuestro ParticleSystem, con el método getParticleEmitter, para que de esta forma posteriormente podamos utilizarlo, por ejemplo para re-establecer su posición.
Lo demás es lo mismo, que del anterior modo, o sea que para el resto del manejo lo podemos hacer como se hace de la manera tradicional.


Pequeño video de ejemplo, de fuego creado con un ParticleEmitter creado en AndEngine:




Fuentes de referencia:

http://www.andengine.org/blog/

http://www.andengine.org/forums/

https://github.com/nicolasgramlich/AndEngine

Este recurso es uno de los mas importantes:
http://code.google.com/p/andengineexamples/

Y una muy buena lectura el libro Learnin Android Game Programing:

http://www.amazon.com/Learning-Android-Game-Programming-Hands-On/dp/0321769627
Ejemplo completo de un ParticleSystem:

http://code.google.com/p/andengineexamples/source/browse/src/org/anddev/andengine/examples/ParticleSystemNexusExample.java


Bueno este es el fin de este post, y por ahora del tema de las partículas, en el próximo post vamos a seguir hablando de AndEngine, pero vamos a ver algún otro tema. Ya que esta les queria comentar, que todos los post van a ser teóricos, para que podamos comprender el código de manera correcta. Así al final, vamos a poder analizar el código completo de un juego armado con este framework. Espero que les alla gustado, nos vemos en el próximo post :D


Saludos a todos, Gabriel E. Pozo