28/4/14

[Tutorial] Guardar información cifrada (libGDX)

Buenas gente!

Como bien sabéis, la mayoría de las aplicaciones necesitan guardar cierta información, como puede ser puntuaciones, niveles, opciones de música o sonido, etc. Alguna información no es necesario ocultarla, como puede ser la configuración del perfil que se puede cambiar en el menú de opciones (el volumen al que queremos la música, etc.) pero otra información importante no queremos que esté a simple vista y que cualquiera pueda cambiarla fácilmente (por ejemplo la puntuación máxima que hemos obtenido en el juego, etc.). Esta última información irá cifrada.

En esta entrada veremos como guardar la información tanto cifrada como no.

Adaptando nuestras clases para guardar en fichero


Para guardar nuestra información en un fichero crearemos una clase que contenga esa información e implemente la interfaz Serializable.

Veremos un ejemplo con una clase Profile donde guardaremos algunos datos del perfil del jugador de nuestra aplicación:


Como podemos ver necesitamos dos funciones, una para guardar nuestros datos en fichero y otra para leerlos.

En la función write guardaremos valor a valor con la función:


El nombre que pongamos aquí será el que tengamos que usar para leer esa variable del fichero.

Como nota especial, para guardar un vector o una lista indicaremos el comienzo y el final de la lectura del vector con:


En la función read leeremos todos los valores usando los nombres que les pusimos en la función anterior e indicando la clase de cada variable (Integer.class para tipo int, String.class para las cadenas de caracteres, etc.).


Con esto ya tenemos lista nuestra clase para que sea guardada y leída en un fichero de tipo JSON.

Creando la clase de lectura y escritura de datos


Una vez que tenemos las clases que queremos guardar serializadas vamos a crear una clase que se encargue de organizarlo todo, de modo que con una llamada a una función de esta clase se guarden todos los datos, se creen los que no estén creados, etc. La clase debe ser parecida a esta:


En una variable de la clase guardaremos la dirección del fichero donde queremos guardar todos los datos, y tendremos una variable de cada clase que queramos leer/escribir en fichero.

Esta clase además tiene dos funciones: Read y Write, veamos que hace cada una de ellas.

  • Función read()

Si ya existe un perfil creado devuelve ese perfil.

Crea un nuevo objeto de tipo Json para tratar con él y crea un enlace con el fichero que le hemos indicado. Si no existiese el fichero el mismo se encargaría de crearlo.

Si existe ese archivo mira a ver si tiene información correcta. Para ello guarda toda la información en un String y lo decodifica (Si no necesitáis la información codificada podéis borrar la línea 14 y usar en la línea 16 profileAsCode en vez de profileAsText). Una vez tiene la información se la pasa a nuestra clase Profile para que tome los datos de ella (Si se tienen varias clases se les pasa a todas las clases).

Si no existe el fichero crea un nuevo perfil (si tienes más clases las creas aquí también) y lo guarda en el nuevo fichero creado.

  • Función write(Clases_a_leer)

Esta función crea un nuevo Json para tratar con él, y el enlace al fichero en el que escribiremos los datos.

Crea un String con los datos obtenidos de la clase Profile (Si tenéis más de una es solo concatenar las cadenas resultantes de leer todas las clases).

Despues codifica la información (de nuevo si no queréis información codificada podéis eliminar esta línea y sustituir en la línea 36 el profileAsCode por la variable profileAsText).

Una vez está la información lista para ser escrita la pasa al fichero.

Y con esto ya podremos guardar nuestros datos en un fichero de forma segura.

Ahora solo tendremos que llamar a nuestro ProfileSerializer.read() o ProfileSerializer.write(...) cada vez que queramos obtener los datos de perfil (por ejemplo al inicio de la aplicación, cuando seleccionamos una partida concreta, etc.) y guardar esos datos (por ejemplo al terminar una partida, al llegar a ciertos puntos, al cerrar la aplicación...).

Notas:


  •   El fichero se ha guardado en local, de modo que si se ejecuta nuestra aplicación en un ordenador se creará el fichero a partir de la ruta por defecto del usuario, es decir, en su carpeta personal.
  • Es recomendable poner un “.” delante del nombre de la carpeta donde vamos a guardar nuestros datos ya que en algunos sistemas como Linux esto indica que es una carpeta oculta y no nos molestará a la vista una vez creada.
  • El archivo puede tener la extensión que queramos, los más comunes son .json, .dat o .txt.

14/4/14

[Tutorial] Animación de sprites (libGDX)

Buenas gente! En esta entrada vamos a ver como hacer animaciones con nuestros sprites, de forma que nuestro jugador, enemigos y demás cobren vida :)

Variables necesarias


Primero necesitaremos alguna variable que nos indique en que animación se encuentra en estos momentos. Yo uso un enum por comodidad, pero se puede usar un string, un int o cualquier cosa que te guste más:


Para este ejemplo cambiaremos entre dos animaciones: parado (IDLE) y andando (WALK).

Ahora necesitamos una imagen que contenga la animación. Para ello nos vale una imagen .png. Esta imagen deberá estar dividida en cuadrados y cada cuadrado será un cambio en la animación. Lo normal es poner todos los pasos de una animación en una misma imagen o, incluso, todas las animaciones en una misma imagen. Veamos un ejemplo de imagen con nuestro querido personaje principal Pirko:




Esta imagen la cargaremos en la clase de nuestro personaje como una Texture:


Necesitaremos también un TextureRegion para indicar cual es la imagen actual de la animación que el programa tiene que mostrar y un Vector de TextureRegion por cada animación (o un solo TextureRegión si es solo una imagen estática la animación):


Ahora necesitamos tantas variables del tipo Animation como animaciones vayamos a tener y una más para tener la animación actual:


Creando una animación


Vamos a ver ahora como inicializar esas variables de tipo Animation. Para ello primero creamos un vector temporal con todas las TextureRegion de la imagen:


Con la línea anterior hemos cogido la textura con nuestra imagen y dividido en tantos cuadrados según su tamaño y el número de frames que tiene tanto por columnas (FRAME_COLS) como por fila (FRAME_ROWS). (Los frames son los cuadrados en los que se subdivide nuestra imagen con cada paso de la animación). Para nuestro ejemplo, FRAME_COLS vale 4 y FRAME_ROWS vale 1.

Una vez tenemos todas las TextureRegion en el vector temporal, inicializaremos cada vector de TextureRegion que habíamos creado antes para cada animación. Para ello nos vale con una inicialización y un bucle simple:



*Si la animación ocupase más de una fila, tendríamos que hacer dos bucles anidados para recorrerlas.*

Con la animación de estar quieto no necesitamos un bucle ya que será solo una imagen estática:


Ahora ya podemos inicializar nuestras variables de Animation, pasándoles el vector de TextureRegion y el tiempo en segundos que habrá entre un frame de la animación y el siguiente. A menor tiempo, más rápido irá la animación:


Cambiando la animación de nuestro personaje


Para indicarle a nuestra aplicación si nuestro personaje se encuentra andando, parado o cualquier otro estado usaremos una función como la siguiente:


Llamaremos a esta función cada vez que el personaje comience a moverse, se pare o, en definitiva, cambie su estado, pasándole como parámetro un valor del enum que creamos en un principio. Si el estado es diferente al que tiene actualmente, cambiará su animación.

Dibujando al personaje


Una vez que tenemos el estado del personaje y su animación correspondiente vamos a ver como mostrarlo por pantalla. Dado que nuestro personaje es un Actor, usaremos las funciones act(float delta) y draw(SpriteBatch batch, float parentAlpha) que ya vimos en entradas anteriores para que nuestro Stage se encargue de dibujarlo (Ver más información sobre el uso de Stage y Actor aquí).

Comenzamos con la función que actualiza el estado de la animación. Para ello tendremos que usar la siguiente línea dentro de la función:


Con esto seleccionamos el TextureRegion que se tiene que mostrar en ese instante de tiempo de la animación actual.

Ahora veamos que hay que poner en la función de dibujado:


Con la línea anterior le mandamos al SpriteBatch (ver más información sobre SpriteBatch aquí) de nuestro Stage a dibujar el frame de nuestro personaje que le corresponde, comenzando en la posición (posX, posY) con centro en (width/2, height/2), con un tamaño de (width, height), con una escala en los ejes x e y de scaleX y scaleY respectivamente (lo normal es tenerlo a 1) y con una rotación rotation en grados.

¡Tras esto ya podremos ejecutar nuestra aplicación y ver moverse a nuestro personaje!

Notas:


  • Recuerda hacer el dispose() de la textura. No es necesario hacerlo para los TextureRegion o las Animation.
  • Puedes crear animaciones para realizar ataques, recibir daño, morir o cualquier cosa que se te ocurra.
  • Este mismo tutorial vale para animar enemigos, ataques, objetos, etc.

2/4/14

[Tutorial] Mapa con TiledMap

Buenas gente! En esta entrada vamos a ver una herramienta muy útil para crear mapas: TiledMap. Es una herramienta completamente gratuita que podéis descargar de su página web aquí.

Con este editor podremos crear de forma rápida mapas tanto en vista ortogonal como isométrica, pudiendo hacer mapas de tipo plataforma o el típico mapa visto desde arriba.

Primero veremos como crear un mapa simple con este programa y luego veremos cómo añadirlo a nuestro juego fácilmente y como coger información de él.

Usando TiledMap


Para hacer un mapa, en primer lugar necesitaremos una imagen de donde sacar los patrones o “tiles” para nuestro mapa. Un ejemplo de imagen de patrones podría ser la siguiente:



Como podemos ver, la imagen tiene 4 cuadrados, de 64 píxeles cada uno. Esto serán nuestros 4 tiles. Por supuesto, estos son solo tiles de ejemplo, pero puedes crear mapas impresionantes. Otros mapas de patrones que puedes usar es este zip que puedes descargar en la página web de TiledMap como ejemplo. En él te viene un mapa de patrones además de imágenes de arbustos, un árbol y un mapa de ejemplo.

Una vez que tenemos nuestro mapa de patrones abrimos TiledMap y le damos a nuevo. En nuestro caso usaremos una perspectiva ortogonal. En ancho del patrón seleccionamos el tamaño de los “tiles”, suelen ser de 16 o 32 píxeles, pero nosotros usaremos de 64, para que se vean grandes. En el tamaño del mapa pondremos el número de tiles que queramos que tenga.

Ahora que tenemos nuestro mapa vacío vamos a cargar nuestra imagen con los patrones. Para ello vamos al menú de arriba y le damos a Mapa → Nuevo conjunto de patrones...

En la ventana que se nos abre seleccionamos nuestra imagen, le ponemos el nombre que queramos y nos aseguramos que el ancho y alto de nuestros tiles sea el mismo que hemos seleccionado para el mapa.



Ahora ya podemos ir seleccionando los tiles que queramos e ir dibujando nuestro mapa poco a poco. Podemos poner varias capas fácilmente haciendo click derecho en el menú de capas, a la derecha del mapa y seleccionando Nueva capa de patrones...

Yo he creado dos capas para mi juego, la capa suelo que será por donde mi personaje pueda ir andando y la capa de colisiones, donde pondré aquellos objetos que no se podrán traspasar.

Se pueden añadir atributos a cada tipo de tile. Esto nos puede servir por ejemplo para indicar distintas propiedades de cada tile, como puede ser que sea agua, por lo que nuestro personaje tuviera que ir más lento y nadando, que fuera un punto de aparición de enemigos, y cualquier cosa que se nos ocurra que podamos necesitar para nuestro juego. Para ello hacemos click derecho en el tipo de tile que queramos darle un atributo dentro de la pestaña conjunto de patrones, al lado del mapa donde dibujamos y seleccionamos Atributos del patrón... y añadir todos los atributos que queramos.

En mi caso, he puesto los cuadrados morado y rojo en la capa de colisión y he puesto como propiedad a todos los cuadrados verdes que sean el punto de aparición de mi personaje, de modo que no tenga para cada mapa que indicárselo yo manualmente.



Con esto ya tenemos nuestro mapa listo para usarse en el juego. Para ello lo guardamos como fichero con extensión .tmx y lo colocamos en nuestra carpeta Assets de nuestro proyecto name-android, junto con la imagen que hemos usado para los tiles.

Usando el mapa creado en nuestro juego


Una vez tenemos el mapa en la carpeta correcta, vamos a nuestro código. Creamos una nueva clase para colocar aquí todo lo que necesitemos de nuestro mapa. Necesitaremos los siguientes objetos:


El primero será nuestro mapa, cargado de nuestro fichero .tmx como veremos ahora.

El segundo será el encargado de renderizar y mostrar por pantalla el mapa.

El tercero es opcional y guardará una capa del mapa, en mi caso, la de colisión.

Ahora vamos a inicializarlos, para ello, usaremos las siguientes líneas:


Para inicializar nuestro mapa crearemos un nuevo TiledMap y cargaremos el fichero .tmx que hemos creado.

Para obtener nuestra capa de colisiones cogeremos del mapa recién creado la capa con el nombre que hayamos puesto, en mi caso “Colisiones”.

Por último, inicializamos el renderer pasándole nuestro mapa y un SpriteBatch, que puede ser alguno que ya estemos usando. Puedes ver más información sobre SpriteBatch aquí.

Para dibujar nuestro mapa tan solo tendremos que ejecutar la siguiente línea de código donde queramos que se dibuje:


Al cerrar la aplicación tendrás que hacer un dispose() tanto del mapa como del renderer:


Puedes coger la celda de una capa en determinada posición con la línea:


En este caso, i y j son números enteros, para pasar de una posición de pantalla a un número de celda deberás dividir la posición entre el tamaño de cada celda:


Podrás ver los atributos de una celda con la siguiente línea:


Puedes ver más funciones y usos de estos mapas en esta página.




Notas:


  •   Recuerda hacer el dispose() del mapa y su renderer. No es necesario hacerlo de las capas.
  • Puedes dibujar todo el mapa, como hemos hecho aquí, o dibujar solo una o varias capas que tu decidas.
  • Si ya tienes el batch que usas en el renderer iniciado deberás pararlo (batch.end()), luego dibujar el mapa con el renderer y por último volver a iniciar el batch (batch.begin()). Si no te dará un fallo.
  • Cada tile acepta canal alfa, por lo que puedes crear tiles con transparencias y que se vea el tile de la capa inferior al dibujarse una celda.