Mini Manual de Android

Embed Size (px)

Citation preview

MINIMANUAL DE ANDROID. JUEGOS EN 2D Nota sobre esta guaEsta gua est sin terminar y no se finalizar debido a los continuos cambios que hacen a la API de la misma y a la poca colaboracin por parte de los lectores. Recomiendo encarecidamente utilizar la librera libGDX, la cual parece ser mucho ms seria: http://libgdx.badlogicgames.com/ Muchas gracias a todos los que han colaborado en esta gua!

Enlazar el proyecto con la libreraAndEngine est generado para funcionar con la versin de Android 1.6 y posteriores. A la hora de crear un proyecto, selecciona un API Level mayor o igual a 4. Recuerda que cuanto ms bajo sea ese valor, ms versiones de Android podrn ejecutar tu juego. Bjate la librera de AndEngine desde este enlace: http://dl.dropbox.com/u/30775291/andengine.jar Una vez creado un proyecto en blanco de Android:

1. Desde Eclipse, crear un directorio llamado "lib" dentro del proyecto y poner ah el archivo 2. Haz clic con el botn derecho del ratn sobre el archivo "andengine.jar" del proyecto y"andengine.jar" descargado. selecciona "Build Path" "Add to Build Path". Nos aparecer este archivo tambin dentro de "Referenced Libraries". Si tienes alguna duda, en el siguiente vdeo puedes ver cmo se crea un proyecto desde cero: http://www.youtube.com/watch?v=q-genimutmQ Ya estamos preparados para empezar a programar videojuegos!

La estructura bsica de un juegoPara empezar a programar un videojuego necesitas definir una clase que herede de BaseGameActivity.BaseGameActivity es el corazn del juego. Contiene un Engine y se encarga de crear el SurfaceView en el que el contenido del Engine ser dibujado. Hay exctamente un Engine para un BaseGameActivity. Podrs saltar de un BaseGameActivity a otro utilizando los mecanismos de control de Android: Intents. Veamos un cdigo completamente funcional simplificado al mximo. (Lo nico que hace es mostrar la pantalla en color negro) public class HelloWorld extends BaseGameActivity { private static final int CAMERA_WIDTH = 720; private static final int CAMERA_HEIGHT = 480; private Camera mCamera;

@Override public Engine onLoadEngine() { this.mCamera = new Camera(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() { } @Override public Scene onLoadScene() { final Scene scene = new Scene(1); return scene; } @Override public void onLoadComplete() { } } Por el mero hecho de heredar de BaseGameActivity nos vemos obligados a dar una implementacin de:onLoadEngine, onLoadResources, onLoadScene y onLoadComplete. Veamos qu lgica tenemos que dar a cada mtodo:

onLoadEngine. Nos encargaremos de inicializar el Engine del juego, ya que lo debemos devolver. onLoadResources. Aqu cargaremos las imgenes, msica, sonidos, fuentes, etc. onLoadScene. Aqu prepararemos todos los objetos que entraran en juego. onLoadComplete. No he visto ningn ejemplo que lo utilice todava. Supongo que se ejecutar una vez haya finalizado el mtodo anterior. Vamos a analizar el cdigo anterior... Lo primero que definimos son dos constantes que contendrn el ancho (CAMERA_WIDTH) y alto (CAMERA_HEIGHT) de la pantalla de nuestro juego, as como la variable de instancia que har referencia a la cmara. private static final int CAMERA_WIDTH = 720; private static final int CAMERA_HEIGHT = 480; private Camera mCamera; Lo bueno de programar un juego en OpenGL es que podemos definir el alto y ancho que queramos. Ser OpenGL quien lo reescale para que entre en la pantalla de nuestro movil manteniendo las proporciones. Si quieres que tu juego ocupe la pantalla por completo y no aparezcan ningunas bandas negras arriba y abajo, o en los laterales, asegrate que ests indicando un alto y ancho proporcional a la resolucin de tu pantalla. (Podemos complicarlo un poco ms, pero ahora no merece la pena.)

Veamos ahora qu se hace en onLoadEngine. @Override public Engine onLoadEngine() { this.mCamera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT); return new Engine(new EngineOptions(true, ScreenOrientation.LANDSCAPE, new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT), this.mCamera)); } Creamos la cmara con la resolucin que le habamos dado a la pantalla de juego. Devolvemos el Engine creado, al cual le hemos indicado que el juego se desarrollar sosteniendo el movil en posicin horizontal (ScreenOrientation.LANDSCAPE), adems de indicarle que queremos que la pantalla de juego no se deforme si se ejecuta en un terminal con una tasa de aspecto distinta. (Pueden aparecer bandas negras, como muchas pelculas de cine cuando se visualizan en televisores 4:3) Si el juego se jugara con la pantalla del movil en posicin vertical, indicaramos ScreenOrientation.PORTRAIT. Engine hace que el juego se desarrolle en pequeos pasos discretos de tiempo. El Engine se encarga de sincronizar los refrescos en pantalla y de actualizar la escena, la cual contiene todo el contenido que tu juego est manejando activamente. Normalmente hay una escena por Engine, exceptuando cuando utilizamosSplitScreenEngines. Vamos a terminar con onLoadScene: @Override public Scene onLoadScene() { final Scene scene = new Scene(1); return scene; } Aqu crearemos todos los objetos que aparecern en el juego. En este caso, crearemos una escena vaca y la devolvemos (al Engine). Scene es el contenedor principal de todos los objetos que sern dibujados en la pantalla. Una escena tiene una cantidad especfica de capas (Layers), las cuales contienen una cantidad (fija o dinmica) de entidades (Entity). Hay subclases como CameraScene / HUD / MenuScene que siempre se dibujan en la misma posicin de la escena sin importar la posicin de la cmara. Vamos a darle un poco de color a la escena poniendo de color verde el fondo. Aadimos una nueva lnea aonLoadScene. @Override public Scene onLoadScene() { final Scene scene = new Scene(1); scene.setBackground(new ColorBackground(0f,1f,0f)); return scene; } ColorBackground recibe el total de porcentaje del color rojo (0), del verde (1) y del azul (0). Los porcentajes indicados son valores reales que van de 0 a 1, as que es correcto indicar un

total de 0.5 para el color rojo. (Tambin podemos establecer un valor para el Alpha como cuarto parmetro.) Te recomiendo que hagas unas cuantas pruebas cambiando los valores con valores decimales entre 0 y 1. Podemos establecer como fondo de la escena, del ms simple al ms avanzado:

ColorBackground. Establecemos un color como fondo. EntityBackground. Establecemos una entidad (algo dibujable) como fondo. Como normalmente lo que vamos a utilizar sern sprites, en vez de esta clase, utilizaremos SpriteBackground, que la hereda. SpriteBackground. Para establecer como fondo un sprite (una imagen). RepeatingSpriteBackground. Para establecer un fondo con una imagen que se repite hasta rellenar la pantalla por completo. ParallaxBackground. Para aadir varias capas de fondo y hacer scroll parallax controlado por nosotros. AutoParallaxBackground. Igual que el anterior, slo que estar siempre en movimiento. Espero tratar posteriormente estos elementos con mucho ms detalle.

Dibujar lneas y rectngulosEn AndEngine tenemos dos tipos de entidades primitivas: Line y Rectangle Vamos a modificar el onLoadScene de la seccin "La estructura bsica de un juego" para dibujar unas cuantas lneas y rectngulos. @Override public Scene onLoadScene() { final Scene scene = new Scene(1); // Ponemos el fondo de color blanco scene.setBackground(new ColorBackground(1f,1f,1f)); // Creamos una lnea y un rectndulo final Line linea = new Line(0f, 0f, 720f, 480f, 1); final Rectangle rectangulo = new Rectangle(180f, 60f, 360f, 360f); // Establecemos los colores linea.setColor(1f, 0f, 0f); rectangulo.setColor(0f, 0f, 1f); // Aadimos las primitivas a la capa superior de la escena scene.getLastChild().attachChild(linea); scene.getLastChild().attachChild(rectangulo); return scene; } Para construir la lnea, especificamos la 'x' e 'y' del punto inicial de la lnea y la 'x' e 'y' del punto final, indicando como quinto parmetro el grueso de la lnea en pxeles. (En los HTC G1 todas las lneas que se dibujen tendrn un grueso de 1 pixel ya que el adaptador de grficos del G1 no soporta glLineWidth)

Para crear el rectngulo, indicamos la coordenada de la esquina superior izquierda y el ancho y alto del mismo. Luego establecemos los colores: La lnea con el color rojo y el rectngulo con color azul. Aadimos los objetos a la capa superior de la escena. Muy importante! El orden en que los aadamos ser el orden en el que se dibujarn. Esto ser as mientras no toquemos la propiedad Z de los objetos en la escena. Prueba a cambiar el orden para que primero se dibuje el rectngulo y vers como la lnea ya no aparece cortada. Otra prueba que puedes hacer es: Dibujando primero la lnea y luego el rectngulo, hacer el rectngulo transparente al 50%. Esto lo logrars estableciendo el valor Alpha: // Puedes establecer el color y el valor alpha en una sola lnea. rectangulo.setColor(0f, 0f, 1f, 0.5f); // o establecer el color y luego el valor del alpha rectangulo.setColor(0f, 0f, 1f); rectangulo.setAlpha(0.5f); Gracias al mtodo setAlpha puedes cambiar el valor del Alpha cada vez que quieras.

Mostrar textosVamos a ver todo lo relativo a cargar fuentes y mostrar texto en la pantalla de juego. AndEngine nos permite usar una fuente por defecto o una fuente personalizada cargndola de un archivo .ttf. Veamos rpidamente como presentar un texto con el tipo de letra por defecto y pasaremos a cargar fuentes de archivos ttf que nos ser mas til en la creacin de juegos. Para empezar debemos definir 2 variables: private Texture mFontTexture; private Font mFont; Creamos una variable para la textura que ir en la memoria (esto se ver ms profundamente en Texturas e Interpolacin) y una variable para la fuente. En el mtodo onLoadResources() creamos la textura y la fuente, y la cargamos en el Engine.

//Creamos la textura de la fuente y la fuente this.mFontTexture = new Texture(256, 256, TextureOptions.BILINEAR_PREMULTIPLYALPHA); this.mFont = new Font(this.mFontTexture, Typeface.create(Typeface.DEFAULT, Typeface.BOLD), 32, true, Color.BLACK); //Las cargamos en el Engine this.mEngine.getTextureManager().loadTexture(this.mFontTexture); this.mEngine.getFontManager().loadFont(this.mFont);

En la creacin de la textura para la fuente aparece una serie de declaraciones que veremos ms adelante, no os preocupis. Cuando creamos la fuente le decimos la textura que debe usar para cargarla en memoria, que use el tipo por defecto, negrita, de tamao 32, suavizado (true), y color negro. Posteriormente cargamos la textura y la fuente en el Engine. En el mtodo onLoadScene() imprimiremos nuestro texto en la pantalla. Primero definiremos la posicin

final Text textTitulo = new Text(100, 50, this.mFont, "Titulo Juego", HorizontalAlign.Center); scene.getLastChild().attachChild(textTitulo);

Primero definimos el texto a imprimir textTitulo, posicionandolo en la pantalla, definiendo el tipo de letra a usar (ahora mismo solo tenemos default), el texto a imprimir y la alineacin. En la siguiente linea lo incluimos en la escena. Esta manera de definir el texto tiene un grave problema, la localizacin. Si queremos publicar nuestro juego en varios idiomas debemos usar una caracterstica muy poderosa de Android. Separar el texto del cdigo usando Strings.xml. Modificamos el cdigo anterior para usar esto

final Text textTitulo = new Text(100, 50, this.mFont, R.string.titulo_juego, HorizontalAlign.Center); scene.getLastChild().attachChild(textTitulo);

Evidentemente debemos aadir una entrada en nuestro archivo Strings.xml que se encuentra en "res>values". AndEngineMinimalExample Titulo Juego

Cargar fuentes desde archivos ttfVamos a repetir el proceso pero esta vez vamos a usar un tipo de letra predefinida. Para eso necesitamos primero descargarnos una fuente, usaremos una del proyecto AndEngine "Droid.ttf". En el directorio de nuestro proyecto assets crearemos una carpeta con el nombre "font" donde copiaremos la fuente descargada. Como en el caso anterior empezaremos definiendo las variables necesarias: // Variables para cargar la fuente private Texture mDroidFontTexture; private Font mDroidFont; //Tamao del texto que vamos a usar private static final int FONT_SIZE = 24; Igual que antes en el mtodo onLoadResources() creamos la textura y la fuente, y la cargamos en el Engine. Pero esta vez lo haremos de la siguiente forma:

//le decimos al sistema donde debe buscar las fuentes dentro del //directorio Asset FontFactory.setAssetBasePath("font/"); //Cargamos fuentes de texto

this.mDroidFontTexture = new Texture(256,256, TextureOptions.BILINEAR_PREMULTIPLYALPHA); this.mDroidFont = FontFactory.createFromAsset(mDroidFontTexture, this, "Droid.ttf", FONT_SIZE, true, Color.BLACK); //Las cargamos en el Engine this.mEngine.getTextureManager().loadTexture(this.mDroidFontTexture); this.mEngine.getFontManager().loadFont(this.mDroidFont);Debemos indicarle el directorio desde el que vamos a cargar las fuentes, en nuestro caso font. La creacion de la textura es igual que antes, lo que se modifica es la carga de la fuente, en este caso usamosFontFactory.createFromAsset en vez de new font. Le pasamos la textura donde ir en memoria, el archivo que se va a usar (debe contenerse en asset/font), le pasamos el tamao, suavizado (true), y el color negro. En dos lineas siguientes cargamos tanto la textura como la fuente en el Engine igual que en el caso anterior. AndEngine nos permite cargar en el Engine varias fuentes a la vez, veamos un ejemplo con dos fuentes: // creamos las dos texturas por separado this.mDroidFontTexture = new Texture(256, 256, TextureOptions.BILINEAR_PREMULTIPLYALPHA); this.mKingdomFontTexture = new Texture(256, 256, TextureOptions.BILINEAR_PREMULTIPLYALPHA); // cargarmos las dos fuentes por separado FontFactory.setAssetBasePath("font/"); this.mDroidFont = FontFactory.createFromAsset(this.mDroidFontTexture, this, "Droid.ttf", FONT_SIZE, true, Color.BLACK); this.mKingdomFont = FontFactory.createFromAsset(this.mKingdomFontTexture, this, "KingdomOfHearts.ttf", FONT_SIZE + 20, true, Color.BLACK); // cargamos en el engine las dos texutras y las fuentes a la vez this.mEngine.getTextureManager().loadTextures(this.mDroidFontTexture, this.mKingdomFontTexture); this.mEngine.getFontManager().loadFonts(this.mDroidFont, this.mKingdomFont); Solo cambia en la "s" de los procesos. Continuamos con una sola fuente. En el mtodo onLoadScene() imprimiremos nuestro texto en la pantalla, igual que antes usaremos el archivo externo Strings.xml para separar el texto del cdigo.

final Text textTitulo = new Text(100, 50, this.mDroidFont, R.string.titulo_juego, HorizontalAlign.Center); scene.getLastChild().attachChild(textTitulo);

Vamos a ponerlo todo junto para que quede ms claro.

public class HelloWorld extends BaseGameActivity { // Constantes private static final int CAMERA_WIDTH = 720; private static final int CAMERA_HEIGHT = 480; private static final int FONT_SIZE = 48; // Variables private Camera mCamera;

private Font mDroidFont; private Texture mDroidFontTexture; @Override public Engine onLoadEngine() { this.mCamera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT); return new Engine(new EngineOptions(true, ScreenOrientation.LANDSCAPE, new RatioResolutionPolicy(C AMERA_WIDTH, CAMERA_HEIGHT), this.mCamera)); } @Override public void onLoadResources() { /* The custom fonts. */ this.mDroidFontTexture=new Texture(256, 256, TextureOptions.BILINEAR_PREMULTIPLYALPHA); FontFactory.setAssetBasePath("font/"); this.mDroidFont = FontFactory.createFromAsset(this.mDroidFontTexture, this, "Droid.ttf", FONT_SIZE, true, Color.BLACK); this.mEngine.getTextureManager().loadTexture(this.mDroidFontTexture); this.mEngine.getFontManager().loadFont(this.mDroidFont); } @Override public Scene onLoadScene() { this.mEngine.registerUpdateHandler(new FPSLogger()); final Scene scene = new Scene(1); scene.setBackground(new ColorBackground(0.09804f, 0.6274f, 0.8784f)); final IEntity lastChild = scene.getLastChild(); lastChild.attachChild(new Text(150, 30, this.mDroidFont, getString(R.string.titulo_juego))); return scene; } @Override public void onLoadComplete() { } }Hemos aprendido lo ms bsico: cargar las fuentes e imprimirlas en pantalla, pero para realizar un juego necesitamos hacer algo ms con el texto. AndEngine nos permite modificar el texto (por ejemplo un contador de vidas o puntos de la partida) animarlo, moverlo por la pantalla, hacerlo girar, y aplicarle efectos (contorno).

Modificar texto

// TODO: CONTINUAR...

Texturas e InterpolacinTexture es una "imagen" en la memoria del chip grfico (las imgenes en disco se cargarn en esta zona de memoria). En OpenGL el ancho y alto de una textura tiene que ser potencia de 2 (...32, 64, 128, 256, 512, 1024, etc). Aunque en los ejemplos tiendo a definir la textura cuadrada, no tiene por qu ser as. Puedes tener una textura de 128x32, 32x256, etc. Por ejemplo, el HTC G1 soporta como mucho, una textura de 1024x1024 (Aunque puedes tener ms de una textura a la vez). Intentar utilizar texturas ms grandes en este dispositivo provocarn errores en el juego. Recuerda que la primera tarjeta aceleradora grfica para PC slo soportaba texturas de 256x256. A pesar de esto Quake 2 se vea bastante bien. No? Mi recomendacin es que no pases del valor 1024 para establecer el ancho o alto de tus texturas. Un parmetro muy importante que hay que indicar a la hora de crear un objeto Texture es un valor TextureOptions que indicar qu mtodo se utilizar para redimensionar las imgenes que tenemos dentro de la textura. (Para no complicar las cosas, dejaremos esa explicacin). Tienes para elegir los siguientes modos:

NEAREST: La imagen aparecer pixelada. Es mucho ms rpido, pero la imagen tiene menor calidad. Original:

Ampliada con NEAREST:

BILINEAR: La imagen aparece ms difuminada. Es un poco ms lento, pero no vers pxeles en la misma. Original:

Ampliada con BILINEAR:

REPEATING: Si la textura es de 32x32 y tiene que dibujarse sobre una superficie de 64x64, veremos 4 imgenes rellenando ese hueco. Como si pusiramos la misma imagen una y otra vez repitindola. Este modo no funciona con los Sprites normales, as que por ahora, vamos a ignorarlo. Cuando utilizas imgenes que contienen valores alpha (transparencia) puedes encontrarte con que te aparezcan "halos" que rodean los bordes de las mismas. Aqu es donde entra en juego la tcnica "premultiplied alpha", la cual corrige ese problema. Intenta evitar utilizar "premultiplied alpha" a menos que veas cosas raras en los bordes de tus imgenes, como sobras o halos resplandecientes que no deberan estar. Si quieres una interpolacin bilineal, estableces: TextureOptions.BILINEAR Si quieres una interpolacin bilineal con "premultiplied alpha", estableces: TextureOptions.BILINEAR_PREMULTIPLYALPHA Lo mismo ocurre si quieres interpolacin NEAREST con o sin "premultiplied alpha". Es cosa tuya explorar los valores que puedes elegir de TextureOptions. Para finalizar, decir que TextureOptions.DEFAULT = TextureOptions.NEAREST_PREMULTIPLYALPHA

Cargar los grficos en memoriaSi las texturas tienen dimesiones en potencia de dos... Entonces no puedo tener en mi juego un grfico de 5x5? Me explico: Lo que se hace es definir una textura con un ancho y alto (potencia de dos) que tenga el espacio suficiente para contener todos los grficos de nuestro juego. Podremos tener todos y cada uno de los grficos por separado, pero luego, a la hora de cargarlos en memoria, los meteremos todos en una textura (cuantas menos texturas tengamos, mejor). Un TextureRegion define un rectngulo en una textura y es utilizado por los Sprites para que el sistema sepa que rectngulo de la textura grande contiene la imagen que representa. Un Sprite representa a un elemento del juego: El personaje, el fondo, los enemigos, las explosiones, etc. Podemos crear Sprites sin animacin (Sprite), sprites con varios estados

controlados con cdigo (TiledSprite) o animados (AnimatedSprite). Estos contendrn informacin sobre su posicin (x, y), aceleracin, velocidad, rotacin etc. de tal forma que sepan donde dibujarse y cmo en cualquier momento. Todo esto se ver posteriormente. Ahora nos vamos a centrar en los Sprite normales. Estas son las tres imgenes de 32x32 con las que vamos a trabajar en los prximos ejemplos:

Arrstralas al algn directorio de tu disco para guardarlas. Tenemos dos formas de cargar las imgenes dentro de la textura: 1. Nosotros tenemos que preocuparnos de poner cada imagen dentro de la textura teniendo en cuenta de que entren todas y no se pongan unas encima de otras. 2. Dejamos que AndEngine las coloque como mejor vea. Utilizar este algoritmo: http://www.blackpawn.com/texts/lightmaps/default.html De lo que no nos libramos es de indicar el tamao suficiente para que la textura pueda albergar todas las imgenes. Recuerda que cuanto ms ajustado definas los tamaos, menos recursos desperdiciars. Vamos a ver cada forma por separado. Sers t el que decida con cual te quedas.

Cargar las imgenes en la textura posicionndolas de forma manualSi tenemos tres imgenes de 32x32, definiremos una textura de 64x64. Recuerda que los valores tienen que ser potencia de dos. Cargaremos las imagenes en memoria (en la Texture) de esta forma:

Mientras utilicemos interpolacin NEAREST no tendremos ningn problema. Si utilizamos interpolacin BILINEAR (de ms calidad) tendremos el problema de que a la hora de ampliar las imgenes, los bordes de estas sern afectados por los colores de las imgenes adyacentes. Si ampliramos la imagen del cesped, nos encontraramos con esto:

Fjate en el borde derecho e inferior. Para evitar esto cuando utilizamos interpolacin BILINEAR, lo que hacemos es cargar las imgenes en la textura de forma que haya una separacin entre cada imagen. Por ahora no he tenido problemas dejando slo un pixel de separacin entre imgenes. Con qu problema nos encontramos ahora? Pues que las dos imgenes y el pixel de separacin suman un ancho de 65 y no entra en una textura de 64, as que el siguiente valor que tendremos que darle a la textura de ancho es de 128. (Lo mismo ocurre para el alto) As quedarn las imgenes cargadas en memoria (en la textura de 128x128):

He pintado el fondo con lneas rojas para que te hagas una idea del espacio que estamos desaprovechando en esta textura de 128x128. Una vez tenemos todos los conceptos claros, vamos a ver cmo es el cdigo para cargar estas tres imgenes dentro de la textura en memoria. (Nos centraremos en onLoadResources) Recuerda colocar los tres archivos de imgenes que presentamos antes por separado dentro del directorio de tu proyecto "/assets/gfx". (Si no existiera esta ruta dentro de tu proyecto, crala y pones ah las imgenes.) public class Hello extends BaseGameActivity { private static final int CAMERA_WIDTH = 720; private static final int CAMERA_HEIGHT = 480; Texture mBloquesTexture; TextureRegion mCespedTextureRegion; TextureRegion mCajaTextureRegion; TextureRegion mCaracajaTextureRegion; private Camera mCamera; @Override public Engine onLoadEngine() {

this.mCamera = new Camera(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() { // Creamos la textura donde alojar todas las imgenes this.mBloquesTexture = new Texture(128, 128, TextureOptions.BILINEAR_PREMULTIPLYALPHA); // Establecemos la ruta dentro del "assets" donde estn las imgenes TextureRegionFactory.setAssetBasePath("gfx/"); // Definimos donde cargar cada imagen dentro de la textura this.mCespedTextureRegion = TextureRegionFactory.createFromAsset( this.mBloquesTexture, this, "backgroundgrass.png", 0, 0); this.mCajaTextureRegion = TextureRegionFactory.createFromAsset( this.mBloquesTexture, this, "box.png", 32, 0); this.mCaracajaTextureRegion = TextureRegionFactory.createFromAsset( this.mBloquesTexture, this, "facebox.png", 0, 32); // Cargamos las imgenes en memoria (dentro de la textura) this.mEngine.getTextureManager().loadTextures(this.mBloquesText ure); } @Override public Scene onLoadScene() { final Scene scene = new Scene(1); scene.setBackground(new ColorBackground(1f,1f,1f)); return scene; } @Override public void onLoadComplete() { } } Fjate en la definicin de las variables que van a referenciar la textura (Texture) y las que contendrn informacin sobre donde estn las imgenes dentro esta textura (TextureRegion). Texture mBloquesTexture; TextureRegion mCespedTextureRegion; TextureRegion mCajaTextureRegion; TextureRegion mCaracajaTextureRegion; El mtodo onLoadResources es en el que haremos la carga de imgenes, sonidos, msica, fuentes, etc. Lo primero que hacemos es crear un Texture de 128x128 con interpolacin bilineal y con "premultiplied alpha". La referenciaremos desde la variable mBloquesTexture. Aqu cargaremos las tres imgenes organizadas tal y como hemos especificado.

this.mBloquesTexture = new Texture(128, 128, TextureOptions.BILINEAR_PREMULTIPLYALPHA); Mediante la siguiente lnea establecemos desde donde se cargarn las imgenes dentro del directorio "assets". Se recomienda poner aqu todas las imgenes que no requieran de "Localizacin" (que no dependan del idioma). La ruta de carga por defecto es el directorio "assets". TextureRegionFactory.setAssetBasePath("gfx/"); A continuacin establecemos donde se cargar cada imagen dentro de la textura y guardaremos una referencia a la misma como TextureRegion en mCespedTextureRegion, mCajaTextureRegion ymCaracajaTextureR egion. Fjate que la primera la cargamos en la posicin [0,0] de la textura. La segunda en la posicin [33,0] (recuerda que estamos dejando un pixel de separacin entre imgenes). La tercera en la posicin [0,33]. TextureRegionFactory.createFromAsset requiere como primer parmetro, la textura (memoria de vdeo) en la que se cargar la imagen, luego el Context, el nombre del archivo, la coordenada 'x' y la coordenada 'y' donde querremos situar la imagen. (Conociendo la 'x' y la 'y', y el ancho y alto de la imagen que queremos cargar, sabremos el area exacta que ocupar en la textura.) Si queremos cargar un Drawable desde el directorio "/res/drawable", utilizaremos:TextureRegionFactory.createFromResource(pTexture, pContext, pDrawableResourceID, pTexturePositionX, pTexturePositionY) this.mCespedTextureRegion = TextureRegionFactory.createFromAsset( this.mBloquesTexture, this, "backgroundgrass.png", 0, 0); this.mCajaTextureRegion = TextureRegionFactory.createFromAsset( this.mBloquesTexture, this, "box.png", 33, 0); this.mCaracajaTextureRegion = TextureRegionFactory.createFromAsset( this.mBloquesTexture, this, "facebox.png", 0, 33); Una vez tenemos todo definido, hacemos una llamada al TextureManager del Engine pasndole una lista de todas las texturas de las que queramos que inicie la carga. En este caso, slo tenemos mBloquesTexturecomo textura. En el caso de tener ms de una textura (porque no queramos todos los grficos con las mismas opciones de textura, o por agrupar los grficos categorizndolos en texturas genricas o de un nivel de juego especfico), al loadTextures le pasaramos tantos parmetros como texturas tenemos ya que admite (Texture... pTextures). this.mEngine.getTextureManager().loadTextures(this.mBloquesTexture) ; Con esto tenemos las imgenes cargadas en memoria. Antes de ver cmo mostrarlas en la pantalla del mvil te voy a ensear la otra forma de cargar las imgenes dentro de una textura.

Cargar las imgenes en la textura posicionndolas de forma automtica

En el mtodo anterior, para cargar las imgenes en la memoria de vdeo (Texture) hemos tenido que especificar la posicin donde queremos cargarlas dentro de la textura. Vamos a ver ahora una forma de ahorrarnos eso.

La magia de todo esto est en BuildableTexture, que hereda de Texture y la hace un poco ms inteligente ya que sabr ir colocando las imgenes en su interior segn las vaya cargando. Como coment antes, no nos seguimos librando de establecer un ancho y alto lo suficientemente grande para que puedan cargarse todas las imgenes y lo suficientemente ajustado al tamao ideal para no desaprovechar memoria. Veamos cmo cargar las imgenes en la BuildableTexture. Recuerda colocar los tres archivos de imgenes que presentamos antes por separado dentro del directorio de tu proyecto "/assets/gfx". public class Hello extends BaseGameActivity { private static final int CAMERA_WIDTH = 720; private static final int CAMERA_HEIGHT = 480; BuildableTexture mBloquesTexture; TextureRegion mCespedTextureRegion; TextureRegion mCajaTextureRegion; TextureRegion mCaracajaTextureRegion; private Camera mCamera; @Override public Engine onLoadEngine() { this.mCamera = new Camera(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() { // Creamos la textura donde alojar todas las imgenes this.mBloquesTexture = new BuildableTexture( 128, 128, TextureOptions.BILINEAR_PREMULTIPLYALPHA); // Establecemos la ruta dentro del "assets" donde estn las imgenes TextureRegionFactory.setAssetBasePath("gfx/"); // Indicamos las imgenes que queremos cargar this.mCespedTextureRegion = TextureRegionFactory.createFromAsset( this.mBloquesTexture, this, "backgroundgrass.png"); this.mCajaTextureRegion = TextureRegionFactory.createFromAsset( this.mBloquesTexture, this, "box.png"); this.mCaracajaTextureRegion = TextureRegionFactory.createFromAsset( this.mBloquesTexture, this, "facebox.png"); // Le indicamos a la textura que gestione donde cargar cada imagen try { this.mBloquesTexture.build(new BlackPawnTextureBuilder(0));

} catch (final TextureSourcePackingException e) { Debug.e(e); } // Cargamos las imgenes en memoria (dentro de la textura) this.mEngine.getTextureManager().loadTextures(this.mBloquesText ure); } @Override public Scene onLoadScene() { final Scene scene = new Scene(1); scene.setBackground(new ColorBackground(1f,1f,1f)); return scene; } @Override public void onLoadComplete() { }

}

Lo primero que podemos observar es que en vez de Texture, estamos declarando un BuildableTexture. BuildableTexture mBloquesTexture; TextureRegion mCespedTextureRegion; TextureRegion mCajaTextureRegion; TextureRegion mCaracajaTextureRegion; La construccin del objeto mBloquesTexture tiene exactamente los mismos parmetros que tiene el constructor de la clase Texture. En este caso estamos definiendo en memoria de vdeo una zona de 128x128. this.mBloquesTexture = new BuildableTexture( 128, 128, TextureOptions.BILINEAR_PREMULTIPLYALPHA); La instruccin para indicar el directorio donde estn las imgenes no cambia. A continuacin creamos los TextureRegion que contendrn los nombres de los archivos a cargar junto con la posicin de estos archivos cargados dentro de la textura. En este momento no le hemos indicado a la textura que gestione donde colocar cada imagen, as que estos TextureRegion no tienen todava informacin sobre donde estar cargada la imagen a la que referencia. Fjate bien en que esta vez no tenemos que indicar ninguna coordenada ya que BuildableTexture es lo suficientemente inteligente como para ir posicionando las imgenes en la textura segn las va cargando. this.mCespedTextureRegion = TextureRegionFactory.createFromAsset( this.mBloquesTexture, this, "backgroundgrass.png"); this.mCajaTextureRegion = TextureRegionFactory.createFromAsset( this.mBloquesTexture, this, "box.png"); this.mCaracajaTextureRegion = TextureRegionFactory.createFromAsset( this.mBloquesTexture, this, "facebox.png");

En el cdigo siguiente es donde le decimos a la textura que gestione donde poner cada una de las imgenes que se cargarn en ella. A partir de este momento, si todas las imgenes entran correctamente dentro de la textura, todos los TextureRegion contendrn la informacin de posicin de la imagen en la textura. try { this.mBloquesTexture.build(new BlackPawnTextureBuilder(0)); } catch (final TextureSourcePackingException e) { Debug.e(e); } Si obtuvieras un TextureSourcePackingException significa que tienes que hacer la textura ms grande ya que no se pueden cargar todas las imgenes en ella. (Recuerda que las dimensiones de la textura tienen que ir en potencia de dos.) La lnea en la que se cargan todas las imgenes en la textura no cambia con respecto al mtodo manual. this.mEngine.getTextureManager().loadTextures(this.mBloquesTexture) ; Ya conoces las dos formas de cargar las imgenes en memoria. Creo que va siendo hora de mostrar algo en pantalla. No?

Mostrar Sprites en pantallaUn sprite es una imagen que representa a un elemento del juego con una rotacin, unas dimensiones y posicin especfica en la escena del juego. Tenemos tres tipos de sprites: Sprite (estticos),AnimatedSprite (animados) y TiledSprite (construdos con celdas). Nos centraremos en el primero que es el ms bsico. Para el siguiente ejemplo utilizaremos una de las imgenes con las que hemos estado trabajando. En este caso, necesitas poner el archivo "facebox.png" dentro del directorio "/assets/gfx" de tu proyecto Android. Es recomendable acostumbrarse a colocar de forma ordenada los recursos del juego. Este es el cdigo que carga una imagen y la muestra como objeto en pantalla. Lo nico nuevo interesante es lo que est marcado en negrita. public class Hello extends BaseGameActivity { private static final int CAMERA_WIDTH = 720; private static final int CAMERA_HEIGHT = 480; Texture mBloquesTexture; TextureRegion mCaracajaTextureRegion; private Camera mCamera; @Override public Engine onLoadEngine() { this.mCamera = new Camera(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.mBloquesTexture =

re); }

new Texture(32, 32, TextureOptions.BILINEAR); this.mCaracajaTextureRegion = TextureRegionFactory.createFromAsset( this.mBloquesTexture, this, "gfx/facebox.png", 0, 0); this.mEngine.getTextureManager().loadTexture(this.mBloquesTextu

@Override public Scene onLoadScene() { final Scene scene = new Scene(1); scene.setBackground(new ColorBackground(0.09804f, 0.6274f, 0.8784f)); // Creamos el Sprite y lo aadimos a la capa superior de la escena final Sprite face = new Sprite(344, 224, this.mCaracajaTextureRegion); scene.getLastChild().attachChild(face); } return scene;

@Override public void onLoadComplete() { } } Veamos las partes a destacar en el cdigo... Observa como definimos un objeto Texture. Esto quiere decir que el posicionamiento de la imagen en la textura cuando la carguemos en memoria ser manual. (Para cargar una sola imagen con el mtodo de posicionamiento manual escribes mucho menos cdigo) Texture mBloquesTexture; TextureRegion mCaracajaTextureRegion; A la hora de cargar las imgenes, establecemos la textura con un ancho y alto de 32, exctamente igual que el de la imagen a cargar. Por simplicidad tampoco establecemos desde donde se cargarn las imgenes. Por defecto se buscarn en "assets", as que las rutas que especifiquemos sern relativas a este directorio. Observa como en vez de poner "facebox.png" tenemos que poner "gfx/facebox.png" cuando creamos el TextureRegion. Por lo dems, estamos relizando la carga de imgenes en memoria como explicamos en los captulos anteriores. public void onLoadResources() { this.mBloquesTexture = new Texture(32, 32, TextureOptions.BILINEAR); this.mCaracajaTextureRegion = TextureRegionFactory.createFromAsset( this.mBloquesTexture, this, "gfx/facebox.png", 0, 0); this.mEngine.getTextureManager().loadTexture(this.mBloquesTextu re); } Una vez la imagen est cargada en memoria, vamos a ver qu hacemos en onLoadScene para preparar todos los elementos que aparecern en la escena.

Creamos una escena con una sola capa. En el constructor del objeto Scene establecemos el nmero de capas que quieres que tenga. Qu son todos esos nmeros decimales en el constructor de ColorBackground? Para cambiar un poco el color de fondo, lo establezco a un azul muy parecido al color de fondo de escritorio de Windows. Ahora viene lo interesante: Creamos un Sprite pasndole en su constructor la posicin que queremos que tenga en la escena [344, 224] (Esta posicin ser el centro de la pantalla para el ancho y alto de la cmara establecido), y el TextureRegion que hace referencia a la imagen cargada que queremos que tenga esteSprite. Una vez tenemos el Sprite creado, lo aadimos a la escena del juego. El mtodo de instancia getLastChild de Scene devuelve la capa superior de la escena. Para obtener la capa que est abajo del todo utilizaremos el mtodo de instancia getFirstChild de Scene. En este ejemplo, nuestra escena la hemos creado slo con una capa, as que esta ser la que est arriba del todo y la que est abajo del todo. @Override public Scene onLoadScene() { final Scene scene = new Scene(1); scene.setBackground(new ColorBackground(0.09804f, 0.6274f, 0.8784f)); final Sprite face = new Sprite(344, 224, this.mCaracajaTextureRegion); scene.getLastChild().attachChild(face); return scene; } @Override public void onLoadComplete() { }

}

Ejecutamos la aplicacin y veremos cmo aparece el Sprite en el centro de la pantalla.

BackgroundsSon los fondos sobre los que se dibujarn los personajes y objetos del juego. A la hora de dibujar la pantalla de juego, AndEngine dibujar el Background que hayamos especificado antes que ningn otro grfico. Recuerda que depende de la complejidad de la escena, organizaremos los grficos en capas (Layer): La capa que muestra los personajes, la capa que muestra los objetos, la capa de las puntuaciones y el nivel de vida, etc. De hecho, puedes pensar en el Background como el caso especial de la capa que est ms abajo. Teniendo en cuenta que la ltima capa que se dibuja ser la que est ms arriba. Si quisiramos gestionar el fondo de nuestro juego como si fuera una capa ms en la escena y trabajar solo con las capas de la misma, llamaramos al mtodo de instancia setBackgroundEnabled(false) del objetoScene creado. De esta forma, AndEngine har como si no hubiera Background y empezara a dibujar la escena por la capa ms baja. (Esto no nos impide utilizar la capa ms baja para dibujar ah los grficos que formen el fondo de pantalla.) AndEngine nos permite utilizar esta caracterstica, as que tendremos que aprovecharla.

Tal y como hemos visto en ejemplos anteriores, el Background se asocia a la escena de juego actual en el mtodo onLoadScene. Vamos a ver uno por uno los diferentes tipos de fondos que podemos mostrar:

ColorBackground

Nos ser ltil cuando queramos mostrar un fondo de color uniforme. Como ya lo hemos visto en varios ejemplos, nos centraremos en los dems tipos de Backgrounds.

SpriteBackground

Este tipo de Background nos ser util cuando queramos mostrar una imagen esttica en el fondo de la pantalla. Normalmente esta imagen tendr las mismas dimensiones que la cmara que establezcamos para el juego. Es decir, que a la hora de dibujarse ocupe toda el rea de la pantalla. Supongamos que tenemos una imagen cargada en memoria e identificada con un objeto TextureRegion llamado mBackgroundTextureRegion. @Override public Scene onLoadScene() { final Scene scene = new Scene(1); scene.setBackground( new SpriteBackground( new Sprite(0, 0, this.mBackgroundTextureRegion))); return scene; } Como se puede observar en el cdigo anterior, establecemos el Background pasndole unSpriteBackground construdo a partir de un Sprite en el cual establecemos en la posicin [0,0] haciendo referencia a la imagen cargada mBackgroundTextureRegion. Anteriormente hemos hablado de la posibilidad de desactivar esta la caracterstica del Background en la escena. Veamos un ejemplo de cmo se hara visualmente lo mismo sin tratar con un Background. (Trabajando slo con las capas) @Override public Scene onLoadScene() { final Scene scene = new Scene(2); scene.setBackgroundEnabled(false); scene.getFirstChild().attachChild(new Sprite(0, 0, this.mBackgroundTextureRegion)); return scene; } Lo que hemos hecho ha sido:

1. Crear el objeto Scene con dos capas. Puedes crear tantas como te hicieran falta. En este caso 2. Deshabilitar el Background en la escena. 3. Obtener la capa que se encuentra ms abajo de las dos (la primera que se dibuja) y aadiruna para el fondo y otra para todo lo dems, si furamos a dibujar algo mas que el fondo.

un Spritecuya esquina superior izquierda se encontrar en la posicin [0,0] de la escena y que representa la imagen que queremos de fondo de pantalla. Esto es como en todo: Muchos caminos llevan hasta Roma. Cual utilizar? Yo recomiendo utilizar siempre que sea posible un Background (del tipo que ms te convenga) y olvidarse de trabajar los fondos de pantalla dentro de capas. Esta sera una forma de abstraer sutilmente la tarea de dibujar el fondo de la pantalla de las dems tareas de dibujado.

RepeatingSpriteBackground

Este Background nos vendr muy bien cuando queramos mostrar una imagen que se repita tantas veces hasta rellenar la pantalla. (Es til para dibujar agua, suelos de tierra, de cesped, etc) Por ejemplo, imagnate que queremos rellenar el fondo de la pantalla completamente de cesped teniendo como imagen esto: (Para guardarla en disco, arrstrala en cualquier carpeta. Recuerda que para probar los ejemplos, debers incluirla en el proyecto de Android dentro del directorio "/assets/gfx" )

El tamao de esta imagen es de 32x32, as que para rellenar la pantalla completa se tendr que repetir tantas veces como sea necesario. Algo que se repite muchas veces para cubrir un rea especfica se le conoce por el nombre de Textura. Vamos a dejar esta tarea al procesador grfico, ya que la realizar de forma bastante eficiente. Esto quiere decir que en este caso no trabajaremos con Sprites, sino que trabajaremos con la texturas en s. Los valores de las dimensiones de las texturas tienen que ser potencia de dos, y no tienen por qu ser cuadradas. Recuerda que para cargar las imgenes que luego utilizbamos dentro de Sprites, crebamos primero un objeto Texture. Luego crebamos objetos TextureRegion para asignar imgenes (en archivos) a una zona concreta de esa textura y para finalizar le decamos al TextureManager que cargara en la textura todas las imgenes que habamos especificado anteriormente en los TextureRegion. Para este Background slo tenemos que cargar una imagen, que ser la que ocupe por completo la textura que nos servir para rellenar toda la pantalla. Esto lo haremos en onLoadResources cargando la imagen dentro de un objeto RepeatingSpriteBackground. Luego, podemos pasar este objeto al mtodosetBackground del objeto Scene. Veamos un ejemplo completo: public class Hello extends BaseGameActivity { private static final int CAMERA_WIDTH = 720; private static final int CAMERA_HEIGHT = 480; RepeatingSpriteBackground fondo; private Camera mCamera; @Override public Engine onLoadEngine() { this.mCamera = new Camera(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.fondo = new RepeatingSpriteBackground( CAMERA_WIDTH, CAMERA_HEIGHT, this.mEngine.getTextureManager(), new AssetTextureSource(this, "gfx/backgroundgrass.png")); } @Override public Scene onLoadScene() { final Scene scene = new Scene(1); scene.setBackground(this.fondo);

return scene; } @Override public void onLoadComplete() { }

}

A la hora de crear el RepeatingSpriteBackground le pasamos: Las dimensiones. En este caso, como queremos que ocupe la pantalla completa, sern las mismas dimensiones que la cmara. El TextureManager, ya que ser necesario para cargar la textura en memoria. La referencia a la imagen que queremos cargar en la textura. La textura se generar con las mismas dimensiones que la imagen. Para terminar, le indicamos al objeto Scene que queremos utilizar este RepeatingSpriteBackground.

ParallaxBackground// TODO:

AutoParallaxBackground// TODO:

Sprites animados

Hasta el momento hemos visto como podemos mostrar imgenes estticas en nuestro juego. Con esto ya se podra abarcar un buen nmero de videojuegos pero, no nos engaemos, las necesidades de un producto multimedia van ms all. Qu sera de un videojuego sin unas realistas e impactantes animaciones? AndEngine pone a nuestra disposicin la infraestructura necesaria para cargar y ejecutar animaciones y, como vers lo hace de una manera realmente fcil e intuitiva. A efectos normales un sprite animado no deja de ser un sprite, as que es necesario que hayas ledo y entendido qu son los sprites, cmo funcionan y cmo los gestiona AndEngine. A modo de repaso rpido, podemos decir que el ciclo de vida de un sprite se podra resumir en: 1. 2. 3. 4. Carga de la imagen. (Usando una textura como contenedor). Creacin. Manipulacin. (Transformaciones, animaciones externas, animaciones internas, etc...). Muerte. (Liberacin de recursos). Para un sprite animado tambin este ser nuestro guin. Antes de nada vamos a presentar nuestro personaje a animar. Tomaremos una animacin de ejemplo deAndEngine.

Como podrs observar AndEngine hace uso de la animacin por frames. Un frame es la unidad mnima de una animacin, es decir, ser la imagen que se ver en un determinado tiempo. La imagen de ejemplo tiene cuatro frames dispuestos en dos columnas y dos filas. Es importante esta organizacin, puesto que segn sea construiremos nuestro sprite animado de una forma u otra. Como comentario sobre la imagen, no tiene por qu ser una nica imagen para cada sprite. En una misma imagen pueden convivir varios sprites distintos con sus respectivas animaciones. An as, es recomendable que cadasprite tenga su propia zona comn en el fichero, si se decide usar uno nico; es decir, los frames de cadasprite estaran dispuesto de forma consecutiva. Vamos a ver un pequeo ejemplo en el que crearemos un sprite, crearemos una animacin asociada a esesprite y la mostraremos en pantalla (por comodidad slo se muestra la parte del cdigo interesante, obviando importaciones, mtodos no implementados, etc..). public class AndEnginePruebas extends BaseGameActivity { private static final int CAMERA_WIDTH = 320; private static final int CAMERA_HEIGHT = 200; private Camera mCamera; private Texture mTexture; private TiledTextureRegion helicoptero; @Override public Engine onLoadEngine() { mCamera = new Camera(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() { mTexture = new Texture(128, 128, TextureOptions.BILINEAR_PREMULTIPLYALPHA); helicoptero = TextureRegionFactory.createTiledFromAsset(this.mTexture, this, "gfx/helicoptertiled.png", 0, 0, 2, 2); } mEngine.getTextureManager().loadTexture(this.mTexture);

@Override public Scene onLoadScene() {

final Scene scene = new Scene(1); scene.setBackground(new ColorBackground(0.09804f, 0.6274f, 0.8784f)); float centerX = (CAMERA_WIDTH - (this.helicoptero.getWidth() / 2)) / 2; float centerY = (CAMERA_HEIGHT (this.helicoptero.getHeight() / 2)) / 2; final AnimatedSprite heliVolador = new AnimatedSprite(centerX, centerY, this.helicoptero); heliVolador.animate(new long[]{100L, 100L}, 1, 2, true); scene.getLastChild().attachChild(heliVolador); return scene; } @Override public void onLoadComplete() { }

}

La estructura del cdigo es exactamente la misma que la que hemos visto en temas anteriores. De hecho, si no fuera por una nueva clase, el cdigo sera idntico al que vimos cuando tratamos el tema de sprites. Para cargar la imagen de nuestro helicptero, hacemos uso de la factora TextureRegionFactory y de uno de sus mtodos estticos: createTiledFromAsset. Este mtodo (hay varios, os invitamos a que miris el cdigo fuente de la librera para ver las distintas opciones) nos fabrica el contenido de una textura con la imagen, por lo tanto los parmetros ms interesantes sern la textura donde se cargar la imagen (el objeto mTexture), la ruta donde est la imagen a cargar (debe estar en el directorio de recursos /assets/ a no ser que le hayamos indicado a AndEngine otro lugar), la posicin 'x' e 'y' donde cargar esta imagen en la textura y el nmero de columnas y de filas que tiene la imagen que cargamos. (En este caso, tenemos cuatro helicpteros en la imagen organizados en dos filas y dos columnas.) Referenciaremos nuestra imagen con un objeto TiledTextureRegion. Este objeto se utiliza cuando cargamos una imagen formada por muchas imgenes. Crearemos este objeto dndole indicaciones de las filas y columnas que forman la matriz de imgenes. A partir de ese momento, podremos acceder a cada una de las imgenes que contiene de forma independiente. AndEngine automaticamente dividir esta imagen en el nmero de filas y columnas indicados para extraer cada fotograma (frame). La numeracin de cada fotograma ser de izquierda a derecha y de arriba hacia abajo. De esta forma, el fotograma nmero cero hace referencia al helicptero que est detenido (La imagen superior izquierda). El nmero uno (superior derecha) y el nmero dos (inferior izquierda) a diferentes fotogramas del helicptero en funcionamiento, y el forograma nmero tres (inferior derecha) hace referencia al helicptero accidentado. Una vez tenemos la imagen cargada, creamos un objeto AnimatedSprite que representar a nuestro helicptero, pudiendo tener 4 estados. En este caso, aprovechamos el constructor del

objeto para indicarle la posicin donde queremos que aparezca en la escena. (tantos como fotogramas hayamos cargado) En cualquier momento, podemos cambiar la posicin de un Sprite utilizando el mtodo setPosition del mismo. (Tambien aplicable a AnimatedSprite) Finalizamos indicando al helicptero que queremos que se visualice animado llamando al mtodo animate. Esta animacin tendr dos fotogramas que durarn cada uno 100ms. La animacin ir del fotograma nmero 1 al nmero 2 y se repetir indefinidamente o hasta que se llame otra vez al mtodo animate. La base de uso de los sprites animados que ofrece AndEngine es la que hemos visto. Como complemento podemos aadir que podemos detener la animacin de nuestro sprite en cualquier momento con hacer la llamada al mtodo stopAnimation. De este mtodo disponemos de una especializacin que nos permite indicar el frame que queremos que muestre el sprite una vez detenida la animacin en curso. Esto nos puede ser especialmente til para crear transiciones entre distintas animaciones cuando estas depende de entradas del usuario. Adems, el API nos ofrece un mtodo para determinar el estado de la animacin, es decir, saber si nuestrosprite est mostrando una animacin o ya ha finalizado. Para esto ltimo, disponemos del mtodoisAnimationRunning, que nos devuelve un valor lgico: true o false. Por lo tanto, si deseramos saber cundo ha finalizado la animacin de un sprite (por supuesto que no tenga activada la iteracin infinita) podramos preguntar en cada iteracin de nuestro juego si la animacin sigue en curso. Este proceso, puede resultar bastante engorroso, por lo que AndEngine acude a nuestra ayuda para facilitarnos las cosas: listener. Como comentamos al comienzo de esta seccin, la clase AnimatedSprite ofrece en su interfaz una serie de mtodos animate. Dependiendo de las necesidades de nuestro cdigo, algunos nos servirn mejor que otros. De entre esos mtodos, existe un grupo que tiene como parmetro de entrada un interfaz declarado como interfaz interno de AnimatedSprite: IAnimationListener. Mediante la implementacin de este interfaz podemos indicar qu hacer cuando la animacin asociada a unsprite ha finalizado. Como puedes ver, es lo que obtenamos con nuestro interrogatorio a la clase medianteisAnimationRunning, sin embargo al usar este pequeo patrn de diseo nos evitamos el tener que estar pidiendo a la clase que compruebe su estado. Al indicarle nosotros qu tiene que hacer cuando acaba la animacin, le pasamos todo el trabajo (y la responsabilidad) a la clase. Fcil, verdad?. Vamos a ver todo esto que hemos dicho con un pequeo ejemplo. En el ejemplo se mostrar a nuestro helicptero haciendo un vuelo de dos segundos ( 2 frames * 100 ms * 10 iteraciones). Cuando se llegue al fin de la animacin, nuestro helicptero detendr los rotores y descender en cada libre. Cuando llegue al suelo quedar destrozado. La imagen que usaremos en el ejemplo ser la misma que hemos visto en la seccin. Uno de los cambios recientes en AndEngine ha sido la separacin del motor de fsica de los

sprites animados. Si queremos seguir utilizndolo como lo hacamos antes, deberemos crearnos una clase que herede de AnimatedSprite y que contenga una instancia al manejador de fsicas. Entonces slo nos queda exponer los mtodos del manejador de fsicas en nuestra nueva clase. private static class HelicopteroAnimado extends AnimatedSprite { private final PhysicsHandler mPhysicsHandler; public HelicopteroAnimado(final float pX, final float pY, final TiledTextureRegion pTextureRegion) { super(pX, pY, pTextureRegion); this.mPhysicsHandler = new PhysicsHandler(this); this.registerUpdateHandler(this.mPhysicsHandler); } public void setVelocityY(float velocity){ mPhysicsHandler.setVelocityY(velocity); } } Ahora, podemos seguir programando la aplicacin como en anteriores versiones de AndEngine. Eso s, en vez de instanciar AnimatedSprite, deberemos instanciar HelicopteroAnimado. Veamos cmo quedara ahora el mtodo onLoadScene. @Override public Scene onLoadScene() { final Scene scene = new Scene(1); scene.setBackground(new ColorBackground(0.09804f, 0.6274f, 0.8784f)); float centerX = (CAMERA_WIDTH - (this.helicoptero.getWidth() / 2)) / 2; float centerY = (CAMERA_HEIGHT - (this.helicoptero.getHeight() / 2)) / 2; final HelicopteroAnimado heliVolador = new HelicopteroAnimado(centerX, centerY, this.helicoptero); heliVolador.animate(new long[]{100L, 100L}, 1, 2, 10, new AnimatedSprite.IAnimationListener(){ @Override public void onAnimationEnd(final AnimatedSprite sprite) { ((HelicopteroAnimado)sprite).setVelocityY(40.0f); } } ); scene.registerUpdateHandler(new IUpdateHandler() { @Override public void onUpdate(float arg0) { //Si hemos llegado al suelo... crash. if (heliVolador.getY() + heliVolador.getHeight() >= CAMERA_HEIGHT){ heliVolador.setVelocityY(0.0f); heliVolador.animate(new long[]{100L, 100L}, 3, 4, false); } }

@Override public void reset() {} }); scene.getLastChild().attachChild(heliVolador); } return scene;

Slo hemos cambiado el mtodo animate que vamos a usar y hemos registrado en la escena un nuevo actualizador, para que se ejecute en cada iteracin. Como ves, el nuevo mtodo tiene un parmetro adicional, que es la implementacin del interfaz IAnimationListener. Este interfaz dispone nicamente de un mtodo:onAnimationEnd, que es donde debemos indicar qu va a ocurrir cuando la animacin termine su ejecucin. Este mtodo nos ofrece como parmetro de entrada el sprite animado, que recordamos que es de la claseHelicopteroAnimado. Una vez hecho el casting a la misma, le aplicamos un movimiento de cada al helicptero ((HelicopteroAnimado)sprite).setVelocity(40.0f)). Por lo tanto, cuando finalice las diez iteraciones de las que est compuesta nuestra animacin de dosframes, nuestro objeto heliVolador ejecutar el cdigo del mtodo del interfaz: onAnimationEnd. Para finalizar, en el bucle de nuestro "juego", miramos cundo el helicptero llega al suelo (Aprovechndonos de que hemos solicitado que en cada actualizacin del estado del juego se nos notifique en el onUpdate). Cuando esto ocurra (heliVolador.getY() + heliVolador.getHeight() >= CAMERA_HEIGHT), detenemos el movimiento de cada del helicptero (heliVolador.setVelocityY(0.0f)) y mostranos una nueva animacin con nuestro helicptero destrozado. Como es una animacin nica, desactivamos la repeticin de esta.

Interactuando con el juego// TODO: Aqu se listarn todas las formas de interactuar con el juego.

Interactuando con pulsaciones de teclas// TODO: Aqu se explicar cmo detectar pulsaciones de teclas.

Interactuando con la pantalla tactil// TODO: Aqu se explicar cmo detectar pulsaciones en la pantalla. (No multitactil)

Interacciones multitctiles// TODO: Aqu se explicar cmo gestionar cuando hay ms de un dedo en la pantalla.

Utilizando un gamepad virtual// TODO: Aqu se explicar cmo crear un gamepad virtual para manejar el juego.

ColisionesLa Colisin entre dos objetos esta dada por la interseccin de sus reas. Pudiendo muchas veces sobreponerse si no se toma en cuenta correctamente cuando se dio la colisin.

Colisiones usando Geometra Crculo - Crculo Ejm Cuadrado - Cuadrado Ejm

Colisiones usando AndEngine(Box2D)//TODO : por editar

Msica y efectos de sonidoSi consideramos un videojuego como un programa multimedia debemos equiparar el sonido a, por ejemplo, el apartado grfico o a la jugabilidad. La capacidad del sonido para impregnar la vivencia de juego de sensaciones y emociones es similar al mejor de los grficos. Es cierto, sin embargo, que el "sonido" en la programacin para dispositivos mviles siempre se ha considerado prescindible. En un primer momento por las capacidades de los dispositivos y, posteriormente, por el modo de juego al que van dirigidos estos programas. Esto ha cambiado, radicalmente, con la llegada de nuevos dispositivos con capacidades multimedia sonoras de gran calidad y, con el uso masivo de smartphones, el usuario ha cambiado su modo de juego de un modo casual a usar su telfono como consola de videojuegos, por lo que cada vez el usuario es ms exigente con un cuidado apartado sonoro. Con AndEngine podrs dotar a tu juego de toda la potencia de Android a la hora de reproducir sonidos.AndEngine te ofrece una capa por encima de la programacin sonora que el API de Android pone a nuestra disposicin. Te podrs estar preguntando que si slo es una capa, por qu no usar directamente la API deAndroid? La respuesta no es sencilla, sin embargo debes considerar que utilizar la capa de abstraccin deAndEngine permitir hacer la programacin mucho ms uniforme y te ahorrar, muchas veces, el tener que programar tus propias clases de utilidades. As pues, si podemos hacer lo mismo que hace Android, podremos reproducir un amplio abanico de formatos multimedia sonoros: AAC, MP3, Ogg Vorbis (OGG), MIDI y el eterno WAV. Adems de estos formatos, AndEngine incluye una expansin (hace uso de la librera externa XMP) para poder reproducir ficheros MOD (MOD, S3M, IT...). El uso de esto lo veremos en la seccin dedicada a las expansiones de AndEngine. AndEngine divide la capacidad sonora en msica (Music) y efectos de sonido (Sound). Normalmente la msica no se cargar por completo en memoria y se ir reproducciendo en flujo, mientras que los efectos de sonido si estarn cargados en memoria (normalmente tienen un tamao pequeo) para poder hacer un uso rpido de ellos, puesto que requieren un acceso rpido. Sin embargo, como veremos a continuacin, esa distincin es prcticamente transparente para nosotros, puesto que, salvo por el uso de clases de distinto nombre, el uso

de las mismas es casi idntico. Una vez finalizada la pequea introduccin, veamos qu necesitamos hacer para poder usar sonido conAndEngine. Haremos una pequea introduccin literal y luego veremos en un ejemplo todo lo que hemos dicho. Cada vez que queramos usar en nuestra aplicacin sonidos seguiremos este guin:

1. Activar el sonido en AndEngine.2. Cargar los ficheros que contienen la msica o los efectos de sonido. 3. Configurar el sonido: continuidad, repeticiones, volumen... 4. Operaciones sobre el sonido: reproducir, detener, pausar, continuar la reproduccin... Para avisar a AndEngine que vamos a usar sonido debemos activar dos opciones en la claseEngineOptions que, como vimos, es la clase que usamos para construir un objeto de tipo Engine. A estas dos opciones se acceden mediante dos mtodos setters: EngineOptions engineOptions = new EngineOptions([...]); engineOptions.setNeedsMusic(true); engineOptions.setNeedsSound(true); AndEngine utiliza estos dos atributos "activadores" para saber si tiene que construir dos clases de utilidades: MusicManager y SoundManager. Estas dos clases son las encargadas de hablar con la API deAndroid. Aprovechando la referencia que hemos hecho a esas dos clases, vamos a tocar un tema que puede pasar desapercibido, porque realmente no sea necesario modificar, o no. Con AndEngine se puede ejecutar efectos sonidos de forma concurrente. Por defecto, y de forma interna, AndEngine define un nmero mximo de concurrencia en cinco. Por lo tanto, podemos tener en reproduccin msica y cinco efectos de sonido sonando a la vez. En principio, esto es ms que suficiente para la programacin de cualquier juego, sin embargo si por necesidad del proyecto tienes que ampliar ese parmetro, te comentamos cmo poder ampliar ese parmetro. Exceptuando la opcin ms evidente que es modificar la constante en la claseSoundManager, una opcin ms elegante es no usar los atributos activadores de la clase Engine, como hemos hecho en nuestro cdigo ejemplo, y construir nuestro propio SoundManager. Esta clase ofrece un constructor SoundManager(final int pMaxSimultaneousStreams) que recibe como parmetro el nmero de canales concurrentes que queremos tener a nuestra disposicin. En la carga de los archivos, especificaremos el MusicManager o SoundManager del propio Engine, o una referencia al que hayamos creado. Para cargar los archivos sonoros, AndEngine nos ofrece dos factoras para hacerlo, una para msica y otra para efectos sonoros. Cada una de estas factoras disponen de varios mtodos para obtener el archivo de varias formas, como vimos al cargar ficheros grficos. En nuestros ejemplos usaremos la cargar de archivos desde el directorio /assets/, pero tambin puedes hacerlo desde un fichero de recursos Android. Las clases factoras para cargar los archivos son: MusicFactory y SoundFactory. Para cargar un archivo de msica usaremos el mtodo esttico createMusicFromAsset de la

clase MusicFactory y para cargar un archivo de efecto de sonido usaremos el mtodo esttico createSounFromAsset de la claseSoundFactory. Veamos un ejemplo con un corte del cdigo que veremos ms adelante.

Music musica = MusicFactory.createMusicFromAsset(mEngine.getMusicManager(), this, "mfx/50.mid"); Sound helices = helices = SoundFactory.createSoundFromAsset(mEngine.getSoundManager(), this, "sfx/copter.ogg");

Como veis, el cdigo es auto explicativo. Para construir un fichero sonoro usamos el mtodo de la factora que necesitemos, en este caso los ficheros estarn en el directorio /assets/ de nuestro paquete. Estos mtodos, reciben como parmetros el gestor de msica y sonido de nuestra clase Engine (si hubiramos necesitado usar un gestor de sonido propio, como vimos al tratar el tema de la concurrencia de sonidos, este sera el que debemos usar aqu), la propia clase y la ruta relativa a /assets/ donde estn nuestros ficheros. En este ejemplo cargaremos dos ficheros sonoros. Un fichero de msica tipo MIDI que est situado en /assets/mfx/ y con el nombre 50.mid y un fichero de sonido de tipo Ogg Vorbis situado en /assets/sfx/ y de nombrecopter.ogg.

Nota: no es necesario indicar qu tipo de archivo queremos cargar. Android reconocer el tipo de archivo que es por la extensin en el nombre del archivo. Si no es un tipo de archivo que reconoce, te dar una excepcin en la carga o devolver null en la construccin. La configuracin de los sonidos se puede hacer en la carga de estos o durante el bucle de nuestro juego. Entre las operaciones principales tenemos:

Control de volumen: getVolume, setVolume y sus especializaciones en volumen izquierdo y derecho. Nmero de repeticiones: setLoopCount (nmero de repeticiones) y setLooping (repeticin continua). Algunos mtodos estn slo disponible para slo algn tipo de objeto de sonido (Music o Sound). Os invitamos a que miris la API de las distintas clases para ver las pequeas diferencias. Operar sobre los sonidos cargados es igual de sencillo. AndEngine ofrece una serie de mtodos para realizar todas las acciones que podemos necesitar sobre un sonido, ya sea msica o efecto de sonido. Estos mtodos son: play (reproducir), stop (detener la reproduccin completamente), pause (detener la reproduccin y tener la posibilidad de continuarla), resume (continuar la reproduccin en la ltima pausa) y release(descargar el fichero de sonido). Como ocurra con los mtodos de configuracin, existen ciertos mtodos nicos para una u otra clase de sonido (Music o Sound). Por ejemplo, la clase Music ofrece un mtodo para situarte

en una posicin concreta de un fichero de msica (mtodo: seekTo) que en un fichero de sonido no tendra mucha utilidad. Otro mtodo interesante de Music es isPlaying que nos dice si est en ejecucin. Nota: al igual que vimos en el apartado de reproduccin de sprites animados, podemos asociar un listener al objeto de clase Music para que ejecute cdigo cuando finalice la reproduccin. En este caso tendremos que implementar un listener de la API de Android: OnCompletionListener. Para finalizar este apartado, vamos a ver todo lo que hemos dicho en un nico ejemplo. El ejemplo continuar el cdigo que vimos en el apartado de sprites animados. En l, activaremos el apartado sonoro deAndEngine, cargaremos una msica de ambiente (usaremos MID por el menor tamao de estos ficheros en comparacin con OGG) y un par de efectos de sonidos en formato Ogg Vorbis: hlices rotando y un efecto de golpe y rotura de cristales que sonar cuando nuestro helicptero caiga al suelo. Los archivos de sonido son: 50.mid, copter.ogg y crash.ogg. Como siempre, resaltamos el cdigo nuevo en negrita y obviamos partes de cdigo (importaciones, mtodos vacos...) para facilitar la lectura. public class AndEnginePruebas extends BaseGameActivity { private static final int CAMERA_WIDTH = 320; private static final int CAMERA_HEIGHT = 200; private Camera mCamera; private Texture mTexture; private TiledTextureRegion helicoptero; private Music musica; private Sound helices; private Sound crash; private boolean destruido = false; @Override public Engine onLoadEngine() { this.mCamera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT); EngineOptions engineOptions = new EngineOptions(true, ScreenOrientation.LANDSCAPE, new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT), this.mCamera); engineOptions.setNeedsMusic(true); engineOptions.setNeedsSound(true); } return new Engine(engineOptions);

@Override public void onLoadResources() { mTexture = new Texture(128, 128,

TextureOptions.BILINEAR_PREMULTIPLYALPHA); helicoptero = TextureRegionFactory.createTiledFromAsset(this.mTexture, this, "gfx/helicopter_tiled.png", 0, 0, 2, 2); mEngine.getTextureManager().loadTexture(this.mTexture); //Carga de msica y sonido. try { musica = MusicFactory.createMusicFromAsset(mEngine.getMusicManager(), this, "mfx/50.mid"); musica.setLooping(true); helices = SoundFactory.createSoundFromAsset(mEngine.getSoundManager(), this, "sfx/copter.ogg"); helices.setLooping(true); crash = SoundFactory.createSoundFromAsset(mEngine.getSoundManager(), this, "sfx/crash.ogg"); crash.setLooping(false); } catch (IOException ioe) { //No se ha podido cargar el fichero. Realizar las operaciones //oportunas. } } @Override public Scene onLoadScene() { final Scene scene = new Scene(1); scene.setBackground(new ColorBackground(0.09804f, 0.6274f, 0.8784f)); float centerX = (CAMERA_WIDTH - (this.helicoptero.getWidth() / 2)) / 2; float centerY = (CAMERA_HEIGHT (this.helicoptero.getHeight() / 2)) / 2; final HelicopteroAnimado heliVolador = new HelicopteroAnimado(centerX, centerY, this.helicoptero); heliVolador.animate(new long[]{100L, 100L}, 1, 2, 10, new AnimatedSprite.IAnimationListener(){ @Override public void onAnimationEnd(final AnimatedSprite sprite) { helices.stop(); helices.release(); ((HelicopteroAnimado)sprite).setVelocityY(40.0f); } } ); scene.registerUpdateHandler(new IUpdateHandler() { @Override public void onUpdate(float arg0) {

//Si hemos llegado al suelo... crash. if (heliVolador.getY() + heliVolador.getHeight() >= CAMERA_HEIGHT && !destruido) { destruido = true; crash.play(); heliVolador.setVelocityY(0.0f); heliVolador.animate(new long[]{100L, 100L}, 3, 4, false); } } @Override public void reset() {} }); scene.getLastChild().attachChild(heliVolador); return scene; } @Override public void onLoadComplete() { try { Thread.sleep(1000); } catch (InterruptedException e) {} this.musica.play(); this.helices.play(); } // Para ahorrar espacio, no incluiremos aqu la clase HelicopteroAnimado. // La puedes encontrar en el ejemplo del captulo de Sprites animados. } En el ejemplo vemos cmo activamos en uso de sonido al construir el objeto Engine y cmo cargamos los ficheros en el mtodo onLoadResources. Al cargar los archivos debemos capturar la excepcinIOException por si no estn disponibles los ficheros a cargar. La reproduccin de la msica de fondo y las hlices la hacemos en el mtodo onLoadComplete. Que ser justo antes de mostrarse la escena del juego. Nota: algunas veces la carga del archivo se solapa con la reproduccin del mismo. En este caso, ocurre con el sonido de las hlices. Para solucionarlo, hacemos una pequea pausa de un segundo antes de la reproduccin. Esto es slo necesario la primera vez y slo si queremos reproducir sonidos inmediatamente tras la carga de la escena. Para ver lo que queremos decir, puedes eliminar la pausa de un segundo que hacemos mediante Thread.sleep(...) y lanzar el ejemplo. Observars que el sonido de las hlices no se reproducir. (Si alguien sabe cmo evitar este problema, que nos lo comente.)

Mejorar el rendimiento del juegoEn esta seccin veremos una serie de mejoras que puedes realizar en tu proyecto para obtener un mejor rendimiento.

Evitar dibujar el fondo de la ventana

Cuando programamos videojuegos en Android, por lo general, no necesitaremos que se dibuje el fondo (a nivel de sistema) de la ventana de la actividad. Como los grficos de nuestro juego se dibujarn encima ocultndolo, no merece la pena gastar tiempo de proceso en esa tarea. Por consiguiente, conseguiremos una mayor tasa de refrescos de pantalla por segundo. Para indicarselo a Android, deberemos crear el siguiente archivo "theme.xml" y alojarlo dentro del directorio "res/values" de nuestro proyecto. @null Slo falta aadir en el Manifest.xml el atributo android:theme="@style/Theme.NoBackground" al tag para aplicar este tema a todas las actividades de la misma, o al tag en caso de que quieras aplicarlo slo a determinadas actividades.