Manejo de imágenes

En los videojuegos 2D las imágenes suelen estar en formatos gráficos como png o jpg ya diseñados con anterioridad.

En pilas se pueden cargar estos recursos usando el módulo imagenes. Por ejemplo, si tenemos una imagen llamada hola.png podríamos incorporarla a nuestro juego así:

hola = pilas.imagenes.cargar('hola.png')

Las imágenes no se imprimen directamente en pantalla, en su lugar tienes que crear un Actor y asignarle la imagen.

Por ejemplo, el siguiente código muestra la imagen en pantalla:

imagen = pilas.imagenes.cargar("mi_personaje.png")
actor = pilas.actores.Actor(imagen)

otra opción similar es crear al actor, y luego asignarle la imagen:

imagen = pilas.imagenes.cargar("mi_personaje.png")
actor = pilas.actores.Actor()

actor.imagen = imagen

Cualquiera de las dos opciones produce el mismo resultado, personaje "cambiará" de apariencia cuando se le asigne una nueva imagen.

Imágenes de fondo

Muchas veces queremos que las imágenes cubran el fondo de pantalla completamente, como si se tratara de un fondo o papel tapiz.

Si la imagen es suficientemente gránde para cubrir la pantalla, podemos cargarla como una imagen normal y luego crear un fondo que la represente:

fondo = pilas.fondos.Fondo()
fondo.imagen = pilas.imagenes.cargar('mi_fondo.png')

Ahora, si en realidad queremos que el fondo se dibuje como si fuera un mozaico (o papel tapiz), tenemos que indicarle a la imagen que se re-dibuje muchas veces hasta cubrir el fondo de pantalla. El código es muy similar al anterior, solo que ahora usamos las propiedades repetir_horizontal y repetir_vertical

fondo = pilas.fondos.Fondo()
fondo.imagen = pilas.imagenes.cargar('mi_fondo.png')

fondo.imagen.repetir_vertical = True
fondo.imagen.repetir_horizontal = True

Grillas de imágenes

Un forma conveniente de almacenar las imágenes de tus personajes es usar una grilla.

La siguiente imagen es una grilla de 10 columnas que utilizamos para crear al personaje "pingu":

Internamente la imagen se almacena así, pero a la hora de mostrarse en pantalla se puede seleccionar el cuadro.

Este es un ejemplo que carga la grilla de mas arriba y genera un actor para mostrar el cuadro 1:

actor = pilas.actores.Actor()
grilla = pilas.imagenes.cargar_grilla("pingu.png", 10)
actor.imagen = grilla

Ten en cuenta que el último argumento de la función pilas.imagenes.cargar_grilla es la cantidad de columnas que tiene la grilla. También es posible usar funciones que tengan filas y columnas, solo tendrías que indicar un argumento mas con el número de filas. Lo veremos mas adelante.

Puedes ejecutar la siguiente sentencia para ver la documentación completa de esta función:

help(pilas.imagenes.cargar_grilla)

Reproduciendo animaciones

Tener una grilla de imagenes es una buena forma de comenzar a realizar animaciones.

Si quieres tomar una grilla y mostrar una y otra vez sus cuadros podrías usar el actor Animación.

El siguiente código genera un actor que mostrará uno a uno los cuadros de la grilla:

grilla = pilas.imagenes.cargar_grilla("explosion.png", 7)
p = pilas.actores.Animacion(grilla, True)

El actor Animacion, también puede recibir cómo argumento la velocidad con la que tiene que reproducir la animación (medida en cuadros por segundo).

El segundo argumento indica que la animación tiene que ser cíclica (nunca termina).

Observa este ejemplo, muestra la misma animación de antes pero mostrando un cuadro por segundo y se elimina cuando termina:

grilla = pilas.imagenes.cargar_grilla("explosion.png", 7)
p = pilas.actores.Animacion(grilla, False, velocidad=1)

Animaciones controladas a mano con una grilla

Otra forma de hacer animaciones, es asociar una grilla directamente a un actor y cambiar el cuadro a mostrar.

Por ejemplo, la siguiente sentencia avanza al siguiente cuadro de animación en la grilla. Recuerda que comienza en 1:

grilla.avanzar()
actor.imagen = grilla

Ten en cuenta que el método avanzar va a retornar True o False. True significa que la grilla ha avanzado y ha mostrado un cuadro nuevo. False significa que la grilla volvió a mostrar el primer cuadro.

Este valor de retorno es muy útil a la hora de saber si una animación terminó, y poder tomar alguna decisión al respecto.

Grillas con filas y columnas

En el ejemplo anterior mencioné que las grillas pueden tener filas y columnas. Esto se logra gracias a que python permite tener funciones y métodos con argumentos opcionales.

En este caso, la función cargar_grilla también puede recibir la cantidad de filas que tiene una grilla:

animacion = pilas.imagenes.cargar_grilla("grilla.png", 2, 2)

el primer número 2 indica que la grilla tiene dos columnas y el segudo 2 indica que la grilla tiene dos filas.

Cuando usas una grilla con pilas y columnas, la función avanzar que vimos antes va a recorriendo los cuadros de la misma manera en que se lee una historieta (de izquierda a derecha y de arriba a abajo).

Esta es la apariencia de la imágen que usamos antes y los números indican el órden con que pilas leerá los cuadros:

Haciendo animaciones sencillas

En muchas oportunidades nos interesa hacer animaciones simples y que se repitan todo el tiempo sin mucho esfuerzo.

Con lo que vimos hasta ahora, hacer esas animación es cuestión de cargar una grilla y llamar cada un determinado tiempo a la función avanzar.

Pero como esta es una tarea muy común, en pilas hay una forma mas sencilla de hacer esto.

Existe un actor llamado Animación que tiene la capacidad de mostrar una animación cíclica, es decir, que se repita todo el tiempo, comenzando desde el principio cuando llega al final.

Veamos un ejemplo, esta imagen tiene 6 cuadros de animación ordenados en columnas:

Una forma sencilla de convertir esta animación en un actor simple es crear la grilla, construir un actor Animacion e indicarle a pilas que será una animación cíclica, es decir, que se tendrá que repetir indefinidamente:

grilla = pilas.imagenes.cargar_grilla("fuego.png", 6)
actor = pilas.actores.Animacion(grilla, ciclica=True)

El resultado en la ventana será una animación de fuego que no terminará nunca. Cuando el actor termine de mostrar el cuadro 6 de la animación regresará al primero para comenzar nuevamente.

Otra posibilidad es especificar el argumento ciclica=False. En ese caso el actor comenzará a mostrar la animación desde el cuadro 1 y cuanto termine eliminará al actor de la ventana. Esto es útil para hacer efectos especiales, como explosiones o destellos, cosas que quieres tener en la ventana un instante de tiempo y nada mas...

Haciendo actores con animación

Puede que quieras hacer un actor que tenga múltiples animaciones, y que las muestre en determinados momentos. Por ejemplo, si tienes una nave con motores, es probable que quieras mostrar una animación de motores en funcionamiento cuando la nave avanza y detener la animación de motores cuando finaliza el movimiento.

Una forma de lograr esto de manera sencilla es crear tu propio actor, y que este tenga dos atributos, uno para cada animación:

class MiNave(pilasengine.actores.Actor):

    def iniciar(self, x=0, y=0):
        self.animacion_detenida = pilas.imagenes.cargar_grilla("nave_detenida.png", 1)
        self.animacion_movimiento = pilas.imagenes.cargar_grilla("nave_en_movimiento.png", 3)

Luego, en el método actualizar del propio actor podrías avanzar la animación actual y permitirle al programador invocar métodos para intercambiar animaciones:

class MiNave(pilasengine.actores.Actor):

    # [...] codigo anterior

    def poner_en_movimiento(self):
        self.imagen = self.animacion_movimiento

    def poner_en_reposo(self):
        self.imagen = self.animacion_detenida

    def actualizar(self):
        self.imagen.avanzar()

Como puedes ver, el concepto inicial es el mismo, cuando queremos cambiar de animación tenemos que cambiar de grilla, y cuando queremos avanzar la animación solamente tenemos que llamar al método avanzar.

Animaciones

Además de las imágenes y las grillas, pilas incluye un recurso llamado animación, que nos permite declarar y utilizar animaciones almacenadas en una grilla.

Por ejemplo, si tenemos una grilla con varios cuadros de animación como aquí:

podemos cargar la grilla completa y definir las dos animaciones por separado.

Enumerando los cuadros de animación nos quedaría así:

y desde aquí podemos extraer dos animaciones:

  • La animación que podemos armar con los cuadros 0, 1, 4:

  • Y la animación que se puede armar con los cuadros 3, 4, 5:

Luego, para indicarle a pilas como interpretar las animaciones podemos cargar la animación y especificar los cuadros:

animacion = pilas.imagenes.cargar_animacion('alien.png', 5, 1)

animacion.definir_animacion('baja_palanca', [0, 1, 4], 10)
animacion.definir_animacion('parado', [3, 3, 3, 3, 4, 5, 4], 10)

Al llamar al método definir_animacion tenemos que especificar en nombre de la animación, los cuadros a mostrar y luego la velocidad (medido en cuadros por segundo.)

El siguiente paso es crear al actor e indicarle que animación mostrar en cada momento:


class MiActor(pilasengine.actores.Actor):

    def iniciar(self):

        # Las animaciones que cargamos antes:
        animacion = pilas.imagenes.cargar_animacion('alien.png', 5, 1)

        animacion.definir_animacion('baja_palanca', [0, 1, 4], 10)
        animacion.definir_animacion('parado', [3, 3, 3, 3, 4, 5, 4], 10)

        # Vinculamos la animación al actor
        self.imagen = animacion

        # Le indicamos que muestre la animación 'parado'
        self.imagen.cargar_animacion('parado')

    def actualizar(self):
        self.imagen.avanzar()


pilas.actores.vincular(MiActor)
mi_actor = pilas.actores.MiActor()

Es decir, con esta nueva clase, podremos representar a nuestro actor y seleccionar cualquiera de las dos animaciones que declaramos usando el método cargar_animacion, que en este caso usamos para cargar la animación parado.