lunes, 21 de marzo de 2011

Introducción a AndEngine (Parte II)

Anteriormente en Droideando



Creando nuestro primer objeto


Antes de crear nuestro objeto, vamos a tocar un poco el AndroidManifest.xml para añadirle permiso a la aplicación para que bloquee la suspensión del teléfono, bastante útil para un juego. Editamos el archivo y después de la linea donde pone

ponemos
.


Vamos a crear nuestro primer objeto en pantalla, para usar imágenes en AndEngine no se usa el típico R de android. Creamos una carpeta, en assets llamada gfx.





Ahora descarga esta imagen a tu disco duro y ponla en la carpeta assets/gfx de tu proyecto, Puedes guardarla directamente ahí o arrastrarla desde tu carpeta de descargas a la carpeta gfx de eclipse. Si vas a usar una imagen diferente tienes que tener en cuenta que tanto la anchura de la imagen como la altura en píxeles deben ser potencias de 2.

Edito: No es necesario que las imágenes estén en un tamaño que sea potenica de 2. Lo que se necesita que sea una potencia de 2 es el objeto mTexture. Acabo de enterarme como va ésto, el siguiente artículo ya sé de que va a ir..



Lo siguiente es tocar nuestro proyecto en eclipse para que se muestre esa imagen. En la parte donde definimos la cámara añadimos 2 variables nuevas.


// ===========================================================
 // Fields
 // ===========================================================
 
 private ZoomCamera mCamera;
 private Texture mTexture;
 private TextureRegion mFaceTextureRegion;

En el método onLoadResources() tenemos que cargar la imagen a memoria.


@Override
 public void onLoadResources() {
  this.mTexture = new Texture(64, 64, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
  this.mFaceTextureRegion = TextureRegionFactory.createFromAsset(this.mTexture, this, "gfx/ui_ball_1.png", 0, 0);

  this.mEngine.getTextureManager().loadTexture(this.mTexture);
  
 }

Y finalmente en el método onLoadScene() ponemos el código necesario para mostrar la imagen centrada en la pantalla.


@Override
 public Scene onLoadScene() {
  this.mEngine.registerUpdateHandler(new FPSLogger());

        final Scene scene = new Scene(1);
        scene.setBackground(new ColorBackground(0, 0, 0.8784f));
        
        final int centerX = (CAMERA_WIDTH - this.mFaceTextureRegion.getWidth()) / 2;
  final int centerY = (CAMERA_HEIGHT - this.mFaceTextureRegion.getHeight()) / 2;

  /* Dibujamos la bola en el centro de la pantalla. */
  final Sprite ball = new Sprite(centerX, centerY, this.mFaceTextureRegion);
  scene.getLastChild().attachChild(ball);


        return scene;
 }

Ahora si ejecutamos nuestro programa en pantalla sale lo que nosotros esperábamos.



Hasta ahora todo correcto. Ahora vamos a controlar el movimiento de la cámara con toques de dedo.



Controlando el Scroll


Para que funcione el movimiento de la cámara debemos hacer que nuestra clase Main implemente dos clases.


  • IScrollDetectorListener: Esta clase es una clase que nos brinda AndEngine para controlar scroll, la implementamos en la clase e implementamos el método al que nos obliga.

  • IOnSceneTouchListener: Esta clase nos da la posibilidad de controlar la pantalla táctil.

La clase se nos queda así:

public class Main extends BaseGameActivity implements IScrollDetectorListener, IOnSceneTouchListener 

Implementamos los métodos necesarios

@Override
 public void onScroll(ScrollDetector pScollDetector, TouchEvent pTouchEvent,
   float pDistanceX, float pDistanceY) {
 }

 @Override
 public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
  return true;
 }

Ahora vamos a poner algo dentro de esos métodos, en onScroll ponemos solamente un mensaje de log, para verlo luego en el debug.

@Override
 public void onScroll(ScrollDetector pScollDetector, TouchEvent pTouchEvent,
   float pDistanceX, float pDistanceY) {
  Log.d(TAG, "Scroll {x:"+pDistanceX+", y: "+pDistanceY+"}");  
 }

En el TouchEvent pasamos el evento directamente al ScrollDetector.

 @Override
 public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
  this.mScrollDetector.onTouchEvent(pSceneTouchEvent);
  return true;
 }

Tenemos que tocar un poco también el método onLoadScene() para que el engine escuche los enventos de la pantalla táctil:

@Override
 public Scene onLoadScene() {
 
        this.mEngine.registerUpdateHandler(new FPSLogger());

        final Scene scene = new Scene(1);
        scene.setBackground(new ColorBackground(0, 0, 0.8784f));
        scene.setOnAreaTouchTraversalFrontToBack();
        
        this.mScrollDetector = new SurfaceScrollDetector(this);
        this.mScrollDetector.setEnabled(true);
        
        
        final int centerX = (CAMERA_WIDTH - this.mFaceTextureRegion.getWidth()) / 2;
 final int centerY = (CAMERA_HEIGHT - this.mFaceTextureRegion.getHeight()) / 2;

 /* Dibujamos la bola en el centro de la pantalla. */
 final Sprite ball = new Sprite(centerX, centerY, this.mFaceTextureRegion);
 scene.getLastChild().attachChild(ball);

 scene.setOnSceneTouchListener(this);
 scene.setTouchAreaBindingEnabled(true);
  
        return scene;
 }

Y añadimos a la clase el ScrollDetector. Con lo cual los Fields se quedan asi

// ===========================================================
 // Fields
 // ===========================================================
  
 private ZoomCamera mCamera;
 private Texture mTexture;
 private TextureRegion mFaceTextureRegion;
 private SurfaceScrollDetector mScrollDetector;

Vamos un momento a la vista debug y definimos dos vistas para poder filtrar la información de Logcat y entender un poco lo que está pasando. Creamos dos Filtros, uno para "AndEngineTest" y otro para "AndEngine".

Ahora ya tenemos el entorno preparado para ver un poco de información de lo que pasa por debajo con lo que hemos escrito. Debugeamos el proyecto (F11), pasamos a la vista debug y pinchamos con el dedo en la pantalla y lo movemos hacia arriba y hacia abajo. Ésto es lo que vemos en la pantalla de debug...

Ahora mismo tenemos los fuentes del Main.java así:

package com.pruebas.andengine;

import org.anddev.andengine.engine.Engine;
import org.anddev.andengine.engine.camera.ZoomCamera;
import org.anddev.andengine.engine.options.EngineOptions;
import org.anddev.andengine.engine.options.EngineOptions.ScreenOrientation;
import org.anddev.andengine.engine.options.resolutionpolicy.RatioResolutionPolicy;
import org.anddev.andengine.entity.scene.Scene;
import org.anddev.andengine.entity.scene.Scene.IOnSceneTouchListener;
import org.anddev.andengine.entity.scene.background.ColorBackground;
import org.anddev.andengine.entity.sprite.Sprite;
import org.anddev.andengine.entity.util.FPSLogger;
import org.anddev.andengine.input.touch.TouchEvent;
import org.anddev.andengine.input.touch.detector.ScrollDetector;
import org.anddev.andengine.input.touch.detector.SurfaceScrollDetector;
import org.anddev.andengine.input.touch.detector.ScrollDetector.IScrollDetectorListener;
import org.anddev.andengine.opengl.texture.Texture;
import org.anddev.andengine.opengl.texture.TextureOptions;
import org.anddev.andengine.opengl.texture.region.TextureRegion;
import org.anddev.andengine.opengl.texture.region.TextureRegionFactory;
import org.anddev.andengine.ui.activity.BaseGameActivity;

import android.util.Log;

public class Main extends BaseGameActivity implements IScrollDetectorListener,
  IOnSceneTouchListener {
 // ===========================================================
 // Constants
 // ===========================================================
 static final int CAMERA_WIDTH = 480;
 static final int CAMERA_HEIGHT = 320;

 private static final String TAG = "AndEngineTest";

 // ===========================================================
 // Fields
 // ===========================================================

 private ZoomCamera mCamera;
 private Texture mTexture;
 private TextureRegion mFaceTextureRegion;
 private SurfaceScrollDetector mScrollDetector;

 // ===========================================================
 // Constructors
 // ===========================================================

 // ===========================================================
 // Getter & Setter
 // ===========================================================

 // ===========================================================
 // Methods for/from SuperClass/Interfaces
 // ===========================================================

 @Override
 public void onLoadComplete() {
  // TODO Auto-generated method stub

 }

 @Override
 public Engine onLoadEngine() {
  this.mCamera = new ZoomCamera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT);
  return new Engine(new EngineOptions(true, ScreenOrientation.LANDSCAPE,
    new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT),
    this.mCamera));
 }

 @Override
 public void onLoadResources() {
  this.mTexture = new Texture(64, 64,
    TextureOptions.BILINEAR_PREMULTIPLYALPHA);
  this.mFaceTextureRegion = TextureRegionFactory.createFromAsset(
    this.mTexture, this, "gfx/ui_ball_1.png", 0, 0);

  this.mEngine.getTextureManager().loadTexture(this.mTexture);

 }

 @Override
 public Scene onLoadScene() {
  this.mEngine.registerUpdateHandler(new FPSLogger());

  final Scene scene = new Scene(1);
  scene.setBackground(new ColorBackground(0, 0, 0.8784f));
  scene.setOnAreaTouchTraversalFrontToBack();

  this.mScrollDetector = new SurfaceScrollDetector(this);
  this.mScrollDetector.setEnabled(true);

  final int centerX = (CAMERA_WIDTH - this.mFaceTextureRegion.getWidth()) / 2;
  final int centerY = (CAMERA_HEIGHT - this.mFaceTextureRegion
    .getHeight()) / 2;

  /* Dibujamos la bola en el centro de la pantalla. */
  final Sprite ball = new Sprite(centerX, centerY,
    this.mFaceTextureRegion);
  scene.getLastChild().attachChild(ball);

  scene.setOnSceneTouchListener(this);
  scene.setTouchAreaBindingEnabled(true);

  return scene;
 }

 @Override
 public void onScroll(ScrollDetector pScollDetector, TouchEvent pTouchEvent,
   float pDistanceX, float pDistanceY) {
  Log.d(TAG, "Scroll {x:" + pDistanceX + ", y: " + pDistanceY + "}");
 }

 @Override
 public boolean onSceneTouchEvent(Scene pScene, TouchEvent pSceneTouchEvent) {
  this.mScrollDetector.onTouchEvent(pSceneTouchEvent);
  return true;
 }

 // ===========================================================
 // Methods
 // ===========================================================

 // ===========================================================
 // Inner and Anonymous Classes
 // ===========================================================
}

En la pestaña "AndEngine" vemos mensajes del AndEngine, inicialización y muestra cada cierto tiempo los FPS (escenas dibujadas por segundo). En la pestaña "AndEngineTest" vemos lo que hemos colocado en el método onScroll().

En mi caso he movido el dedo primero arriba y luego hacia abajo verticalmente, y me muestra mensajes que lo corroboran, primero mensajes de scroll con pocas variaciones en el eje X y variaciones positivas en el eje Y, y luego cambiando a variaciones negativas en el eje Y.

Ya estamos detectando el movimiento del dedo sobre la pantalla y AndEngine nos brinda clases para hacernos más sencillo todo ésto. Ahora vamos a ver como podemos mover la cámara según el scroll, y como definimos los límites.

Controlando la cámara

Ahora ponemos el siguiente código en nuestro método onScroll()

@Override
 public void onScroll(ScrollDetector pScollDetector, TouchEvent pTouchEvent,
   float pDistanceX, float pDistanceY) {
  this.mCamera.offsetCenter(-pDistanceX, -pDistanceY);
 } 

Ejecutamos y podemos mover la cámara con el dedo, y se mueve fluidamente, pero no tiene límites, se va hasta donde quiera, y lo que me interesa en este caso es que la pantalla tenga unas dimensones determinadas. Para ello tocamos un poco el método onLoadEngine() y solucionado.

@Override
 public Engine onLoadEngine() {
  this.mCamera = new ZoomCamera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT);  
  final int alturaTotal = CAMERA_HEIGHT*3;
  this.mCamera.setBounds(0, CAMERA_WIDTH, 0, alturaTotal);
  this.mCamera.setBoundsEnabled(true);
  return new Engine(new EngineOptions(true, ScreenOrientation.LANDSCAPE,
    new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT),
    this.mCamera));
 }

Bueno, pues con ésto terminamos la segunda parte la introducción a AndEngine. En la próxima lección intentaré poner un fondo de cesped en con una textura, pintar los jugadores desde un xml y cierta interacción de los jugadores.

Otras partes del tutorial:

28 comentarios:

EphramDoyle dijo...

No me funciona correctamente. He repetido todo 3 veces y no lo entiendo..

El proyecto no muestra ningún error, compila y ejecuta el apk correctamente en el emulador, muestra el fondo azul y la pelota.

Pero al hacer clic en la pantalla para mover, no se mueve nada y en el debug me mostraba source not found, pero utilice tu tutorial (gran ayuda) y ahora dice esto:
http://img219.imageshack.us/i/pantallazoum.png/

Además en el modo debug de los filtros que nombras solo 1 se llena.

Angel dijo...

He comenzado desde el principio todo, tienes razon falta una cosa, meter la variable private SurfaceScrollDetector mScrollDetector; en la clase Main. Ya lo he corregido en el articulo.

Una vez hecho eso funciona, lo primero que se me viene a la cabeza, igual falla en esta linea:
this.mFaceTextureRegion = TextureRegionFactory.createFromAsset(
this.mTexture, this, "gfx/ui_ball_1.png", 0, 0);

has comprobado que tienes la imagen en assets/gfx/ui_ball_1.png?

Te aseguro que con el código que hay del archivo completo funciona todo perfectamente. Si no es eso, dimelo y te digo como podemos encontrarlo.

Un saludo.

EphramDoyle dijo...

La variable "private SurfaceScrollDetector mScrollDetector;" ya la tenia insertada dentro del main cuando seguí el tutorial.
Y la imagen está en su sitio por eso se muestra cuando cargo la aplicación.

El proyecto carga bien, inicia el terminal y muestra el fondo con la pelota, pero si toco hago clic con el raton en el fondo falla.

Voy a probar de cambiar la libreria de AndEngine la que descargué de ellos ocupa 818KB por la tuya de 1,5MB.

EphramDoyle dijo...

¡Solucionado! La pelota se mueve!!

Perdona era error mio. Estaba utilizando una version de AndEngine antigua o incompleta. He utilizado la que has nombrado en el tutorial "Introducción a AndEngine (Parte |) y me ha funcionado.

Pero al hacer "build path" de tu jar me daba problemas de duplicidad "AndroidManifest.xml". Así que he eliminado del andengine.jar el fichero AndroidManifest.xml he vuelto a crear el proyecto, crear lib, copiar el nuevo andengine.jar editado, buildpath, etc etc y todo listo!

Tengo un par de dudas:
¿En que vista tienes el emulador? ¿Y que resolución?
En mi caso con todo el código aplicado, incluyendo el limite de pantalla hace lo siguiente:
http://img580.imageshack.us/img580/3334/capturafc.png
¿Es correcto que haga eso?

Mañana revisaré el código con calma para intentar que la pelota no salga de la pantalla.

saludos!

Angel dijo...

Bien!!

Yo estoy haciendo las pruebas con un terminal orange boston, que no es ninguna maravilla, funciona a 480×320, y me funciona todo correctamente con la pelota en el centro de la pantalla. Tengo esto un poco avanzado, a ver si esta noche me da tiempo a subir otro.

Un saludo.

Eduardo dijo...
Este comentario ha sido eliminado por el autor.
Eduardo dijo...

cual es la diferencia entre las dos librerias andengine.jar? aparte del peso... yo tambien tenia la de 800 kb y la tuve que cambiar por la tuya de 1.5 mb, de donde sacaste tu libreria?

Angel dijo...

Pues la bajé de los foros de AndEngine. La que yo estoy usando es exactamente esta:

http://www.mediafire.com/?sz7xjjohbk3vo6o

Eduardo dijo...

Voy a utilizar la libreria del tu enlace, ya te digo como va, gracias por la ayuda y los tutoriales.

Unknown dijo...

Aquí tenéis las librerías:

http://wiki.andengine.org/AndEngine_Jars

Muy buen tutorial Angel. En el apartado Introducción a AndEngine colocaría algunos enlaces a la web oficial. ;)

Aetsu dijo...
Este comentario ha sido eliminado por el autor.
CAF dijo...
Este comentario ha sido eliminado por el autor.
Angel dijo...

Basicamente, con el nuevo andengine haz estos cambios en los fuentes->

TextureRegionFactory - > BitmapTextureAtlasTextureRegionFactory
ExternalStorageFileTextureSource -> ExternalStorageFileBitmapTextureAtlasSource
Texture -> BitmapTextureAtlas
BuildableTexture -> BuildableBitmapTextureAtlas

Angel dijo...

Porqué eliminais los comentarios?

Gerard dijo...

Me acabo de enganchar a vuestro tutorial de AndEnfine, creo que la idea esta bien, pero creo que falta explicar un poco mas profundamente cada clase. "tocar un poco esto o aquello" a mi me confunde un poco. Quizás a lo mejor con algo de explicación en el código a modo de comentarios estaría bien.

PaBlOoOo!!!!!!!!!!!! dijo...

Tiene alguna importancia, si el Target lo hago en Android 2.2 ??? Saludos

PaBlOoOo!!!!!!!!!!!! dijo...

Estimado, me resulta todo ok, solo al realizar el DEBUG, me dice en el log "Necesita lasa llaves para ser enviado", eso me lo dice en el LOG ???

Jw0rmC dijo...

funciona bien con android 2.2. gracias por el tuto. no habia vist los comntarios, acerca de lo del nuevo engine,como cree mi jar con los sources tube problemas pero los solucione. exelente tutorial. Saludos

Marvin_666 dijo...

Hola excelente tutorial sigue así...Tengo un problema al intentar mover la aplicación me manda un msj y se cierra, se deberá al emulador??

owei dijo...

el createFromAsset pone que ese comando no existe :S

Ozuno dijo...

Me sale este error y no se por que Cannot instantiate the type Texture

Jano dijo...

No te funciona por cambios en el AndEngine, ahora Texture debe ser cambiado por BitmapTextureAtlas.

Lo que no veo claro es con qué reimplementar el supuestamente caduco "createFromAsset"

Cristian dijo...

hola, están muy interesantes los tutoriales y te agradezco mucho el aporte, pero resulta que tengo un pequeño problema, en el método onLoadScene() cuando haces:

scene.getLastChild().attachChild(ball);

por algun motivo no encuentra el metodo getLastChild() me dice: "The method getLastChild() is undefined for the type Scene".

Tengo todos los imports igual que tu y nada, copie y pegue tu codigo y muestra el mismo error.

Me pregunto si sera un cambio en el AndEngine, en tal caso que metodo debe de ir, ya que no tengo ni la menor idea de que hace el metodo "getLastChild()" como para buscar su semejante

Cristian dijo...

hahahaha XD
ya no, parece que si era el AndEngine, use el que pusiste en un post de arriba y me sirvió.
de nuevo gracias por los tutoriales y a continuar con los otros XD

bluesun88 dijo...

yo tengo una duda cuando implemento basegameactivity no me salen las clases con @Override y se lo pongo me da error que hago mal

Evol Morell dijo...

Hola... siento molestar con estas pavadas. La aplicación me muestra el error de salida inesperada. En el logCat tengo esto : "Unable to open stack trace file '/data/anr/traces.txt': Permission denied". Según el stacktrace el error se presenta en el this.mScrollDetector.onTouchEvent(pSceneTouchEvent);
y es una excepción de tipo java.lang.NullPointerException

Soy novato en android así que agradezco toda la ayuda posible. Salvando el detalle que aquí expongo... el tutorial está de madres!!

GiTwo dijo...

Aquí los cambios que hice:
en la declaración:
private BitmapTextureAtlas mTexture;

en la función:
public void onLoadResources() {
this.mTexture = new BitmapTextureAtlas(64, 64, TextureOptions.BILINEAR_PREMULTIPLYALPHA);
this.mFaceTextureRegion = BitmapTextureAtlasTextureRegionFactory.createFromAsset(this.mTexture, this, "gfx/ui_ball_1.png", 0, 0);
this.mEngine.getTextureManager().loadTexture(this.mTexture);
}

José Rodríguez dijo...

Eres un crack Angel, hasta el momento fantástico tutorial

Publicar un comentario