domingo, 12 de junio de 2011

Como compilar tu propio andengine.jar

Consiguiendo los fuentes


Primero necesitamos los fuentes de andengine. Usaremos un Cliente de Mercurial.


En mi caso (debian) "apt-get install mercurial", en ubuntu deberia funcionar también. Si estás usando windows, de los clientes de arriba usa el que más te guste, pero no los he probado y ahí no te puedo ayudar. Creamos un directorio en nuestro pc, y vamos con un terminal a ese directorio.


hg clone https://andengine.googlecode.com/hg/ andengine

Nos tiene que dar una salida como ésta:
requesting all changes
adding changesets
adding manifests
adding file changes
added 818 changesets with 3243 changes to 703 files (+4 heads)
updating to branch default
410 files updated, 0 files merged, 0 files removed, 0 files unresolved


Enhorabuena, nuestro primer paso para tener nuestro propio andengine.jar esta completo.


Importando los fuentes al android


En Eclipse, vamos a File -> Import



Seleccionamos "Existing Projects into Workspace" y le damos a Siguiente. Ahora en "Select root directory" vamos al directorio que creamos anteriormente para bajar los fuentes de andengine. En mi caso, esto pinta como ésto:



Si has seguido los pasos bien, en Projects debes ver AndEngine chequeado. Dale a Finish.



Ahora tenemos el proyecto metido en eclipse, pero tiene algún error.


Arreglando los errores


Una vez importado el proyecto en Eclipse, necesitamos crear un directorio "res", hacemos click derecho en el raiz del proyecto AndEngine, y le damos a New -> Folder.




Una vez creados el directorio res, ya no ha errores en el proyecto.


Creando el archivo .jar


Botón derecho en el raiz del proyecto AndEngine y seleccionamos export.



Ahora seleccionamos Java -> Jar y hacemos click en Siguiente.



En la siguiente pantalla necesitamos hacer algo importante, necesitamos quitar del andengine.jar lo que no necesitamos. En la parte de la derecha, deschequear TODO lo de andengine, porque no es necesario. También he quitado la carpeta donde hay imagenes y uml...
Ahora le damos una salida al archivo jar y hacemos click en finish.



Espero que os haya servido de ayuda. Un saludo.

viernes, 8 de abril de 2011

Estado actual del proyecto

Perdon por la calidad del video. Espero en los proximos dias subir a subversion el codigo del proyecto y hacer unos 3 articulo para llegar de lo que tenemos ahora a lo que se ve en el video. Usando Box2d para las físicas. Espero que os guste.




sábado, 26 de marzo de 2011

Introducción a AndEngine (Parte V)

Anteriormente en Droideando



Objetivos de hoy

  • Terminar el CatapultDetector para hacerlo funcionar bien
  • Hacer que el jugador reaccione al Detector


Con el Detector


Ya tengo terminado el detector, pongo aqui el código completo y luego vamos a verlo por partes


CatapultDetector.java
  1. package com.pruebas.andengine;
  2.  
  3. import org.anddev.andengine.input.touch.TouchEvent;
  4. import org.anddev.andengine.input.touch.detector.BaseDetector;
  5.  
  6. import android.view.MotionEvent;
  7.  
  8. public class CatapultDetector extends BaseDetector {
  9. // ===========================================================
  10. // Constants
  11. // ===========================================================
  12. private static final float ANGLE_CONSTANT = 90;
  13. private static final float DEFAULT_MAX_DISTANCE = 80;
  14. // ===========================================================
  15. // Fields
  16. // ===========================================================
  17.  
  18. // Listener for the Detector
  19. private final ICatapultDetectorListener mCatapultDetectorListener;
  20. private float mMaxDistance;
  21.  
  22. // First Touch
  23. private float mFirstX;
  24. private float mFirstY;
  25.  
  26. // ===========================================================
  27. // Constructors
  28. // ===========================================================
  29.  
  30. public CatapultDetector(
  31. final ICatapultDetectorListener pCatapultDetectorListener) {
  32. this(DEFAULT_MAX_DISTANCE, pCatapultDetectorListener);
  33. }
  34.  
  35. public CatapultDetector(final float pMaxDistance,
  36. final ICatapultDetectorListener pCatapultDetectorListener) {
  37. this.setMaxDistance(pMaxDistance);
  38. this.mCatapultDetectorListener = pCatapultDetectorListener;
  39. }
  40.  
  41. // ===========================================================
  42. // Methods for/from SuperClass/Interfaces
  43. // ===========================================================
  44.  
  45. @Override
  46. protected boolean onManagedTouchEvent(TouchEvent pSceneTouchEvent) {
  47. final float touchX = this.getX(pSceneTouchEvent);
  48. final float touchY = this.getY(pSceneTouchEvent);
  49. final int action = pSceneTouchEvent.getAction();
  50.  
  51. switch (action) {
  52. case MotionEvent.ACTION_DOWN:
  53. this.mFirstX = touchX;
  54. this.mFirstY = touchY;
  55. return true;
  56. case MotionEvent.ACTION_MOVE:
  57. case MotionEvent.ACTION_UP:
  58. // case MotionEvent.ACTION_CANCEL:
  59. final float distanceX = Math.abs(touchX - this.mFirstX);
  60. final float distanceY = Math.abs(touchY - this.mFirstY);
  61. final float distance = Math.min((float) Math.hypot(
  62. (double) distanceX, (double) distanceY), mMaxDistance);
  63. final double angleX = touchX - this.mFirstX;
  64. final double angleY = touchY - this.mFirstY;
  65. final float angle = (float) Math.toDegrees(Math.atan2(angleY,
  66. angleX))
  67. + ANGLE_CONSTANT;
  68. if (action == MotionEvent.ACTION_MOVE) {
  69. this.mCatapultDetectorListener.onCharge(this, pSceneTouchEvent,
  70. distance, angle);
  71. } else {
  72. this.mCatapultDetectorListener.onShoot(this, pSceneTouchEvent,
  73. distance, angle);
  74. }
  75. return true;
  76. default:
  77. return false;
  78. }
  79. }
  80.  
  81. // ===========================================================
  82. // Getter & Setter
  83. // ===========================================================
  84.  
  85. public void setMaxDistance(float mMaxDistance) {
  86. this.mMaxDistance = mMaxDistance;
  87. }
  88.  
  89. public float getMaxDistance() {
  90. return mMaxDistance;
  91. }
  92.  
  93. // ===========================================================
  94. // Methods
  95. // ===========================================================
  96.  
  97. protected float getX(final TouchEvent pTouchEvent) {
  98. return pTouchEvent.getX();
  99. }
  100.  
  101. protected float getY(final TouchEvent pTouchEvent) {
  102. return pTouchEvent.getY();
  103. }
  104.  
  105. // ===========================================================
  106. // Inner and Anonymous Classes
  107. // ===========================================================
  108.  
  109. public static interface ICatapultDetectorListener {
  110. // ===========================================================
  111. // Constants
  112. // ===========================================================
  113.  
  114. // ===========================================================
  115. // Methods
  116. // ===========================================================
  117.  
  118. public void onCharge(final CatapultDetector pCatapultDetector,
  119. final TouchEvent pTouchEvent, final float pDistance,
  120. final float pAngle);
  121.  
  122. public void onShoot(final CatapultDetector pCatapultDetector,
  123. final TouchEvent pTouchEvent, final float pDistance,
  124. final float pAngle);
  125. }
  126.  
  127. }
  128.  
Parsed in 0.130 seconds at 31.87 KB/s, using GeSHi 1.0.8.10


Vamos primero a profundizar un poco en el Listener que es lo primero que hay que hacer bajo mi punto de vista


Listener de la clase CatapultDetector
  1. public static interface ICatapultDetectorListener {
  2. // ===========================================================
  3. // Constants
  4. // ===========================================================
  5.  
  6. // ===========================================================
  7. // Methods
  8. // ===========================================================
  9.  
  10. public void onCharge(final CatapultDetector pCatapultDetector,
  11. final TouchEvent pTouchEvent, final float pDistance,
  12. final float pAngle);
  13.  
  14. public void onShoot(final CatapultDetector pCatapultDetector,
  15. final TouchEvent pTouchEvent, final float pDistance,
  16. final float pAngle);
  17. }
Parsed in 0.091 seconds at 7.05 KB/s, using GeSHi 1.0.8.10

Este Listener tiene que responder a dos eventos, cuando estamos cargando y cuando soltamos para realizar el disparo. Para el primero usamos un método onCharge(), que le pasa los siguientes parámetros:

  • pCatapultDetector: El propio detector.
  • pTouchEvent: El evento en pantalla que ha desencadenado la acción.
  • pDistance: la distancia desde el punto incial de pulsación hasta el punto actual.
  • pAngle: En ángulo formado desde el primer punto de pulsación al actual. Este parámetro se puede aplicar a un sprite y girará correctamente .
Los parámetros del el onShoot() son los mismos, solo que el evento implica disparo. Veamos ahora las variables del Detector.


Variables de la clase CatapultDetector
  1. // ===========================================================
  2. // Constants
  3. // ===========================================================
  4. private static final float ANGLE_CONSTANT = 90;
  5. private static final float DEFAULT_MAX_DISTANCE = 80;
  6. // ===========================================================
  7. // Fields
  8. // ===========================================================
  9.  
  10. // Listener for the Detector
  11. private final ICatapultDetectorListener mCatapultDetectorListener;
  12. private float mMaxDistance;
  13.  
  14. // First Touch
  15. private float mFirstX;
  16. private float mFirstY;
Parsed in 0.086 seconds at 6.68 KB/s, using GeSHi 1.0.8.10

Tenemos primero las constantes

  • ANGLE_CONSTANT: En las pruebas que realicé con el Detector, tenia que sumarle 90 al ángulo que calculaba para que el sprite rotara en la dirección que yo necesitaba.
  • DEFAULT_MAX_DISTANCE: Distancia máxima de carga por defecto, si no se le indica ninguna, la velocidad máxima de carga son 80 pixeles. Esto quiere decir que si alejo el dedo mas de 80 píxeles del punto inicial de toque, no carga a 81 ni a 90, el máximo es 80. Recordad que siempre son 80 pixeles relativos a la resolución deseada de trabajar, Si luego ejecutamos el programa en un terminal con una resolución mayor, AndEngine controlará ésto .


Un repaso a las variables

  • mCatapultDetectorListener: Es un ICatapultDetectorListener que hemos visto antes. Es obligado para un Detector tener un Listener que "escuche" los eventos que este Detector dispara. Se le indica en el constructor de la clase.
  • mMaxDistance: Variable donde guardamos la máxima distancia actual para este detector.
  • mFirstX,mFirstY: Aqui guardamos la posición de la primera pulsación para calcular la distancia el ángulo.


Veamos los dos constructures de la clase


Constructores de la clase CatapultDetector
  1. // ===========================================================
  2. // Constructors
  3. // ===========================================================
  4.  
  5. public CatapultDetector(
  6. final ICatapultDetectorListener pCatapultDetectorListener) {
  7. this(DEFAULT_MAX_DISTANCE, pCatapultDetectorListener);
  8. }
  9.  
  10. public CatapultDetector(final float pMaxDistance,
  11. final ICatapultDetectorListener pCatapultDetectorListener) {
  12. this.setMaxDistance(pMaxDistance);
  13. this.mCatapultDetectorListener = pCatapultDetectorListener;
  14. }
  15.  
Parsed in 0.086 seconds at 5.98 KB/s, using GeSHi 1.0.8.10

Tenemos dos constructores. Al primero se le manda solamente el ICatapultDetectorListener que va a escuchar, con este constructor se usa la máxima distancia por defecto. Éste llama al segundo con el parámetro de maxima distancia con la constante antes vista. El segundo es el más completo, que aparte del listener recibe la máxima distancia de carga.


La miga del Detector está en el método onManagedTouchEvent(). Recibe como parámetro únicamente el TouchEvent. Vamos por partes.


  1. final float touchX = this.getX(pSceneTouchEvent);
  2. final float touchY = this.getY(pSceneTouchEvent);
  3. final int action = pSceneTouchEvent.getAction();
Parsed in 0.081 seconds at 1.88 KB/s, using GeSHi 1.0.8.10

Aqui usamos variables finales para guardar datos que vamos a usar despues. En la primera línea guardamos la posición X pulsada, en la segunda guardamos la posición Y y en la tercera guardamos la acción realizada, en este caso vamos a controlar los siguientes:

  • ACTION_DOWN : Cuando se pulsa la pantalla.
  • ACTION_MOVE : Cuando ya tenemos la pantalla pulsada y lo que hacemos es mover el dedo por la pantalla.
  • ACTION_UP : Acción de levantar el dedo.


Cuando el evento es ACTION_DOWN, lo único que hacemos es guardar los puntos donde hemos pulsado para posteriormente calcular ángulos y distancia.


Para el ACTION_MOVE y ACTION_UP tenemos un mismo código que controla los dos.


ACTION_MOVE y ACTION_UP
  1. final float distanceX = Math.abs(touchX - this.mFirstX);
  2. final float distanceY = Math.abs(touchY - this.mFirstY);
  3. final float distance = Math.min((float) Math.hypot(
  4. (double) distanceX, (double) distanceY), mMaxDistance);
  5. final double angleX = touchX - this.mFirstX;
  6. final double angleY = touchY - this.mFirstY;
  7. final float angle = (float) Math.toDegrees(Math.atan2(angleY,
  8. angleX))
  9. + ANGLE_CONSTANT;
  10. if (action == MotionEvent.ACTION_MOVE) {
  11. this.mCatapultDetectorListener.onCharge(this, pSceneTouchEvent,
  12. distance, angle);
  13. } else {
  14. this.mCatapultDetectorListener.onShoot(this, pSceneTouchEvent,
  15. distance, angle);
  16. }
  17. return true;
Parsed in 0.078 seconds at 8.91 KB/s, using GeSHi 1.0.8.10

Primera y segunda linea, cogemos el número de píxeles de distancia desde el primero toque hasta el actual. En la tercera línea calculamos con la función hypot de Math la distancia real entre los dos puntos de toque, el inicial y el actual. En esa tercera línea realmente cogemos el mínimo de la distancia Real y la variable mMaxDistance, con lo cual si la distancia es mayor que la máxima nos devuelve nMaxDistance. Ya tenemos calculada la distancia, vamos a por el ángulo.


La mejor manera que encontré de hallar el ángulo entre los dos puntos es la de las siguientes lineas. hasta que guardamos en la variable angle el valor del ángulo.


Luego si la acción es ACTION_MOVE lanzamos un onCharge() y si es un ACTION_UP lanzamos un onShoot().


Con esto terminamos por completo el Detector, habrá maneras más bonitas y eficientes de hacerlo, pero a mi con esta me vale por ahora. Si alguien ve mejoras/correcciones sobre el Detector le mandaré unas cañas virtuales.


Repaso a la clase Player


Primero si miramos el sprite que hemos creado del jugador, realmente lo hemos guardado en un objeto AnimatedSprite, para poder hacer la animación del balanceo de cuando se suelta el jugador o cuando la pelota lo golpee.



Tenemos aqui los diferentes frames de la animación. Vamos al tajo. Definimos una constante para definir el frame máximo para la carga, los dos últimos son para hacer el movimiento de rebote cuando se suelta.


  1. private static final int PLAYER_CHARGE_ANIMATIONS = 5;
Parsed in 0.073 seconds at 737 B/s, using GeSHi 1.0.8.10

Ahora añadimos un procedimiento a la clase


Añadir este procedimiento a la clase Player
  1. public void setCharge(final float angle, final float distance)
  2. {
  3. final int step = Math.round(distance * PLAYER_CHARGE_ANIMATIONS / Main.MAX_CHARGE_DISTANCE);  
  4. this.stopAnimation(step);
  5. this.setRotation(angle);  
  6. }
Parsed in 0.067 seconds at 3.32 KB/s, using GeSHi 1.0.8.10

En la primera linea sacamos por una regla de 3 el paso correspondiente a la distancia que nos ha enviado el Detector. Aqui usamos una constante Main.MAX_CHARGE_DISTANCE que definiremos después. Una vez tenemos el sprite que vamos a pintar, usamos el método stopAnimation de SpriteAnimation para parar la animación en ese frame y luego lo rotamos en el ángulo que toca.


Vamos ahora al Main.java y metemos esa constante. A mi se me quedan así.


  1. // ===========================================================
  2. // Constants
  3. // ===========================================================
  4. private static final int CAMERA_WIDTH = 480;
  5. private static final int CAMERA_HEIGHT = 320;
  6. public static final float MAX_CHARGE_DISTANCE = 80;
  7.  
  8. private static final String TAG = "AndEngineTest";
  9.  
Parsed in 0.069 seconds at 4.94 KB/s, using GeSHi 1.0.8.10

Ahora en el onLoadScene() tocamos donde se crea el CatapultDetector y le pasamos esta constante.


  1. this.mScrollDetector = new SurfaceScrollDetector(this);
  2. this.mScrollDetector.setEnabled(false); 
  3.  
  4. this.mCatapultDetector = new CatapultDetector(MAX_CHARGE_DISTANCE,this);
  5. this.mCatapultDetector.setEnabled(true);
Parsed in 0.089 seconds at 2.46 KB/s, using GeSHi 1.0.8.10

Y cambiamos el método onCharge()


  1. @Override
  2. public void onCharge(CatapultDetector pCatapultDetector,
  3. TouchEvent pTouchEvent, float pDistance, float pAngle) {
  4. this.mActivePlayer.setCharge(pAngle,pDistance);  
  5. }
Parsed in 0.076 seconds at 2.42 KB/s, using GeSHi 1.0.8.10

Donde le pasamos al jugador la carga. Con esto ya funciona perfectamente el movimiento. En mi caso esto funciona bien. Si teneis algún problema con esta parte del tutorial decidmelo y aclaro ese punto. Por hoy está bien, en el siguiente voy a intentar hacer las animaciones de soltar al jugador, y poner un HUD para el marcador.

viernes, 25 de marzo de 2011

Introducción a AndEngine (Parte IV)

Anteriormente en Droideando



Objetivos de hoy

  • Crear el sprite del jugador
  • Crear un Detector de AndEngine para el efecto de catapulta en el jugador



Creamos el jugador


Para crear el jugador he decidido usar un AnimatedSprite, pero como seguro que le tengo que meter alguna cosa, heredo la clase y la uso.


Para crear la clase, vamos a la carpeta de nuestros fuentes en Eclipse. File New -> Class y le ponemos Name Player y SuperClass AnimatedSprite. Le añadimos un TAG para el debug y por ahora listo.Se nos queda algo así.


  1. package com.pruebas.andengine;
  2.  
  3. import org.anddev.andengine.entity.sprite.AnimatedSprite;
  4. import org.anddev.andengine.input.touch.detector.HoldDetector;
  5. import org.anddev.andengine.input.touch.detector.HoldDetector.IHoldDetectorListener;
  6. import org.anddev.andengine.opengl.texture.region.TiledTextureRegion;
  7.  
  8. import android.util.Log;
  9.  
  10. public class Player extends AnimatedSprite  {
  11.  
  12. private static final String TAG = "Player"; 
  13.  
  14. public Player(float pX, float pY, TiledTextureRegion pTiledTextureRegion) {
  15. super(pX, pY, pTiledTextureRegion);  
  16. }
  17.  
  18.  
  19. }
  20.  
Parsed in 0.077 seconds at 7.25 KB/s, using GeSHi 1.0.8.10

Ahora volvemos al Main.java y añadimos una variable para guardar el jugador activo y cambamos la variable mFaceTextureRegion por mBallTextureRegion para que el nombre coincida con lo que lleva dentro, y le cambiamos también el tipo a TiledTextureRegion. Se queda así.


  1. // ===========================================================
  2. // Fields
  3. // ===========================================================
  4.  
  5. private ZoomCamera mCamera;
  6. private Texture mTexture;
  7. private TiledTextureRegion mBallTextureRegion;
  8. private TiledTextureRegion mPlayerTextureRegion;
  9. private SurfaceScrollDetector mScrollDetector;
  10. private TMXTiledMap mTMXTiledMap;
  11. private Player mActivePlayer;
Parsed in 0.106 seconds at 3.86 KB/s, using GeSHi 1.0.8.10

Aqui pongo el sprite del jugador para que lo bajeis a vuestro proyecto, en la carpeta assets/gfx como de costumbre



Pongo también el sprite de la pelota (provisional, necesito un balón de verdad para echarle fotos), al mismo sitio assets/gfx.



Ahora vamos a cambiar la carga de texturas para cargar estas dos nuevas textuas. La carga de texturas se hace en el objeto mTexture. El objeto mTexture debe ser por exigencias de AndEngine tanto en altura como longitud potencias de 2 en píxeles. Para las texturas que tenemos ahora mismo, lo mínimo sin despercidicar ni un pixel en memoria es 256X256 píxeles. La textura en memoria se quedaría algo como ésto:




Ahora vamos a cambiar en onLoadResources para cargar todo esto que hemos metido nuevo. Primero el tamaño de la textura pasa a ser 256X256 para poder albergar las texturas que hemos metido nuevas. A la hora de crear la textura de la pelota, usamos la funcion createTiledFromAsset(), que usa los siguientes parámetros:

  • this.mTexture: El primero parámetro es el objeto textura donde lo vamos a guardar. En el objeto mTexture de la clase.
  • this: La clase actual. Pocos comentarios al respecto.
  • "gfx/ui_ball.png": La ruta a la imagen.
  • 0,0: los dos siguientes parámetros es donde vamos a colocar éste objeto dentro de mTexture, en este caso vamos a colocar la pelota en la parte de la izquierda y el jugador en la derecha, o sea, para la pelota le ponemos 0,0 y para el jugador 128,0.
  • 2,4: Número de Filas y Columnas, en el caso tanto de la pelota como del jugador son 2,4.
Se queda una cosa así:


  1. @Override
  2. public void onLoadResources() {
  3. this.mTexture = new Texture(256, 256,
  4. TextureOptions.BILINEAR_PREMULTIPLYALPHA);
  5. this.mBallTextureRegion = TextureRegionFactory.createTiledFromAsset(
  6. this.mTexture, this, "gfx/ui_ball.png", 0, 0, 2, 4);
  7. this.mPlayerTextureRegion = TextureRegionFactory.createTiledFromAsset(
  8. this.mTexture, this, "gfx/ui_player.png", 128, 0, 2, 4);
  9.  
  10. this.mEngine.getTextureManager().loadTexture(this.mTexture);
  11.  
  12. }
Parsed in 0.074 seconds at 6.24 KB/s, using GeSHi 1.0.8.10

Ahora vamos a tocar el método onLoadScene(). Primero eliminamos el código que creaba la bola y desactivamos el Detector de Scroll. El final del procedimiento se me queda así.


  1. scene.setOnAreaTouchTraversalFrontToBack();
  2.  
  3. this.mScrollDetector = new SurfaceScrollDetector(this);
  4. this.mScrollDetector.setEnabled(false); //Hemos puesto esto a false
  5.  
  6. /*
  7.   final int centerX = (CAMERA_WIDTH - this.mFaceTextureRegion.getWidth()) / 2;
  8.   final int centerY = (CAMERA_HEIGHT - this.mFaceTextureRegion
  9.     .getHeight()) / 2;
  10.  
  11.   final Sprite ball = new Sprite(centerX, centerY,
  12.     this.mFaceTextureRegion);
  13.   scene.getLastChild().attachChild(ball);
  14.      */
  15. scene.setOnSceneTouchListener(this);
  16. scene.setTouchAreaBindingEnabled(true);
  17.  
  18. return scene;
  19. }
Parsed in 0.077 seconds at 7.57 KB/s, using GeSHi 1.0.8.10

Ahora metemos el jugador en pantalla y ya podemos probar como se ve y eso. Para ello creamos un método createPlayer().


  1. // ===========================================================
  2. // Methods
  3. // ===========================================================
  4.  
  5. private void createPlayer() {
  6. final Scene scene = this.mEngine.getScene();
  7.  
  8. final Player sprite = new Player(200, 100, this.mPlayerTextureRegion);
  9. // scene.registerTouchArea(sprite);
  10.  
  11. scene.getLastChild().attachChild(sprite);
  12. this.mActivePlayer = sprite;
  13. }
Parsed in 0.069 seconds at 5.99 KB/s, using GeSHi 1.0.8.10

Aqui lo que hacemos es para probar ponemos un jugador en la posicion 200,100. Ahora llamamos a este método desde onLoadComplete().


  1. @Override
  2. public void onLoadComplete() { // scene.set 
  3. createPlayer();
  4. }
Parsed in 0.072 seconds at 1.08 KB/s, using GeSHi 1.0.8.10

En este punto ya podemos ejecutar el programa para ver como va la cosa. Se ve el cesped y el jugador, pero no responde al Scroll porque lo hemos desactivado. Por ahora todo va bien. Vamos a crear el Detector.


Creando un Detector


Mirando un poco los fuentes del ScrollDetector, que funciona de fábula y siendo el juego tipo catapultas bastante popular, porqué no hacer un Detector de catapultas?? Manos a la obra.


Creamos una nueva clase en nuestro proyecto. Nombre: CatapultDetector y SuperClass BaseDetector (org.anddev.andengine.input.touch.detector.BaseDetector)


  1. package com.pruebas.andengine;
  2.  
  3. import org.anddev.andengine.input.touch.TouchEvent;
  4. import org.anddev.andengine.input.touch.detector.BaseDetector;
  5.  
  6. public class CatapultDetector extends BaseDetector {
  7.  
  8. @Override
  9. protected boolean onManagedTouchEvent(TouchEvent pSceneTouchEvent) {
  10. // TODO Auto-generated method stub
  11. return false;
  12. }
  13.  
  14. }
  15.  
Parsed in 0.091 seconds at 3.82 KB/s, using GeSHi 1.0.8.10

Un detector es un objeto para usarlo como hicimos con el ScrollDetector en el anterior tutorial. Necesitamos una interface listener. Vamos a ello.


  1. public static interface ICatapultDetectorListener {
  2. // ===========================================================
  3. // Constants
  4. // ===========================================================
  5.  
  6. // ===========================================================
  7. // Methods
  8. // ===========================================================
  9.  
  10. public void onCharge(final CatapultDetector pCatapultDetector,
  11. final TouchEvent pTouchEvent, final float pDistance,
  12. final float pAngle);
  13.  
  14. public void onShoot(final CatapultDetector pCatapultDetector,
  15. final TouchEvent pTouchEvent, final float pDistance,
  16. final float pAngle);
  17. }
  18.  
Parsed in 0.102 seconds at 6.30 KB/s, using GeSHi 1.0.8.10


Este es un listener básico para probar el funcionamiento. Al objeto que esté escuchando le pasamos un ángulo y una distancia. Esta clase la definimos dentro de la CatapultDetector al final como en los demás Detectors que he visto en los fuentes de AndEngine.


Vamos a meterle algunas variables al Detector que algunas las vamos a usar ya mismo y otras las usaremos después. No preocuparse por los warnings, ya nos encargaremos de eso más tarde


  1. // ===========================================================
  2. // Constants
  3. // ===========================================================
  4. private static final float TRIGGER_SCROLL_MINIMUM_DISTANCE_DEFAULT = 10;
  5. private static final float ANGLE_CONSTANT = 10;
  6. private static final int DEFAULT_STEPS = 6;
  7. private float DEFAULT_MAX_DISTANCE = 100;
  8. // ===========================================================
  9. // Fields
  10. // ===========================================================
  11.  
  12. //Minimum distance to execute 
  13. private float mTriggerScrollMinimumDistance;
  14.  
  15. //Listener for the Detector
  16. private final ICatapultDetectorListener mCatapultDetectorListener;
  17.  
  18. private boolean mTriggered;
  19.  
  20. //First Touch
  21. private float mFirstX;
  22. private float mFirstY;
  23.  
  24. //Last Touch
  25. private float mLastX;
  26. private float mLastY;
  27.  
  28. private int mSteps;
  29. private float mMaxDistance;
Parsed in 0.088 seconds at 10.05 KB/s, using GeSHi 1.0.8.10

Creamos unos métodos que usaremos después y los Getter y Setter para mTriggerScrollMinimumDistance.


  1. // ===========================================================
  2. // Getter & Setter
  3. // ===========================================================
  4.  
  5. public void setTriggerScrollMinimumDistance(
  6. float mTriggerScrollMinimumDistance) {
  7. this.mTriggerScrollMinimumDistance = mTriggerScrollMinimumDistance;
  8. }
  9.  
  10. public float getTriggerScrollMinimumDistance() {
  11. return mTriggerScrollMinimumDistance;
  12. }
  13.  
  14. // ===========================================================
  15. // Methods
  16. // ===========================================================
  17.  
  18. protected float getX(final TouchEvent pTouchEvent) {
  19. return pTouchEvent.getX();
  20. }
  21.  
  22. protected float getY(final TouchEvent pTouchEvent) {
  23. return pTouchEvent.getY();
  24. }
Parsed in 0.098 seconds at 7.41 KB/s, using GeSHi 1.0.8.10

Tocamos un poco los constructores de la clase


  1. // ===========================================================
  2. // Constructors
  3. // ===========================================================
  4.  
  5. public CatapultDetector(
  6. final ICatapultDetectorListener pCatapultDetectorListener) {
  7. this(TRIGGER_SCROLL_MINIMUM_DISTANCE_DEFAULT, pCatapultDetectorListener);
  8. }
  9.  
  10. public CatapultDetector(final float pTriggerScrollMinimumDistance,
  11. final ICatapultDetectorListener pCatapultDetectorListener) {
  12. this.setTriggerScrollMinimumDistance(pTriggerScrollMinimumDistance);
  13. this.mCatapultDetectorListener = pCatapultDetectorListener;
  14. }
Parsed in 0.077 seconds at 7.53 KB/s, using GeSHi 1.0.8.10

Y ahora tocamos el método donde se va a llevar todo a cabo, para nuestra primera prueba nos vale algo sencillo como ésto


  1. // ===========================================================
  2. // Methods for/from SuperClass/Interfaces
  3. // ===========================================================
  4.  
  5. @Override
  6. protected boolean onManagedTouchEvent(TouchEvent pSceneTouchEvent) {
  7. final float touchX = this.getX(pSceneTouchEvent);
  8. final float touchY = this.getY(pSceneTouchEvent);
  9.  
  10. switch (pSceneTouchEvent.getAction()) {
  11. case MotionEvent.ACTION_DOWN:
  12. this.mFirstX = touchX;
  13. this.mFirstY = touchY;
  14. this.mLastX = touchX;
  15. this.mLastY = touchY;
  16. this.mTriggered = false;
  17. return true;
  18. case MotionEvent.ACTION_MOVE:
  19. case MotionEvent.ACTION_UP:
  20. case MotionEvent.ACTION_CANCEL:
  21. final float distanceX = touchX - this.mLastX;
  22. final float distanceY = touchY - this.mLastY;
  23. if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_MOVE) {
  24. final float triggerScrollMinimumDistance = this.mTriggerScrollMinimumDistance;
  25. if (this.mTriggered
  26. || Math.abs(distanceX) > triggerScrollMinimumDistance
  27. || Math.abs(distanceY) > triggerScrollMinimumDistance) {
  28. final float distance = (float)Math.hypot((double)distanceX,(double)distanceY);     
  29. final double angleX = touchX - this.mFirstX;
  30. final double angleY = touchY - this.mFirstY;
  31. final float angle = (float)Math.toDegrees(Math.atan2(angleY, angleX))+ANGLE_CONSTANT;
  32. this.mCatapultDetectorListener.onCharge(this, pSceneTouchEvent, distance, angle);
  33. this.mLastX = touchX;
  34. this.mLastY = touchY;
  35. this.mTriggered = true;
  36. }
  37. else
  38. {
  39.  
  40.  
  41. }
  42. }
  43. return true;
  44. default:
  45. return false;
  46. }
  47. }
  48.  
Parsed in 0.097 seconds at 16.70 KB/s, using GeSHi 1.0.8.10

Ya entraré mas a detalle en otro artículo de como funciona el Detector, pero mirando los fuentes puedes hacerte una idea bastante clara. Con esto ya tenemos el Detector en el punto para probarlo. Volvamos al Main.java. Creamos una variable privada para albergar el detector.


  1. // ===========================================================
  2. // Fields
  3. // ===========================================================
  4.  
  5. private ZoomCamera mCamera;
  6. private Texture mTexture;
  7. private TiledTextureRegion mBallTextureRegion;
  8. private TiledTextureRegion mPlayerTextureRegion;
  9. private SurfaceScrollDetector mScrollDetector; 
  10. private TMXTiledMap mTMXTiledMap;
  11. private Player mActivePlayer;
  12. private CatapultDetector mCatapultDetector; //Nueva variable Creada
Parsed in 0.078 seconds at 6.09 KB/s, using GeSHi 1.0.8.10

Ahora necesitamos le decimos a nuestra Clase Main, que implemente la interface ICatapultDetectorListener.


  1. public class Main extends BaseGameActivity implements IScrollDetectorListener,
  2. IOnSceneTouchListener, ICatapultDetectorListener
Parsed in 0.082 seconds at 1.54 KB/s, using GeSHi 1.0.8.10

Ahora implementamos los métodos de ICatapultDetectorListener.


  1. @Override
  2. public void onCharge(CatapultDetector pCatapultDetector,
  3. TouchEvent pTouchEvent, float pDistance, float pAngle) {
  4. Log.d(TAG, "Cargando... {Distancia:" + pDistance + ", angulo: "
  5. + pAngle + "}");
  6. this.mActivePlayer.setRotation(pAngle);
  7.  
  8. }
  9.  
  10. @Override
  11. public void onShoot(CatapultDetector pCatapultDetector,
  12. TouchEvent pTouchEvent, float pDistance, float pAngle) {
  13. Log.d(TAG, "Disparo... {Distancia:" + pDistance + ", angulo: " + pAngle
  14. + "}");
  15. }
Parsed in 0.084 seconds at 5.75 KB/s, using GeSHi 1.0.8.10

En el método onCharge() rotamos el jugador el ángulo que correspode, en el onShoot() un debug solamente, aunque realmente en el estado del detector no se va a disparar este evento aún. Lo siguiente es crear en el onLoadScene() el Scroll y asignarlo.


  1. this.mScrollDetector = new SurfaceScrollDetector(this);
  2. this.mScrollDetector.setEnabled(false); 
  3.  
  4. //Creamos el detector de catapulta
  5. this.mCatapultDetector = new CatapultDetector(this);
  6. this.mCatapultDetector.setEnabled(true);
Parsed in 0.093 seconds at 2.55 KB/s, using GeSHi 1.0.8.10

Y en el onSceneTouchEvent le pasamos el evento al CatapultDetector.



  1. @Override
  2. public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
  3. if (this.mActivePlayer != null) {
  4. this.mCatapultDetector.onTouchEvent(pSceneTouchEvent);
  5. }
  6. return true;
  7. }
Parsed in 0.079 seconds at 2.59 KB/s, using GeSHi 1.0.8.10


Ya tenemos el proyecto en otro momento para poder ejecutar y ver que está pasando por debajo de todo ésto. F11 y arranca splash... aparece el campo con el jugador, hacemos click en cualquier sitio, y al mover el dedo por la pantalla vemos como el jugador gira, aunque no lo hace en el ángulo correcto, pero es un principio. Ahora mismo mis archivos están asi:



Main.java
  1. package com.pruebas.andengine;
  2.  
  3. import org.anddev.andengine.engine.Engine;
  4. import org.anddev.andengine.engine.camera.ZoomCamera;
  5. import org.anddev.andengine.engine.options.EngineOptions;
  6. import org.anddev.andengine.engine.options.EngineOptions.ScreenOrientation;
  7. import org.anddev.andengine.engine.options.resolutionpolicy.RatioResolutionPolicy;
  8. import org.anddev.andengine.entity.layer.tiled.tmx.TMXLayer;
  9. import org.anddev.andengine.entity.layer.tiled.tmx.TMXLoader;
  10. import org.anddev.andengine.entity.layer.tiled.tmx.TMXProperties;
  11. import org.anddev.andengine.entity.layer.tiled.tmx.TMXTile;
  12. import org.anddev.andengine.entity.layer.tiled.tmx.TMXTileProperty;
  13. import org.anddev.andengine.entity.layer.tiled.tmx.TMXTiledMap;
  14. import org.anddev.andengine.entity.layer.tiled.tmx.TMXLoader.ITMXTilePropertiesListener;
  15. import org.anddev.andengine.entity.layer.tiled.tmx.util.exception.TMXLoadException;
  16. import org.anddev.andengine.entity.scene.Scene;
  17. import org.anddev.andengine.entity.scene.Scene.IOnSceneTouchListener;
  18. import org.anddev.andengine.entity.sprite.AnimatedSprite;
  19. import org.anddev.andengine.entity.sprite.Sprite;
  20. import org.anddev.andengine.entity.util.FPSLogger;
  21. import org.anddev.andengine.input.touch.TouchEvent;
  22. import org.anddev.andengine.input.touch.detector.ScrollDetector;
  23. import org.anddev.andengine.input.touch.detector.SurfaceScrollDetector;
  24. import org.anddev.andengine.input.touch.detector.ScrollDetector.IScrollDetectorListener;
  25. import org.anddev.andengine.opengl.texture.Texture;
  26. import org.anddev.andengine.opengl.texture.TextureOptions;
  27. import org.anddev.andengine.opengl.texture.region.TextureRegion;
  28. import org.anddev.andengine.opengl.texture.region.TextureRegionFactory;
  29. import org.anddev.andengine.opengl.texture.region.TiledTextureRegion;
  30. import org.anddev.andengine.ui.activity.BaseGameActivity;
  31. import org.anddev.andengine.util.Debug;
  32.  
  33. import android.util.Log;
  34.  
  35. import com.pruebas.andengine.CatapultDetector.ICatapultDetectorListener;
  36.  
  37. public class Main extends BaseGameActivity implements IScrollDetectorListener,
  38. IOnSceneTouchListener, ICatapultDetectorListener {
  39. // ===========================================================
  40. // Constants
  41. // ===========================================================
  42. static final int CAMERA_WIDTH = 480;
  43. static final int CAMERA_HEIGHT = 320;
  44.  
  45. private static final String TAG = "AndEngineTest";
  46.  
  47. // ===========================================================
  48. // Fields
  49. // ===========================================================
  50.  
  51. private ZoomCamera mCamera;
  52. private Texture mTexture;
  53. private TiledTextureRegion mBallTextureRegion;
  54. private TiledTextureRegion mPlayerTextureRegion;
  55. private SurfaceScrollDetector mScrollDetector; 
  56. private TMXTiledMap mTMXTiledMap;
  57. private Player mActivePlayer;
  58. private CatapultDetector mCatapultDetector; //Nueva variable Creada
  59.  
  60. // ===========================================================
  61. // Constructors
  62. // ===========================================================
  63.  
  64. // ===========================================================
  65. // Getter & Setter
  66. // ===========================================================
  67.  
  68. // ===========================================================
  69. // Methods for/from SuperClass/Interfaces
  70. // ===========================================================
  71.  
  72. @Override
  73. public void onLoadComplete() {  
  74. createPlayer();
  75. }
  76.  
  77. @Override
  78. public Engine onLoadEngine() {
  79. this.mCamera = new ZoomCamera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT);
  80. final int alturaTotal = CAMERA_HEIGHT * 3;
  81. this.mCamera.setBounds(0, CAMERA_WIDTH, 0, alturaTotal);
  82. this.mCamera.setBoundsEnabled(true);
  83. return new Engine(new EngineOptions(true, ScreenOrientation.LANDSCAPE,
  84. new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT),
  85. this.mCamera));
  86. }
  87.  
  88. @Override
  89. public void onLoadResources() {
  90. this.mTexture = new Texture(256, 256,
  91. TextureOptions.BILINEAR_PREMULTIPLYALPHA);
  92. this.mBallTextureRegion = TextureRegionFactory.createTiledFromAsset(
  93. this.mTexture, this, "gfx/ui_ball.png", 0, 0, 2, 4);
  94. this.mPlayerTextureRegion = TextureRegionFactory.createTiledFromAsset(
  95. this.mTexture, this, "gfx/ui_player.png", 128, 0, 2, 4);
  96.  
  97. this.mEngine.getTextureManager().loadTexture(this.mTexture);
  98.  
  99. }
  100.  
  101. @Override
  102. public Scene onLoadScene() {
  103. this.mEngine.registerUpdateHandler(new FPSLogger());
  104.  
  105. final Scene scene = new Scene(1);
  106.  
  107. try {
  108. final TMXLoader tmxLoader = new TMXLoader(this, this.mEngine
  109. .getTextureManager(), // TextureOptions.BILINEAR_PREMULTIPLYALPHA,
  110. TextureOptions.NEAREST, new ITMXTilePropertiesListener() {
  111.  
  112. @Override
  113. public void onTMXTileWithPropertiesCreated(
  114. final TMXTiledMap pTMXTiledMap,
  115. final TMXLayer pTMXLayer,
  116. final TMXTile pTMXTile,
  117. final TMXProperties<TMXTileProperty> pTMXTileProperties) {
  118.  
  119. }
  120.  
  121. });
  122. this.mTMXTiledMap = tmxLoader.loadFromAsset(this, "tmx/field.tmx");
  123. } catch (final TMXLoadException tmxle) {
  124. Debug.e(tmxle);
  125. }
  126.  
  127. final TMXLayer tmxLayer = this.mTMXTiledMap.getTMXLayers().get(0);
  128. scene.getFirstChild().attachChild(tmxLayer);
  129.  
  130. scene.setOnAreaTouchTraversalFrontToBack();
  131.  
  132. this.mScrollDetector = new SurfaceScrollDetector(this);
  133. this.mScrollDetector.setEnabled(false); 
  134.  
  135. this.mCatapultDetector = new CatapultDetector(this);
  136. this.mCatapultDetector.setEnabled(true);
  137.  
  138. scene.setOnSceneTouchListener(this);
  139. scene.setTouchAreaBindingEnabled(true);
  140.  
  141. return scene;
  142. }
  143.  
  144.  
  145. @Override
  146. public void onCharge(CatapultDetector pCatapultDetector,
  147. TouchEvent pTouchEvent, float pDistance, float pAngle) {
  148. Log.d(TAG, "Cargando... {Distancia:" + pDistance + ", angulo: "
  149. + pAngle + "}");
  150. this.mActivePlayer.setRotation(pAngle);
  151.  
  152. }
  153.  
  154. @Override
  155. public void onShoot(CatapultDetector pCatapultDetector,
  156. TouchEvent pTouchEvent, float pDistance, float pAngle) {
  157. Log.d(TAG, "Disparo... {Distancia:" + pDistance + ", angulo: " + pAngle
  158. + "}");
  159. } 
  160.  
  161.  
  162. @Override
  163. public void onScroll(ScrollDetector pScollDetector, TouchEvent pTouchEvent,
  164. float pDistanceX, float pDistanceY) {
  165. this.mCamera.offsetCenter(-pDistanceX, -pDistanceY);
  166. }
  167.  
  168. @Override
  169. public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
  170. if (this.mActivePlayer != null) {
  171. this.mCatapultDetector.onTouchEvent(pSceneTouchEvent);
  172. }
  173. return true;
  174. }
  175.  
  176. // ===========================================================
  177. // Methods
  178. // ===========================================================
  179.  
  180. private void createPlayer() {
  181. final Scene scene = this.mEngine.getScene();
  182.  
  183. final Player sprite = new Player(200, 100, this.mPlayerTextureRegion);
  184. scene.getLastChild().attachChild(sprite);
  185.  
  186. this.mActivePlayer = sprite;
  187. } 
  188.  
  189.  
  190. // ===========================================================
  191. // Inner and Anonymous Classes
  192. // ===========================================================
  193. }
  194.  
Parsed in 0.129 seconds at 53.47 KB/s, using GeSHi 1.0.8.10


CatapultDetector.java
  1. package com.pruebas.andengine;
  2.  
  3. import org.anddev.andengine.input.touch.TouchEvent;
  4. import org.anddev.andengine.input.touch.detector.BaseDetector;
  5.  
  6. import android.view.MotionEvent;
  7.  
  8. public class CatapultDetector extends BaseDetector {
  9. // ===========================================================
  10. // Constants
  11. // ===========================================================
  12. private static final float TRIGGER_SCROLL_MINIMUM_DISTANCE_DEFAULT = 10;
  13. private static final float ANGLE_CONSTANT = 10;
  14. private static final int DEFAULT_STEPS = 6;
  15. private float DEFAULT_MAX_DISTANCE = 100;
  16. // ===========================================================
  17. // Fields
  18. // ===========================================================
  19.  
  20. //Minimum distance to execute 
  21. private float mTriggerScrollMinimumDistance;
  22.  
  23. //Listener for the Detector
  24. private final ICatapultDetectorListener mCatapultDetectorListener;
  25.  
  26. private boolean mTriggered;
  27.  
  28. //First Touch
  29. private float mFirstX;
  30. private float mFirstY;
  31.  
  32. //Last Touch
  33. private float mLastX;
  34. private float mLastY;
  35.  
  36. private int mSteps;
  37. private float mMaxDistance;
  38.  
  39. // ===========================================================
  40. // Constructors
  41. // ===========================================================
  42.  
  43. public CatapultDetector(
  44. final ICatapultDetectorListener pCatapultDetectorListener) {
  45. this(TRIGGER_SCROLL_MINIMUM_DISTANCE_DEFAULT, pCatapultDetectorListener);
  46. }
  47.  
  48. public CatapultDetector(final float pTriggerScrollMinimumDistance,
  49. final ICatapultDetectorListener pCatapultDetectorListener) {
  50. this.setTriggerScrollMinimumDistance(pTriggerScrollMinimumDistance);
  51. this.mCatapultDetectorListener = pCatapultDetectorListener;
  52. }
  53.  
  54.  
  55.  
  56. // ===========================================================
  57. // Methods for/from SuperClass/Interfaces
  58. // =========================================================== 
  59.  
  60. @Override
  61. protected boolean onManagedTouchEvent(TouchEvent pSceneTouchEvent) {
  62. final float touchX = this.getX(pSceneTouchEvent);
  63. final float touchY = this.getY(pSceneTouchEvent);
  64.  
  65. switch (pSceneTouchEvent.getAction()) {
  66. case MotionEvent.ACTION_DOWN:
  67. this.mFirstX = touchX;
  68. this.mFirstY = touchY;
  69. this.mLastX = touchX;
  70. this.mLastY = touchY;
  71. this.mTriggered = false;
  72. return true;
  73. case MotionEvent.ACTION_MOVE:
  74. case MotionEvent.ACTION_UP:
  75. case MotionEvent.ACTION_CANCEL:
  76. final float distanceX = touchX - this.mLastX;
  77. final float distanceY = touchY - this.mLastY;
  78. if (pSceneTouchEvent.getAction() == MotionEvent.ACTION_MOVE) {
  79. final float triggerScrollMinimumDistance = this.mTriggerScrollMinimumDistance;
  80. if (this.mTriggered
  81. || Math.abs(distanceX) > triggerScrollMinimumDistance
  82. || Math.abs(distanceY) > triggerScrollMinimumDistance) {
  83. final float distance = (float)Math.hypot((double)distanceX,(double)distanceY);     
  84. final double angleX = touchX - this.mFirstX;
  85. final double angleY = touchY - this.mFirstY;
  86. final float angle = (float)Math.toDegrees(Math.atan2(angleY, angleX));
  87. this.mCatapultDetectorListener.onCharge(this, pSceneTouchEvent, distance, angle);
  88. this.mLastX = touchX;
  89. this.mLastY = touchY;
  90. this.mTriggered = true;
  91. }
  92. else
  93. {
  94.  
  95.  
  96. }
  97. }
  98. return true;
  99. default:
  100. return false;
  101. }
  102. }
  103.  
  104. // ===========================================================
  105. // Getter & Setter
  106. // ===========================================================
  107.  
  108. public void setTriggerScrollMinimumDistance(
  109. float mTriggerScrollMinimumDistance) {
  110. this.mTriggerScrollMinimumDistance = mTriggerScrollMinimumDistance;
  111. }
  112.  
  113. public float getTriggerScrollMinimumDistance() {
  114. return mTriggerScrollMinimumDistance;
  115. }
  116.  
  117. // ===========================================================
  118. // Methods
  119. // ===========================================================
  120.  
  121. protected float getX(final TouchEvent pTouchEvent) {
  122. return pTouchEvent.getX();
  123. }
  124.  
  125. protected float getY(final TouchEvent pTouchEvent) {
  126. return pTouchEvent.getY();
  127. } 
  128.  
  129.  
  130. // ===========================================================
  131. // Inner and Anonymous Classes
  132. // =========================================================== 
  133.  
  134. public static interface ICatapultDetectorListener {
  135. // ===========================================================
  136. // Constants
  137. // ===========================================================
  138.  
  139. // ===========================================================
  140. // Methods
  141. // ===========================================================
  142.  
  143. public void onCharge(final CatapultDetector pCatapultDetector,
  144. final TouchEvent pTouchEvent, final float pDistance,
  145. final float pAngle);
  146.  
  147. public void onShoot(final CatapultDetector pCatapultDetector,
  148. final TouchEvent pTouchEvent, final float pDistance,
  149. final float pAngle);
  150. }
  151.  
  152. }
  153.  
Parsed in 0.126 seconds at 38.60 KB/s, using GeSHi 1.0.8.10

Bueno, por hoy está bien, voy a seguir trabajando en el Detector y cuando lo tenga terminado lo pongo en el foro de AndEngine y subo otro artículo siguiendo por la misma línea.