Pregunta

Para un juego 2D que estoy haciendo (para Android), estoy usando un sistema basado en componentes donde un GameObject posee varios objetos GameComponent. Los gamecomponentes pueden ser cosas como componentes de entrada, componentes de representación, componentes de emisión de balas, etc. Actualmente, los gamecomponentes tienen una referencia al objeto que los posee y puede modificarlo, pero el GameObject en sí solo tiene una lista de componentes y no le importa cuáles sean los componentes siempre que puedan actualizarse cuando el objeto se actualice.

A veces, un componente tiene alguna información que GameObject necesita saber. Por ejemplo, para la detección de colisiones, un GameObject se registra con el subsistema de detección de colisiones que se notificará cuando choca con otro objeto. El subsistema de detección de colisiones necesita conocer el cuadro delimitador del objeto. Alto x e y en el objeto directamente (porque es utilizado por varios componentes), pero el ancho y la altura solo son conocidos por el componente de representación que contiene el mapa de bits del objeto. Me gustaría tener un método GetBoundingBox o GetWidth en GameObject que obtenga esa información. O en general, quiero enviar alguna información de un componente al objeto. Sin embargo, en mi diseño actual, GameObject no sabe qué componentes específicos tiene en la lista.

Puedo pensar en varias formas de resolver este problema:

  1. En lugar de tener una lista completamente genérica de componentes, puedo dejar que GameObject tenga un campo específico para algunos de los componentes importantes. Por ejemplo, puede tener una variable miembro llamada RendereringComponent; Siempre que necesito obtener el ancho del objeto que solo uso renderingComponent.getWidth(). Esta solución aún permite una lista genérica de componentes, pero trata a algunos de ellos de manera diferente, y me temo que terminaré teniendo varios campos excepcionales a medida que se deben consultar más componentes. Algunos objetos ni siquiera tienen componentes de representación.

  2. Tenga la información requerida como miembros del GameObject, pero permita que los componentes la actualicen. Por lo tanto, un objeto tiene un ancho y una altura que son 0 o -1 de forma predeterminada, pero un componente de representación puede establecerlos en los valores correctos en su bucle de actualización. Esto se siente como un truco y podría terminar llevando muchas cosas a la clase GameObject por conveniencia, incluso si no todos los objetos los necesitan.

  3. Haga que los componentes implementen una interfaz que indique para qué tipo de información se puede consultar. Por ejemplo, un componente de renderizado implementaría la interfaz Hassize que incluye métodos como GetWidth y Getheight. Cuando el juego de gameObject necesita el ancho, brota sobre sus componentes verificando si implementan la interfaz Hassize (usando el instanceof Palabra clave en Java, o is Cía#). Esto parece una solución más genérica, una desventaja es que buscar el componente podría llevar algún tiempo (pero luego, la mayoría de los objetos tienen solo 3 o 4 componentes).

Esta pregunta no se trata de un problema específico. A menudo aparece en mi diseño y me preguntaba cuál es la mejor manera de manejarlo. El rendimiento es algo importante ya que este es un juego, pero el número de componentes por objeto es generalmente pequeño (el máximo es 8).

La versión corta

En un sistema basado en componentes para un juego, ¿cuál es la mejor manera de pasar la información de los componentes al objeto mientras mantiene el diseño genérico?

¿Fue útil?

Solución

Obtenemos variaciones sobre esta pregunta tres o cuatro veces por semana en Gamedev.net (donde el GameObject generalmente se llama una 'entidad') y hasta ahora no hay consenso sobre el mejor enfoque. Se ha demostrado que varios enfoques diferentes son viables, por lo que no me preocuparía demasiado.

Sin embargo, generalmente los problemas consideran la comunicación entre componentes. Raramente se preocupan a las personas por obtener información de un componente a la entidad: si una entidad sabe qué información necesita, presumiblemente sabe exactamente a qué tipo de componente necesita acceder y a qué propiedad o método necesita llamar a ese componente para obtener los datos. Si necesita ser reactivo en lugar de activo, registre las devoluciones de llamada o tenga un patrón de observador configurado con los componentes para informar a la entidad cuándo ha cambiado algo en el componente, y lea el valor en ese punto.

Los componentes completamente genéricos son en gran medida inútiles: necesitan proporcionar algún tipo de interfaz conocida, de lo contrario, tiene poco sentido que existan. De lo contrario, también puede tener una gran variedad asociativa de valores sin tipo y hacerse con él. En Java, Python, C#y otros lenguajes de nivel ligeramente más altos que C ++, puede usar la reflexión para brindarle una forma más genérica de usar subclases específicas sin tener que codificar la información de tipo e interfaz en los componentes mismos.

En cuanto a la comunicación:

Algunas personas están haciendo suposiciones de que una entidad siempre contendrá un conjunto conocido de tipos de componentes (donde cada instancia es una de varias subclases posibles) y, por lo tanto, puede obtener una referencia directa al otro componente y leer/escribir a través de su interfaz pública.

Algunas personas están utilizando Publicar/Suscribirse, señales/ranuras, etc., para crear conexiones arbitrarias entre componentes. Esto parece un poco más flexible, pero en última instancia, aún necesita algo con conocimiento de estas dependencias implícitas. (Y si esto se conoce en el momento de la compilación, ¿por qué no usar el enfoque anterior?)

O bien, puede poner todos los datos compartidos en la entidad misma y usarlo como un área de comunicación compartida (tenuamente relacionado con el sistema de pizarra en ai) que cada uno de los componentes puede leer y escribir. Esto generalmente requiere cierta robustez frente a ciertas propiedades que no existen cuando esperaba que lo hicieran. Tampoco se presta al paralelismo, aunque dudo que sea una preocupación masiva en un pequeño sistema integrado ...?

Finalmente, algunas personas tienen sistemas donde la entidad no existe en absoluto. Los componentes viven dentro de sus subsistemas y la única noción de una entidad es un valor de identificación en ciertos componentes: si un componente de representación (dentro del sistema de representación) y un componente de reproductor (dentro del sistema de jugadores) tienen la misma identificación, entonces puede asumir El primero maneja el dibujo del segundo. Pero no hay ningún objeto que agregue ninguno de esos componentes.

Otros consejos

Como otros han dicho, no siempre hay una respuesta correcta aquí. Diferentes juegos se prestarán hacia diferentes soluciones. Si estás construyendo un gran juego complejo con muchos tipos diferentes de entidades, una arquitectura genérica más desacoplada con algún tipo de mensajería abstracta entre componentes puede valer la pena el esfuerzo por la mantenibilidad que obtienes. Para un juego más simple con entidades similares, puede tener el mayor sentido para empujar todo ese estado a GameObject.

Para su escenario específico en el que necesita almacenar la caja delimitadora en algún lugar y solo el componente de colisión se preocupa por ello, lo haría:

  1. Guárdelo en el componente de colisión en sí.
  2. Haga que el código de detección de colisión funcione directamente con los componentes.

Por lo tanto, en lugar de que el motor de colisión se ite a través de una colección de GameObjects para resolver la interacción, haga que se itera directamente a través de una colección de componentes de colisión. Una vez que haya ocurrido una colisión, dependerá del componente empujar eso a su GameObject matriz.

Esto te da un par de beneficios:

  1. Deja el estado específico de colisión fuera de GameObject.
  2. Te escabulle de iterar sobre GameObjects que no tienen componentes de colisión. (Si tiene muchos objetos no interactivos como los efectos visuales y la decoración, esto puede ahorrar un número decente de ciclos).
  3. Te escabulle de los ciclos de quemaduras caminando entre el objeto y su componente. Si itera a través de los objetos, entonces haz getCollisionComponent() En cada uno, ese seguimiento del puntero puede causar una falla de caché. Hacer eso para cada cuadro para cada objeto puede grabar mucha CPU.

Si estás interesado, tengo más sobre este patrón aquí, aunque parece que ya entiendes la mayor parte de lo que hay en ese capítulo.

Usa un "bus de eventos". (Tenga en cuenta que probablemente no pueda usar el código tal como está, pero debería darle la idea básica).

Básicamente, cree un recurso central donde cada objeto pueda registrarse como un oyente y decir "si X sucede, quiero saber". Cuando algo sucede en el juego, el objeto responsable simplemente puede enviar un evento X al bus de eventos y todas las partes interesantes lo notarán.

Editar] Para una discusión más detallada, ver paso de mensajes (gracias a snk_kid por señalar esto).

Un enfoque es inicializar un contenedor de componentes. Cada componente puede proporcionar un servicio y también puede requerir servicios de otros componentes. Dependiendo de su lenguaje y entorno de programación, debe encontrar un método para proporcionar esta información.

En su forma más simple, tiene conexiones individuales entre componentes, pero también necesitará conexiones de uno a muchas. Por ejemplo, el CollectionDetector tendrá una lista de componentes implementando IBoundingBox.

Durante la inicialización, el contenedor conectará las conexiones entre los componentes, y durante el tiempo de ejecución no habrá costo adicional.

Esto está cerca de su solución 3), espere que las conexiones entre los componentes se conecten solo una vez y no se verifiquen en cada iteración del bucle del juego.

los Marco de extensibilidad administrado para .NET es una buena solución a este problema. Me doy cuenta de que tiene la intención de desarrollar en Android, pero aún puede inspirarse en este marco.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top