Llamadas de función por cuadro versus mensajes dirigidos por eventos en el diseño del juego

11

El diseño tradicional del juego , como lo sé, utiliza polimorfismo y funciones virtuales para actualizar los estados de los objetos del juego. En otras palabras, el mismo conjunto de funciones virtuales se llama en intervalos regulares (por ejemplo, por cuadro) en cada objeto en el juego.

Descubrí recientemente que hay otro sistema de mensajería controlado por eventos disponible para actualizar los estados de los objetos del juego. Aquí, los objetos generalmente no se actualizan por cuadro. En cambio, se crea un sistema de mensajería de eventos altamente eficiente , y los objetos del juego se actualizan solo después de recibir un mensaje de evento válido.

Event Driven Game Architecture se describe bien en: Game Coding Complete por Mike McShaffry .

¿Podría pedir ayuda con las siguientes preguntas?

  • ¿Cuáles son las ventajas y desventajas de ambos enfoques?
  • ¿Dónde está uno mejor que el otro?
  • ¿Es el diseño del juego dirigido por eventos universal y mejor en todas las áreas? ¿Se recomienda su uso incluso en plataformas móviles?
  • ¿Cuál es más eficiente y cuál es más difícil de desarrollar?

Para aclarar, mi pregunta no es sobre eliminar completamente el polimorfismo del diseño de un juego. Simplemente deseo entender la diferencia y beneficiarme del uso de mensajes dirigidos por eventos versus llamadas regulares (por cuadro) a funciones virtuales para actualizar el estado del juego.


Ejemplo: esta pregunta causó un poco de controversia aquí, así que déjame ofrecerte un ejemplo: según MVC, el motor del juego se divide en tres partes principales:

  1. Capa de aplicación (comunicación de hardware y sistema operativo)
  2. Game Logic
  3. Vista del juego

En un juego de carreras, Game View es responsable de renderizar la pantalla lo más rápido posible, al menos 30 fps. Game View también escucha las opiniones de los jugadores. Ahora esto sucede:

  • El jugador presiona el pedal de combustible al 80%
  • GameView construye un mensaje "Pedal de combustible del automóvil 2 presionado al 80%" y lo envía a Game Logic.
  • Game Logic recibe el mensaje, evalúa, calcula la posición y el comportamiento del nuevo automóvil y crea los siguientes mensajes para GameView: "Dibujar el pedal de combustible del automóvil 2 presionado al 80%", "Aceleración del sonido del automóvil 2", "Coordenadas del automóvil 2 X, Y". .
  • GameView recibe los mensajes y los procesa en consecuencia
Bunkai.Satori
fuente
¿Dónde encontraste? algunos enlaces o referencias? No conozco este enfoque (pero conozco bastante el patrón de diseño de weel en general, y recomiendo un buen uso de los principios de orientación a objetos en general), pero creo que uno basado en eventos es mejor ... piense en la evolución en los sistemas de red: desde sondeo a llamadas asincrónicas ... está optimizado en cómputos y en el nivel de abstracción
nkint
Hola Nkint, gracias por tu comentario. Básicamente deseaba comparar la comunicación basada en eventos frente a las llamadas a funciones virtuales. Modificaré mi pregunta un poco. Por cierto, eche un vistazo a este enlace que contiene patrones de diseño de juegos: gameprogrammingpatterns.com .
Bunkai.Satori
55
Tal vez estoy siendo tonto, pero ¿cómo vas a hacer que un sistema de mensajería funcione sin usar polimorfismo? ¿No vas a necesitar algún tipo de clase base (abstracta o de otro tipo) para definir tu interfaz de receptor de eventos? (Editar: suponiendo que no estás trabajando en un idioma con la reflexión adecuada.)
Tetrad
2
Además, la eficiencia estará muy vinculada a los detalles de implementación. No creo que "cuál sea más eficiente" sea una pregunta que pueda responderse en su forma actual.
Tetrad
1
Los objetos del juego necesitan marcar. Los objetos del juego necesitan comunicarse entre sí. Esas dos cosas deben suceder. Lo primero que no haces con la mensajería porque es redundante (probablemente tengas una lista de todos los objetos en alguna parte, solo invocalos update). Lo segundo que puedes hacer con la mensajería por varias razones.
Tetrad

Respuestas:

8

Creo plenamente en la necesidad de ser la madre de la invención. No me gusta codificar nada a menos que su necesidad sea clara y esté bien definida. Creo que si comienzas tu proyecto configurando un sistema de mensajería de eventos, lo estás haciendo mal. Creo que solo una vez que haya creado y probado su infraestructura y tenga todas las piezas de su proyecto como módulos de trabajo bien definidos, debe centrarse en cómo conectará estos módulos entre sí. Intentar diseñar un sistema de mensajería de eventos antes de comprender la forma y las necesidades de cada módulo está fuera de servicio.

¿Cuáles son las ventajas y desventajas de ambos enfoques?

Creo que algunas personas argumentarían que existen buenos sistemas de mensajería listos para ser instalados y utilizados. Quizás esto es cierto. Ciertamente, el rendimiento de una solución de construcción personalizada específicamente adaptada a sus necesidades sería mayor que un bus de mensajes de uso general. La gran pregunta es: ¿cuánto tiempo pasará construyendo un sistema de mensajería en lugar de usar algo que ya se ha construido? Obviamente, si puede encontrar algún tipo de sistema de eventos con exactamente el conjunto de características que necesita, entonces es una decisión bastante fácil. Es por eso que me aseguro de entender exactamente lo que necesito antes de tomar esa decisión.

¿Dónde está uno mejor que el otro?

Podríamos hablar de memoria y recursos aquí. Si está desarrollando para cualquier hardware con recursos limitados, ciertamente es un problema utilizar un sistema de eventos que reducirá eso significativamente. Realmente, sin embargo, creo que el problema se reduce a lo que necesitas, que como ya mencioné es algo que no sabes hasta que ves cómo se ven todas las piezas. Si tiene suficiente experiencia en la construcción de estos sistemas y sabe de antemano exactamente cómo se verán todas las piezas, también puede responder esta pregunta con anticipación. Supongo que no tienes esta experiencia, ya que no harías esta pregunta si la tuvieras.

¿Es el diseño del juego dirigido por eventos universal y mejor en todas las áreas? Por lo tanto, ¿se recomienda su uso incluso en plataformas móviles?

¿Universal y mejor en todas las áreas? Una declaración bastante general como esa es fácilmente rechazada. Cualquier cosa que agregue gastos generales debe llevar su parte de la carga de trabajo. Si no está utilizando eventos lo suficiente como para garantizar la sobrecarga del diseño, entonces es el diseño incorrecto.

¿Cuál es más eficiente y cuál es más difícil de desarrollar?

La eficiencia depende de cómo se implemente. Creo que un diseño tradicional bien desarrollado superará a un sistema basado en eventos cualquier día de la semana. Esto se debe a que la comunicación entre las piezas puede ser microgestionada y hacerse súper eficiente. Por supuesto, esto también dificulta el diseño desde el punto de vista de la experiencia y el tiempo necesarios para completarlo y hacerlo más eficiente. Por otro lado, si carece de esta experiencia o no tiene el tiempo para desarrollar bien la aplicación, utilizar un diseño basado en eventos que satisfaga sus necesidades será más eficiente. Si tiene necesidades de eventos personalizados que no encajan fácilmente en una estructura basada en eventos, esto podría hacer que la estructura basada en eventos sea muy difícil de diseñar, especialmente para mantener el diseño eficiente.

Erick Robertson
fuente
Hola Eric, gracias por tu detallada opinión sobre mi pregunta. Mientras leo las respuestas de usted y de otros, la implementación de mensajes de eventos probablemente no cause un milagro en el aumento general del rendimiento del juego. En cambio, va a complicar mucho las cosas. Me gustaría ver algunas respuestas más, antes de cerrar esta pregunta. Como todo el libro cubrió este tema, parecía una buena idea para considerar. No estoy muy seguro, si la decisión de usar o no usar mensajes se puede tomar en el medio del proyecto. Si se usan mensajes de eventos, diría que el diseño general del objeto también debe ajustarse.
Bunkai.Satori
Estoy en desacuerdo. Si el diseño general del objeto está bien encapsulado, debe ser lo suficientemente flexible como para ser utilizado en cualquier tipo de marco. He podido intercambiar métodos de comunicación en proyectos grandes con muy poco trabajo porque cada pieza estaba bien encapsulada.
Erick Robertson el
7

Creo que estás comparando manzanas y naranjas aquí. El polimorfismo no se reemplaza por mensajes en absoluto. Probablemente desee que los eventos / mensajes conecten componentes acoplados libremente. P.ej. para enviar un mensaje desde una entidad cuando se produce una colisión, para actualizar el puntaje del jugador o tal vez para activar un efecto de sonido. Para que estas clases individuales no conozcan las otras clases y simplemente envíen y / o manejen mensajes.

Pero lo más probable es que su juego tenga un ciclo de actualización en algún lugar y, dado que tiene ese ciclo de actualización, puede usarse fácilmente para actualizar todas las entidades del juego que también deben actualizarse en cada cuadro. Sin embargo, esto no evita que uses mensajes ...

Si tiene algún tipo de estructura donde agrega / elimina entidades de juego, puede incluirlas en su ciclo de actualización en lugar de enviarles un mensaje de actualización en cada cuadro. Entonces, ¿por qué no llamar a la actualización de sus entidades de juego directamente mientras está usando la mensajería para conectar diferentes subsistemas de su juego? También me gusta el concepto de señal / ranura ( ver ejemplo qt ) para sistemas similares a eventos.

En pocas palabras: no hay un mejor enfoque, ni son exclusivos.

bummzack
fuente
Hola bummzack Finalmente, llegó la primera respuesta. Gracias por eso :-) Cuando mencioné Event Driven Architecture, me refería a un sistema MVC con tres capas clave: Appliaction, GameView, GameLogic. Toda la comunicación entre estos trhee se haría a través de mensajes. Esto es significativamente diferente al sistema tradicional con ciclo de actualización. Cada sistema tiene una arquitectura diferente, algunas ventajas y desventajas, y tienen un costo de rendimiento diferente. Por lo tanto, creo que uno sería un poco mejor en algunas áreas. Después de un buen análisis, deberíamos poder concluir cuál es mejor.
Bunkai.Satori
3
No veo por qué esto es diferente? Necesitará un ciclo de actualización en algún lugar y eso probablemente actualizará el controlador si desea seguir con MVC. Entonces el controlador puede enviar un mensaje al modelo y la vista ... pero como el controlador generalmente conoce la vista y el modelo, también podría actualizarlos directamente. Pero esto no está reemplazando ningún polimorfismo. Su pregunta y suposiciones suenan muy teóricas ... ¿quizás las respalde con algún código de ejemplo o algunas referencias?
bummzack
El libro mencionado anteriormente, Game Coding Complete, recomienda la mensajería de eventos a través de llamadas directas a métodos virtuales. Aunque el sistema de mensajería parece más complejo, su ventaja es que no es necesario que cada objeto del juego verifique el mundo del juego. La lógica del juego verifica el mundo del juego solo una vez, y luego solo se abordan aquellos objetos que se supone que cambian sus estados. Esta es una diferencia y puede significar un ahorro de costos. Si decides que los objetos de tu juego se comunicarán a través de mensajes, no se llamarán en cada cuadro, por lo tanto, los dos enfoques son mutuamente excluyentes.
Bunkai.Satori
1
Votó una respuesta que refleja los comentarios de Tetrad debajo de la pregunta original. @ Bunkai.Satori El 'mundo del juego se verifica solo una vez' es el ciclo de actualización, todo lo que necesita actualización lo consigue. Los mensajes de evento están destinados a ser raramente hechos, pero aún son cosas clave en el motor (PlayerDied, MonsterDied, etc.) de que verificar cada fotograma en un bucle Update () sería un desperdicio, pero es más probable que sean generados por el Update () en sí mismo.
James
1
Si tienes la segunda edición, mira el capítulo llamado Controlling the Main Loop. Debería llamarse igual en la tercera edición. Eso realmente debería responder a tu pregunta.
Ray Dey
4

Esto comenzó como un comentario a la respuesta de bummzack, pero se alargó.

A menos que realmente lo haga asíncrono (despachar eventos en un nuevo hilo), sus objetos aún reciben el memo de forma sincrónica. Suponiendo que su despachador de eventos use una tabla hash básica con una lista vinculada en una celda, el orden será el orden en que se agregaron los objetos a esa celda.

Además, a menos que decida usar punteros de función por alguna razón, todavía está usando llamadas de función virtuales ya que cada objeto que recibe un mensaje debe implementar IEventListener (o como se llame). Los eventos son una abstracción de alto nivel que se construye utilizando polimorfismo.

Use eventos en los que tenga que llamar a una larga lista de métodos para varios eventos en el juego para sincronizar diferentes sistemas en el juego o donde varios objetos y sistemas reaccionen para que dichos eventos no puedan definirse claramente o desee para agregar más reacciones a dichos acontecimientos en el futuro.

Son una gran herramienta, pero como dijo bummzack, no asuma que el polimorfismo y los eventos resuelven el mismo problema.

michael.bartnett
fuente
Hola Bearcdp, gracias por tu respuesta. Tiene sentido para mí. Hay una cosa en mi mente, que vota por mensajes de eventos: digamos, hay 200 expulsiones en la escena. Si usa llamadas de función por cuadro en todos los objetos, debe verificar la colisión de los 200 objetos en cada cuadro (un ejemplo; por supuesto, los intervalos de llamadas se pueden administrar). Con la mensajería de eventos, el mundo del juego se examina solo una vez. Si hay 5 colisiones, solo 5 objetos son notificados por mensajes sobre el evento de colisión, en lugar de 200 pruebas de detección de colisión con llamadas por cuadro. ¿Cuál es tu opinión?
Bunkai.Satori
2
examinar el mundo del juego? ¿Qué significa eso? Tendría que significar ejecutar las mismas 200 comprobaciones para aislar los 5 sobre los que necesita enviar mensajes. Con el concepto de función virtual, el mundo del juego solo se verifica una vez (la función de cada objeto por turno), y luego se mueve solo a los 5 que necesitan cambios de estado ... sin la sobrecarga de un sistema de mensajería.
Steve H
1
Escucha a Steve H, el mundo no choca solo. Principalmente uso eventos para eventos de alto nivel, como PlayerJump, EnemyHit. Para manejar la eficiencia de la verificación de colisión, debes elegir una estructura de datos para organizar la ubicación física de los objetos en tu juego para que puedas priorizar qué objetos necesitan y no deben verificarse para colisión. Una vez que haya determinado todos los pares de objetos que han colisionado, puede enviar un evento con los datos de colisión relevantes (los dos objetos, velocidad / posición / datos normales, etc.).
michael.bartnett
Hola Steve y Beardcp, gracias por tus comentarios. Lo que escribiste tiene sentido. Parece que puede haber muchas implementaciones posibles del sistema de mensajería de eventos. Puede haber un reemplazo puro del concepto de función virtual por cuadro, como lo dice el libro mencionado anteriormente. Sin embargo, el sistema de mensajería también puede coexistir con el concepto de función virtual por cuadro, como usted dice.
Bunkai.Satori
¿Por qué dice "por alguna razón" en referencia a los punteros de función? Así es como implementé un sistema de eventos las veces que escribí uno desde cero (trabajo en lenguajes que tienen funciones de primera clase pero es la misma idea; pasar la función al objeto del centro de eventos y ese objeto asigna las funciones de escucha a eventos), así que me pregunto si hay una desventaja en ese enfoque.
jhocking
2

Yo diría que elegir uno u otro está bastante mal. Algunos objetos necesitan llamar a cada cuadro, otros no. Si tiene un objeto que desea renderizar, debe realizar una llamada de renderizado en cada fotograma. Los eventos no pueden hacer este cambio. Lo mismo es cierto para cualquier objeto de física basado en el tiempo: debe llamarlos cada cuadro.

Sin embargo, no hago llamadas en cada cuadro a mis objetos de administración de objetos, ni a mis objetos de entrada de usuario, ni nada de eso. Las llamadas virtuales masivas a cada objeto en el marco son una idea terrible.

El diseño utilizado para cualquier objeto en particular debe basarse en las necesidades de esos objetos y no en alguna ideología para el sistema en su conjunto.

Editado para ser más claro en mi primera oración.

revs DeadMG
fuente
Hola DeadMG, permítanme explicar: en el caso mencionado anteriormente, el diseño se divide en tres partes principales: capa de aplicación (acceso de hardware), lógica de juego, vista de juego. Lo que se procesa por cuadro es la Vista del juego. Sin embargo, cuando Game View registra una entrada del usuario (por ejemplo, se presiona el pedal del freno al perseguir el juego), envía un mensaje "Car 2 Brake Pressed" a Game Logic para su procesamiento. Game Logic evalúa esta acción, calcula el comportamiento del automóvil y envía un mensaje a la Vista del juego: "Neumáticos de bloque de automóvil 2", "Mover de automóvil 2 a X, Y". De esta manera, no es necesario verificar por cuadro, si se presionó el freno en cada automóvil.
Bunkai.Satori
@Bunkai: Entonces, tiene algunos diseños que se basan en eventos y algunos diseños que se llaman cada cuadro. Eso está bastante de acuerdo con mi respuesta.
DeadMG
Estoy feliz de encontrar entendimiento con ustedes, ya que esta pregunta fue un poco controvertida hoy aquí :-)
Bunkai.Satori