Eventos, conexiones y respuestas
En el desarrollo de videojuegos es muy importante poder comunicarse con el usuario. Lograr que los personajes del juego puedan interactuar con él y exista una fuerte interacción.
En pilas usamos una estrategia llamada eventos, conexiones
y respuestas
, no solo porque es muy sencilla de usar, sino
también porque es una solución conocida y muy utilizada
en otros lugares como en la web.
¿Que es un Evento?
Los eventos representan algo que esperamos que ocurra
dentro de un juego, por ejemplo un click
del mouse, la
pulsación
de una tecla, el cierre
de la
ventana o la colisión
entre un enemigo y nuestro
protagonista.
Lo interesante de los eventos, es que pueden ocurrir en cualquier momento, y generalmente no lo controlamos, solamente los escuchamos y tomamos alguna respuesta predefinida.
Pilas representa a los eventos como objetos, y nos brinda funciones para ser avisados cuando un evento ocurre e incluso emitir y generar eventos nuevos.
Veamos algunos ejemplos:
Conectando la emisión de eventos a funciones
Los eventos
no disparan ninguna acción automática, nosotros
los programadores somos los que tenemos que elegir los
eventos importantes y elegir que hacer al respecto.
Para utilizar estas señales, tenemos que vincularlas a funciones, de forma que al emitirse la señal podamos ejecutar código.
La función conectar
La función conectar
nos permite conectar una señal de
evento a un método o una función.
De esta forma, cada vez que se emita una determinada señal, se avisará a todos los objectos que hallamos conectado.
Por ejemplo, si queremos que un personaje se mueva en pantalla siguiendo la posición del puntero del mouse, tendríamos que escribir algo como esto:
import pilasengine
pilas = pilasengine.iniciar()
mono = pilas.actores.Mono()
def mover_mono_a_la_posicion_del_mouse(evento):
mono.x = evento.x
mono.y = evento.y
pilas.eventos.mueve_mouse.conectar(mover_mono_a_la_posicion_del_mouse)
# O puedes utilizar el método abreviado del actor.
mono.mueve_mouse(mover_mono_a_la_posicion_del_mouse)
pilas.ejecutar()
Es decir, la señal de evento que nos interesa es mueve_mouse
(que se emite
cada vez que el usuario mueve el mouse). Y a esta señal le conectamos
la función que buscamos ejecutar cada vez que se mueva el mouse.
Ten en cuenta que pueden existir tantas funciones conectadas a una señal como quieras.
Las coordenadas que reporta el mouse son relativas al escenario y no de la ventana. Por lo tanto puedes asignar directamente el valor de las coordenadas del mouse a los actores sin efectos colaterales con respecto a la cámara.
Observando a los eventos para conocerlos mejor
Como puedes ver en la función mover_mono_a_la_posicion_del_mouse
, hemos
definido un parámetro llamado evento
y accedimos a sus valores
x
e y
.
Cada evento tiene dentro un conjunto de valores que nos resultará
de utilidad conocer. En el caso del movimiento de mouse usamos
x
e y
, pero si el evento es la pulsación de una tecla, seguramente
vamos a querer saber exactamente qué tecla se pulsó.
Entonces, una forma fácil y simple de conocer el estado de un objeto es imprimir directamente su contenido, por ejemplo, en la función de arriba podíamos escribir:
def mover_mono_a_la_posicion_del_mouse(evento):
print(evento)
y en la ventana de nuestra computadora tendríamos que ver algo así:
{'y': 2.0, 'x': -57.0, 'dx': 0.0, 'dy': -1.0}
donde claramente podemos ver todos los datos que vienen asociados al evento.
Por último, ten en cuenta que este argumento evento
, en realidad,
es un diccionario de python como cualquier otro, solo
que puedes acceder a sus valores usando sentencias cómo
diccionario.clave
en lugar de diccionario['clave']
.
Desconectando señales
Las señales se desconectan por cuenta propia cuando dejan de existir los objetos que le conectamos. En la mayoría de los casos podemos conectar señales y olvidarnos de desconectarlas, no habrá problemas, se deconectarán solas.
De todas formas, puede que quieras conectar una señal, y por algún motivo desconectarla. Por ejemplo si el juego cambia de estado o algo así...
Si ese es tu caso, simplemente asígnale un identificador único
al manejador de la señal y luego usa la función desconectar_por_id
indicando
el identificador.
Por ejemplo, las siguientes sentencias muestran eso:
pilas.eventos.mueve_mouse.conectar(imprimir_posicion, id='drag')
pilas.eventos.mueve_mouse.desconectar_por_id('drag')
En la primera sentencia conecté la señal del evento a una función y le di
un valor al argumento id
. Este valor será el identificador
de ese enlace. Y en la siguiente linea se utilizó el identificador
para desconectarla.
Listado de todos los eventos existentes
Evento | Parametros |
---|---|
mueve_camara | x, y, dx, dy |
mueve_mouse | x, y, dx, dy |
click_de_mouse | boton, x, y |
termina_click | boton, x, y |
mueve_rueda | delta |
pulsa_tecla | codigo, texto |
suelta_tecla | codigo, texto |
pulsa_tecla_escape | |
cuando_actualiza | |
pulsa_boton | numero |
mueve_pad | x, y, x1, y1 |
luego_de_actualizar |
Consultado señales conectadas
Durante el desarrollo es útil poder observar qué eventos se han conectado a funciones.
Una forma de observar la conexión de los eventos
es pulsar la tecla F6
. Eso imprimirá sobre
consola los nombres de las señales conectadas
junto a las funciones.
Creando tus propios eventos
Si tu juego se vuelve mas complejo y hay interacciones entre varios actores, puede ser una buena idea hacer que exista algo de comunicación entre ellos usando eventos.
Veamos cómo crear un evento:
Primero tienes que crear un objeto que represente a tu evento y darle un nombre:
evento = pilas.evento.Evento("Nombre")
luego, este nuevo objeto evento
podrá ser utilizado como
canal de comunicación: muchos actores podrán conectarse
para
recibir alertas y otros podrán emitir
alertas:
def ha_ocurrido_un_evento(datos_evento):
print("Hola!!!", datos_evento)
evento.conectar(ha_ocurrido_un_evento)
# En otra parte...
evento.emitir(argumento1=123, argumento2=123)
Cuando se emite un evento se pueden pasar muchos argumentos, tantos como se quiera. Todos estos argumentos llegarán a la función de respuesta en forma de diccionario.
Por ejemplo, para este caso, cuando llamamos al método evento.emitir
,
el sistema de eventos irá automáticamente a ejecutar la función ha_ocurrido_un_evento
y ésta imprimirá::
Hola!!! {argumento1: 123, argumento2: 123}
Referencias
El concepto que hemos visto en esta sección se utiliza
en muchos sistemas. Tal vez el mas conocido de estos es
la biblioteca GTK
, que se utiliza actualmente para construir
el escritorio GNOME
y Gimp
entre otras aplicaciones.
El sistema de señales que se utiliza en pilas es una adaptación del siguiente sistema de eventos:
http://stackoverflow.com/questions/1092531/event-system-in-python
Anteriormente usábamos parte del código del sistema django
, pero
luego de varios meses lo reescribimos para que sea mas sencillo
de utilizar y no tenga efectos colaterales con los métodos y
el módulo weakref
.
Si quieres obtener mas información sobre otros sistemas de eventos te recomendamos los siguientes documentos:
- http://pydispatcher.sourceforge.net/
- http://www.mercurytide.co.uk/news/article/django-signals/
- http://www.boduch.ca/2009/06/sending-django-dispatch-signals.html
- http://docs.djangoproject.com/en/dev/topics/signals/