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