¿Qué debo tener en cuenta al diseñar un sistema de administrador de eventos?

9

He estado dando vueltas con los fundamentos de un motor de juegos Java, y he llegado al punto en el que estoy listo para agregar un sistema Event Manager.

Sé, en teoría , lo que debe hacer un administrador de eventos: permitir que los objetos se "registren" para ciertos eventos, y cada vez que el administrador de eventos recibe una notificación de un evento, transmite el evento a los oyentes "registrados". Lo que me sorprende es cómo comenzar a implementarlo.

No he podido encontrar nada, en línea, sobre la implementación de un sistema de eventos desde cero, por lo que estoy buscando información sobre cuáles son las mejores prácticas en este caso: qué debo y qué no debo hacer.

Por ejemplo, ¿es realmente necesario que cada uno de mis objetos del juego tenga un EventManagercampo? Como todos mis objetos de juego heredan de una sola clase principal abstracta, creo que debería poder usar una referencia estática para que solo haya una instancia del Administrador de eventos, compartida entre todos los objetos del juego. Hago algo similar, con el Applet, que ya uso para renderizar cada objeto.

Supongo que tendría que mantener una colección de algún tipo para cada posible evento suscrito, agregando y eliminando objetos del juego de la lista, según sea necesario. Creo que debería ser posible hacer una cola de eventos que necesitan ser transmitidos, en cuyo caso simplemente podría agregar "EventManager.Update ()" al ciclo principal del juego, y hacer que el Update()método transmita los eventos que ocurrieron al final de cada cuadro. Finalmente, cada objeto tendría un HandleEvent(Event e)método, que luego podrían analizar y responder adecuadamente.


¿Suena esto como la dirección adecuada hacia la implementación de dicho sistema, o me estoy desviando y / o me estoy perdiendo algo bastante obvio?

Soñador cuervo
fuente
2
gamedev.stackexchange.com/questions/7718/… Este es un Q / AI dado anteriormente que puede ayudarlo a comenzar.
James
@ James Ah: parece un buen enlace. ¡Gracias! Creo que me perdí eso, antes.
Raven Dreamer
44
Algunas sugerencias adicionales: decida si algunos eventos deberían tener efecto inmediatamente en lugar de al final del cuadro, especialmente si un controlador de eventos desencadena eventos adicionales; piensa qué sucede cuando obtienes bucles de eventos; Si va con una cola, tal vez necesite un sistema de prioridad o una forma de evitar la cola.
sam hocevar

Respuestas:

6

Esto puede ser tan simple como quieras.

for each object in the subscriber list:
    object.notify(this event) // (or 'HandleEvent' if you prefer)

No intentes averiguar qué debe "hacer" un administrador de eventos: determina qué necesitas que haga. El resto debería seguir desde allí, o al menos, debería sugerir algunas preguntas más específicas.

Kylotan
fuente
3
+1 para "No intentes averiguar qué debe hacer una X: resolver lo que necesitas que haga". De hecho, no agregue ningún administrador de eventos hasta que sienta que necesita una forma desacoplada pero centralizada de llamar a las funciones.
@JoeWreschnig Oh Dios sí =) "resuelve lo que necesitas que haga" Este es uno de los 3 mejores consejos que cualquier desarrollador debe tomar en serio.
Patrick Hughes
Al principio quería estar en desacuerdo con su segunda oración sobre no agregar un administrador de eventos porque la mayoría de los juegos pueden beneficiarse de una forma desacoplada pero centralizada de llamar funciones, pero luego pensé en cuántos juegos pequeños que hice que no tener cualquier tipo de administrador de eventos. así que, sí
jhocking
3

Los tres métodos esenciales que necesita un sistema de eventos son un método addListener (), un método removeListener () y un método para dispatchEvent (). Es decir, un método que los objetos usan para registrarse para un evento, un método que usan los objetos para cancelar el registro y un método para transmitir un evento a todos los oyentes. Todo lo demás es salsa.

Como observa, seguramente necesitará algún tipo de estructura de datos para realizar un seguimiento de los oyentes registrados. El enfoque más simple sería una matriz asociativa (Diccionario o un Objeto en JavaScript) que asocia un vector (o simplemente una Matriz dependiendo del idioma) con un evento. Ese vector es una lista de todos los oyentes registrados; agregue oyentes a la lista o elimínelos en los métodos add / removeListener ().

Para transmitir un evento en dispatchEvent () puede ser tan simple como recorrer el vector. Puede agregar más complejidad al despacho ordenando la prioridad de los eventos o lo que sea, pero no se preocupe por eso hasta que lo necesite. En muchos casos no lo necesitarás.

Hay un pequeño matiz en dispatchEvent () cuando considera qué datos pasar junto con el evento. En el nivel más básico, es posible que no pase ningún dato adicional; todo lo que el oyente tiene que saber es que ocurrió un evento. Sin embargo, la mayoría de los eventos tienen datos adicionales que van con ellos (p. Ej., Dónde sucedió el evento) y querrás que dispatchEvent () acepte y pase algunos parámetros.

Por ejemplo, ¿es realmente necesario que cada uno de mis GameObjects tenga un campo "EventManagner"? Como todos mis GameObjects heredan de una sola clase principal abstracta, creo que debería poder usar una referencia estática para que solo haya una instancia del EventManager, compartida entre todos los objetos del juego.

Hay muchas formas de dar a todos los objetos del juego una referencia a la clase EventManager. Una referencia estática es ciertamente unidireccional; otro es con un Singleton. Sin embargo, ambos enfoques son bastante inflexibles, por lo que la mayoría de las personas de por aquí recomiendan un localizador de servicios o una inyección de dependencia. He estado haciendo inyección de dependencia; eso significa un campo EventManager separado para cada objeto, que es lo que parece querer evitar, pero no estoy seguro de por qué eso es un problema. No es que haya muchos gastos generales por almacenar un montón de punteros.

jhocking
fuente
Sí, la primera vez que realmente comprendí e implementé la inyección de dependencia fue para un objeto EventDispatcher. Una vez que lo tuve en mi haber, he tenido ganas de refactorizar muchas cosas con ese patrón.
jhocking
0

DESCARGO DE RESPONSABILIDAD

No soy un programador de Java, pero escribí un artículo explicando cómo crear un sistema de eventos en C89, uno de los lenguajes más fáciles (en mi humilde opinión), échale un vistazo

https://prdeving.wordpress.com/2017/04/03/event-driven-programming-with-c-89/

Los fundamentos del manejo de eventos son bastante simples.

  • registrar un evento con una devolución de llamada
  • acontecimiento desencadenante
  • recorrer los oyentes para ver si hay una coincidencia
  • controlador de incendios si se encuentra
  • repetir

Sabiendo esto (pero sin saber cómo implementarlo en Java), es fácil de entender que necesita algunos controladores (funciones para manejar los eventos) y agregarlos a una matriz (con punteros, en C) emparejados con un nombre de evento. Una vez que desencadena un evento, almacena el nombre del evento desencadenado y sus argumentos en una pila, esto se denomina grupo de eventos.

Luego tiene un resumen de eventos (un bucle simple), que muestra el primer evento en esa pila, intenta encontrar un controlador para él y, si lo hay, lo ejecuta con los parámetros.

Por supuesto, este resumen de eventos se puede activar cuando lo desee, por ejemplo, una vez por cuadro después de llamar a su Update()función.

PRDeving
fuente
-2

Para el campo EventManager, use una variable estática. Un Singleton para EventManager también sería una gran idea.

Su enfoque suena bien, pero no olvide hacer que sea seguro para subprocesos.

Es bueno ejecutar IO-Events antes o después de "GameEvent", por lo que en un marco cada "GameEvent" trata con los mismos datos.

Sibbo
fuente
¿"Guardar hilo"? Hmmm
U62
-1 para la sugerencia singleton completamente innecesaria e injustificada.