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!