Rediseñando los eventos de pilas-engine

El sistema de eventos de pilas-engine necesitaba
varias mejoras. Desde que lo implementé, siempre tuve
la sensación de que se podía simplificar y mejorar
notablemente.

Por suerte hoy logré implementar los cambios que quería,
le dediqué varias horas pero valió la pena el esfuerzo, quedó
mucho mejor de lo que esperaba, y en el camino aprendí varias cosas
nuevas.

¿Por qué es importante?

Casi todos los motores de juegos tienen algún sistema de eventos,
de alguna u otra forma se te permite conocer el estado de las
teclas, del mouse etc.

Pero en pilas-engine la cosa es un poco mas ambiciosa, pilas-engine
va a ser una de las primeras experiencias de programación para
muchas personas, y actualmente hay juegos en desarrollo que van
a crecer y se van a volver mas complejos.

Por ese motivo, el sistema de eventos (el corazón de los juegos), merece
mucha atención. Tiene que ser un sistema simple, fácil de utilizar y a
la vez flexible.

El problema

A medida que estás haciendo un juego la cosa se empieza a tornar
compleja. Comienzas a tener contadores, barras de energía, personajes, enemigos etc.

Incluso si has previsto todas estas cosas antes de empezar, no es
una buena idea diseñar con todo eso en mente. Es mejor ir paso a paso, modificando
lo existente y adaptándolo, progresando mediante prototipos simples y que se
puedan extender.

Aquí es donde el sistema de eventos de pilas-engine se destaca
de otros, ¿Por qué?.

Porque los eventos se pueden usar para poner en comunicación a
varios componentes del juego, usando una solución elegante y
muy sofisticada del mundo del software llamada patrón de diseño observador.

Un ejemplo: barras de energía

pilas-engine está pensado para hacer juegos, así que en lugar de hablar desde un punto de vista teórico, te voy a comentar cómo
funciona el sistema de eventos en un juego real: Shaolin's Blind Fury

En el juego Shaolin's Blind Fury hay una barra de energía que
nos permite ver la vitalidad de un enemigo. Esto es útil para conocer
cuanto puede vivir un enemigo mientras luchamos contra él:

Esta barra solamente muestra la energía del enemigo que estamos
golpeando. Si nos alejamos, y luchamos contra otro enemigo, la misma
barra nos tiene que mostrar la energía del nuevo contrincante.

Imagina que no conocemos los eventos de pilas, ¿Cómo podríamos mostrar
una barra de energia?: podríamos escribir algo así:

barra = Energia(un_enemigo)

y luego podríamos hacer que este objeto lea el atributo
numérico energia del enemigo:

class Energia(ActorEnergia):

    def __init__(self, enemigo):
        self.enemigo = enemigo

    def actualizar(self):
        self.progreso = self.enemigo.energia

Si nuestro juego solo tuviera un enemigo, estaríamos perfecto. Pero no, lo
complicado de esta solución es lograr que la misma barra pueda
mostrar la energía de otros enemigos.

¿Cómo tendría que diseñar las cosas si mi juego tiene mas enemigos?.

La respuesta inmediata sería: "enviarle a la energía una lista de enemigos,
en lugar de uno solo". Pero si hago eso, ¿Cómo hago para que la barra
de energía sepa el momento justo en que he logrado golpear a otro enemigo?.

Como verás, la solución inicial se va volviendo demasiado complicada, y
tenemos que hacer que la barra de energía sepa cada vez mas cosas
y reciba mas argumentos. Es demasiado amenazante, si seguimos por
este camino se va a poner demasiado complejo...

Un enfoque distinto: menos acople

Vamos a cambiar la estrategia, usemos el nuevo sistema de eventos
de pilas-engine:

Claramente necesito saber "en qué momento se ha golpeado a un enemigo", así
que mi primer paso es crear un evento que represente eso:

pilas.eventos.cuando_golpean = pilas.eventos.Evento("cuando golpean")

Ese evento, ahora me va a servir para conocer el momento exacto del
golpe.

La barra de energía necesita observar a ese evento, porque cuando
ese evento se emita voy a necesitar redibujar la energía:

class Energia(ActorEnergia):

    def __init__(self):
        pilas.eventos.cuando_golpean.conectar(self.actualizar_energia)

    def actualizar(self):
        # ahora no hace nada...
        pass

    def actualizar_energia(self, evento):
        self.progreso = evento.quien.energia

De esta forma, la barra queda completamente libre de los enemigos, no
necesita tener una referencia o una lista, no importa. La
barra solamente será notificada cuando el evento cuando_golpean sea
emitido por alguien mas.

Por último, en el código del enemigo quero emitir la señal:

class Enemigo(Actor):

    def recibir_golpe(self):
        self.energia -= 10

        if self.energia < 0:
            self.morir()

        pilas.eventos.cuando_golpean.emitir(quien=self)

y listo, ahora cuando un enemigo reciba un golpe, simplemente
emitirá la señal cuando_golpean. Y en nuestro caso, esa señal
es observada por la Energia.

Algo interesante del ejemplo anterior, es que cuando emitimos
una señal podemos enviar los argumentos que queramos. En este caso usé el
argumento quien, porque me interesa saber quién recibió el golpe para
mostrar su energía. Puedes mirar el código de la clase Energia para ver cómo estoy
leyendo ese parámetro quien.

Ten en cuenta que ahora no importa cuantos enemigos tengamos en
nuestro juego. Tampoco nos limita tener una sola barra de energía, de hecho,
podríamos agregar un contador de puntajes, que nos aumente el puntaje
cada vez que golpeamos a un enemigo. ¿Cómo?, simplemente haciendo que
el puntaje sea un observador del evento cuando_golpean, igual que
la energía.

Otro ejemplo pero sin código, solo para pensar

Imagina lo simple que resulta esta comunicación y cómo nos
puede simplificar el desarrollo:

Piensa en el juego pacman. Hay un protagonista, muchas pastillas y fantasmas:

En un juego como pacman podríamos crear un evento llamado come_pastilla,
y emitirlo cada vez que el pacman toca una pastilla.

A su vez, a este evento come_pastilla lo podrían estar observando dos
actores: un contador de puntaje que se incrementa con cada pastilla y
una escena, que podría tener un contador sencillo para saber cuando
tiene que pasar al siguiente nivel.

Otro evento llamado muere_pacman podría ser observado por un actor
contador de vidas, que maneje un visor de vidas al costado de la pantalla.

Y un evento como come_pastilla_especial podría hacer que todos los
fantasmas observadores de ese evento se pongan azules!

Conclusión

El nuevo sistema de eventos de pilas es paso adelante, le
va a permitir a muchas personas lograr diseños de video-juegos
mas simples y fáciles de extender.

Personalmente, estoy contento por las posibilidades técnicas
que ofrece, y además, porque los resultados los estoy
poniendo en práctica ahora mismo con el juego Shaolin.

Ojalá mi artículo te halla resultado útil, y que los eventos
de pilas-engine te parezcan una buena idea.

Gracias!

comments powered by Disqus