Componentes del juego, administradores del juego y propiedades de objetos

15

Estoy tratando de entender el diseño de entidades basado en componentes.

Mi primer paso fue crear varios componentes que pudieran agregarse a un objeto. Para cada tipo de componente, tenía un administrador, que llamaría a la función de actualización de cada componente, pasando cosas como el estado del teclado, etc., según sea necesario.

Lo siguiente que hice fue eliminar el objeto y solo tener cada componente con un Id. Entonces, un objeto se define por componentes que tienen los mismos ID.

Ahora, estoy pensando que no necesito un administrador para todos mis componentes, por ejemplo, tengo un SizeComponent, que solo tiene una Sizepropiedad). Como resultado SizeComponent, no tiene un método de actualización, y el método de actualización del administrador no hace nada.

Mi primer pensamiento fue tener una ObjectPropertyclase que los componentes pudieran consultar, en lugar de tenerlos como propiedades de los componentes. Entonces un objeto tendría un número de ObjectPropertyy ObjectComponent. Los componentes tendrían una lógica de actualización que consulta las propiedades del objeto. El gerente administraría la llamada al método de actualización del componente.

Esto me parece una ingeniería excesiva, pero no creo que pueda deshacerme de los componentes, porque necesito una forma para que los gerentes sepan qué objetos necesitan qué lógica de componente ejecutar (de lo contrario, simplemente eliminaría el componente completamente e inserte su lógica de actualización en el administrador).

  1. Está presente (que tiene ObjectProperty, ObjectComponenty ComponentManagerclases) más de la ingeniería?
  2. ¿Cuál sería una buena alternativa?
George Duckett
fuente
1
Tienes la idea correcta al tratar de aprender el modelo de componente, pero necesitas comprender mejor lo que debe hacer, y la única forma de hacerlo es completar un juego [en su mayoría] sin usarlo. Creo que hacer una SizeComponentexageración es excesivo, puede suponer que la mayoría de los objetos tienen un tamaño, es el renderizado, la inteligencia artificial y la física donde se usa el modelo de componentes; El tamaño siempre se comportará igual, por lo que puede compartir ese código.
Jonathan Dickinson el
@ JonathanDickinson, @ Den: Creo que mi problema es dónde almaceno propiedades comunes. Por ejemplo, un objeto como posición, que es utilizado por a RenderingComponenty a PhysicsComponent. ¿Estoy pensando demasiado en la decisión de dónde colocar la propiedad? ¿Debo pegarlo en cualquiera de los dos y luego hacer que la otra consulta sea un objeto para el componente que tiene la propiedad necesaria?
George Duckett el
Mi comentario anterior, y el proceso de pensamiento detrás de esto, es lo que me está llevando a tener una clase separada para una propiedad (o un grupo de propiedades relacionadas quizás) que los componentes pueden consultar.
George Duckett el
1
Realmente me gusta esa idea, podría valer la pena intentarlo; pero tener un objeto para describir cada propiedad individual es realmente costoso. Puedes probar PhysicalStateInstance(uno por objeto) junto con un GravityPhysicsShared(uno por juego); Sin embargo, estoy tentado a decir que esto es aventurarse en los reinos de la euforia de los arquitectos, no se arme en un hoyo (exactamente lo que hice con mi primer sistema de componentes). BESO.
Jonathan Dickinson el

Respuestas:

6

La respuesta simple a su primera pregunta es Sí, está superando la ingeniería del diseño. El '¿Hasta qué punto desgloso las cosas?' La pregunta es muy común cuando se da el siguiente paso y se elimina el objeto central (generalmente llamado Entidad).

Cuando está desglosando los objetos a un nivel tan detallado que tiene un tamaño propio, el diseño ha ido demasiado lejos. Un valor de datos por sí solo no es un componente. Es un tipo de datos incorporado y a menudo se le puede llamar exactamente como lo que comenzó a llamarlos, una propiedad. Una propiedad no es un componente, pero un componente contiene propiedades.

Entonces, aquí hay algunas pautas que intento y sigo al desarrollar en un sistema de componentes:

  • No hay cuchara.
    • Este es el paso que ya ha dado para deshacerse del objeto central. Esto elimina todo el debate sobre lo que entra en el objeto Entity y lo que entra en un componente, ya que ahora todo lo que tienes son los componentes.
  • Los componentes no son estructuras.
    • Si divide algo en donde solo contiene datos, ya no es un componente, es solo una estructura de datos.
    • Un componente debe contener toda la funcionalidad necesaria para realizar una tarea muy específica de una manera específica.
    • La interfaz IRenderable proporciona la solución genérica para mostrar visualmente cualquier cosa en el juego. CRenderableSprite y CRenderableModel es una implementación de componentes de esa interfaz que proporciona los detalles para renderizar en 2D y 3D, respectivamente.
    • IUseable es la interfaz para algo con lo que un jugador puede interactuar. CUseableItem sería el componente que dispara el arma activa o bebe la poción seleccionada, mientras que CUseableTrigger podría ser donde un jugador va a saltar a una torreta o lanzar una palanca para soltar el puente levadizo.

Entonces, con la guía de componentes que no son estructuras, SizeComponent se ha desglosado demasiado. Contiene solo datos y lo que define el tamaño de algo puede variar. Por ejemplo, en un componente de representación podría ser un escalar 1d o un vector 2 / 3d. En un componente de física podría ser el volumen delimitador del objeto. En un artículo de inventario podría ser la cantidad de espacio que ocupa en una cuadrícula 2D.

Intenta trazar una buena línea entre teoría y practicidad.

Espero que esto ayude.

James
fuente
No olvidemos que en algunas plataformas, llamar a una función desde una Interfaz es más largo que llamar a una desde una Clase principal (ya que su respuesta incluye menciones de interfaces y clases)
ADB
Buen punto para recordar, pero estaba tratando de mantener el lenguaje agnóstico y solo usándolos en los términos generales de diseño.
James
"Si divide algo en donde solo contiene datos, ya no es un componente, es solo una estructura de datos". -- ¿Por qué? "Componente" es una palabra tan genérica que puede significar también estructura de datos.
Paul Manta el
@PaulManta Sí, es un término genérico, pero el punto completo de esta pregunta y respuesta es dónde trazar la línea. Mi respuesta, como usted citó, es solo mi sugerencia de una regla general para hacer precisamente eso. Como siempre, abogo por nunca dejar que la teoría o las consideraciones de diseño sean lo que impulsa el desarrollo, está destinado a ayudarlo.
James
1
@ James Esa fue una discusión interesante. :) chat.stackexchange.com/rooms/2175 Mi mayor queja si su implementación es que los componentes saben demasiado sobre qué otros componentes están interesados. Me gustaría continuar la discusión en el futuro.
Paul Manta
14

Ya aceptó una respuesta, pero aquí está mi puñalada en un CBS. Descubrí que una Componentclase genérica tiene algunas limitaciones, así que elegí un diseño descrito por Radical Entertainment en GDC 2009, que sugirió separar los componentes en Attributesy Behaviors. (" Teoría y práctica de la arquitectura de componentes de objetos de juego ", Marcin Chady)

Explico mis decisiones de diseño en un documento de dos páginas. Solo publicaré el enlace, ya que es demasiado largo para pegarlo aquí. Actualmente solo cubre los componentes lógicos (no también los componentes de representación y física), pero debería darle una idea de lo que intenté hacer:

http://www.pdf-archive.com/2012/01/08/entity-component-system/preview/page/1

Aquí hay un extracto del documento:

Atributos y comportamientos en resumen

Attributesadministrar una categoría de datos y cualquier lógica que tengan es de alcance limitado. Por ejemplo, Healthpuede asegurarse de que su valor actual nunca sea mayor que su valor máximo, e incluso puede notificar a otros componentes cuando el valor actual cae por debajo de cierto nivel crítico, pero no contiene ninguna lógica más compleja. Attributesno dependen de ningún otro Attributeso Behaviors.

Behaviorscontrole cómo reacciona la entidad a los eventos del juego, tome decisiones y modifique los valores Attributessegún sea necesario. Behaviorsdependen de algunos de ellos Attributes, pero no pueden interactuar directamente entre sí; solo reaccionan a cómo los Attributes’valores cambian entre sí Behaviorsy a los eventos que se envían.


Editar: Y aquí hay un diagrama de relación que muestra cómo los componentes se comunican entre sí:

Diagrama de comunicación entre atributos y comportamientos

Un detalle de implementación: el nivel de entidad EventManagersolo se crea si se usa. La Entityclase solo almacena un puntero a un EventManagerque se inicializa solo si algún componente lo solicita.


Editar: En otra pregunta, le di una respuesta similar a esta. Puede encontrarlo aquí para una, quizás, mejor explicación del sistema:
/gamedev//a/23759/6188

Paul Manta
fuente
2

Realmente depende de las propiedades que necesita y dónde las necesita. La cantidad de memoria que tendrá y la potencia / tipo de procesamiento que usará. He visto e intento hacer lo siguiente:

  • Las propiedades que son utilizadas por múltiples componentes pero modificadas solo por una se almacenan en ese componente. Shape es un buen ejemplo en un juego en el que el sistema de IA, el sistema de física y el sistema de representación necesitan acceso a la forma base, es una propiedad pesada y debe permanecer solo en un lugar si es posible.
  • Las propiedades como la posición a veces necesitan ser duplicadas. Por ejemplo, si ejecuta varios sistemas en paralelo, desea evitar mirar a través de los sistemas y más bien sincronizará la posición (copie desde el componente maestro o sincronice a través de deltas o con un pase de colisión si es necesario).
  • Las propiedades que se originan a partir de controles o "intenciones" de IA pueden almacenarse en un sistema dedicado, ya que pueden aplicarse a los otros sistemas sin ser visibles desde el exterior.
  • Las propiedades simples pueden volverse complicadas. A veces, su posición requerirá un sistema dedicado si necesita compartir muchos datos (posición, orientación, delta de cuadro, movimiento actual delta total, movimiento delta para el cuadro actual y para el cuadro anterior, rotación ...). En ese caso, tendrá que ir con el sistema y acceder a los últimos datos del componente dedicado y es posible que tenga que cambiarlo a través de acumuladores (deltas).
  • A veces, sus propiedades se pueden almacenar en una matriz sin formato (doble *) y sus componentes simplemente tendrán punteros a las matrices que contienen las diferentes propiedades. El ejemplo más obvio es cuando necesita cálculos paralelos masivos (CUDA, OpenCL). Por lo tanto, tener un sistema para administrar adecuadamente los punteros podría ser útil.

Estos principios tienen sus limitaciones. Por supuesto, tendrá que llevar la geometría al renderizador, pero probablemente no querrá recuperarla desde allí. La geometría maestra se almacenará en el motor de física en el caso de que ocurran deformaciones allí y se sincronice con el renderizador (de vez en cuando, dependiendo de la distancia de los objetos). Entonces, de alguna manera, lo duplicará de todos modos.

No hay sistemas perfectos. Y algunos juegos estarán mejor con un sistema más simple, mientras que otros requerirán sincronizaciones más complejas entre sistemas.

Al principio, asegúrese de que se puede acceder a todas las propiedades de manera simple desde sus componentes para que pueda cambiar la forma en que almacena las propiedades de forma transparente una vez que comience a ajustar sus sistemas.

No hay vergüenza en copiar algunas propiedades. Si algunos componentes necesitan mantener una copia local, a veces es más eficiente copiar y sincronizar en lugar de acceder a un valor "externo"

Además, la sincronización no tiene que suceder en cada cuadro. Algunos componentes se pueden sincronizar con menos frecuencia que otros. El componente de representación suele ser un buen ejemplo. Los que no interactúan con los jugadores se pueden sincronizar con menos frecuencia, al igual que los que están lejos. Los que están lejos y fuera del campo de la cámara pueden sincronizarse incluso con menos frecuencia.


Cuando se trata de su componente de tamaño, probablemente podría agruparse dentro de su componente de posición:

  • no todas las entidades con un tamaño tienen un componente de física, áreas por ejemplo, por lo que agruparlo con física no es lo mejor para usted.
  • el tamaño probablemente no importará sin una posición
  • Todos los objetos con una posición probablemente tendrán un tamaño (que puede usarse para scripts, física, IA, render ...).
  • el tamaño probablemente no se actualiza en cada ciclo, pero la posición podría serlo.

En lugar del tamaño, incluso podría usar un modificador de tamaño, podría ser más útil.

En cuanto al almacenamiento de todas las propiedades en un sistema de almacenamiento de propiedades genérico ... No estoy seguro de que vaya en la dirección correcta ... Céntrese en las propiedades que son fundamentales para su juego y cree componentes que agrupen tantas propiedades relacionadas como sea posible. Siempre que abstraiga el acceso a estas propiedades correctamente (a través de captadores en los componentes que los necesitan, por ejemplo), debería poder moverlos, copiarlos y sincronizarlos más tarde, sin romper demasiadas lógicas.

Coyote
fuente
2
Por cierto +1 o -1 yo porque mi representante actual es 666 desde el 9 de noviembre ... Es espeluznante.
Coyote
1

Si los componentes se pueden agregar arbitrariamente a las entidades, entonces necesita una forma de consultar si un componente determinado existe en una entidad y obtener una referencia a él. Por lo tanto, puede iterar sobre una lista de objetos derivados de ObjectComponent hasta encontrar el que desea y devolverlo. Pero devolvería un objeto del tipo correcto.

En C ++ o C # esto generalmente significa que tendría un método de plantilla en la entidad como T GetComponent<T>(). Y una vez que tenga esa referencia, sabrá exactamente qué datos de miembro tiene, así que acceda directamente a ella.

En algo como Lua o Python, no necesariamente tiene un tipo explícito de ese objeto, y probablemente tampoco le importe. Pero, de nuevo, puede acceder a la variable miembro y manejar cualquier excepción que surja al intentar acceder a algo que no está allí.

La consulta de propiedades de objetos suena explícitamente como la duplicación del trabajo que el lenguaje puede hacer por usted, ya sea en tiempo de compilación para los idiomas de tipo estático o en tiempo de ejecución para los de tipo dinámico.

Kylotan
fuente
Entiendo cómo obtener componentes fuertemente tipados de una entidad (usando genéricos o similares), mi pregunta es más sobre dónde deberían ir esas propiedades, particularmente dónde una propiedad es utilizada por múltiples componentes y no se puede decir que ningún componente sea el propietario. Vea mi tercer y cuarto comentario sobre la pregunta.
George Duckett el
Simplemente elija uno existente si encaja, o factorice la propiedad en un nuevo componente si no es así. Por ejemplo, Unity tiene un componente 'Transformar' que es solo la posición, y si algo más necesita alterar la posición del objeto, lo hacen a través de ese componente.
Kylotan
1

"Creo que, entonces, mi problema es dónde almaceno propiedades comunes. Por ejemplo, un objeto como una posición, que es utilizado por un componente de renderizado y un componente de física. ¿Estoy pensando demasiado en la decisión de dónde colocar la propiedad? en cualquiera de ellos, ¿tiene la otra consulta un objeto para el componente que tiene la propiedad necesaria? "

La cuestión es que RenderingComponent usa posición, pero PhysicsComponent lo proporciona . Solo necesita una manera de decirle a cada componente de usuario qué proveedor usar. Idealmente de una manera agnóstica, de lo contrario habrá una dependencia.

"... mi pregunta es más acerca de dónde deberían ir esas propiedades, particularmente donde una propiedad es utilizada por múltiples componentes y no se puede decir que ningún componente sea el propietario. Vea mi tercer y cuarto comentario sobre la pregunta".

No hay una regla común. Depende de la propiedad específica (ver arriba).

Crea un juego con una arquitectura fea pero basada en componentes y luego refactoriza.

Guarida
fuente
No creo entender lo que PhysicsComponent debería hacer entonces. Lo veo como la gestión de la simulación del objeto dentro de un entorno físico, lo que me lleva a esta confusión: no todas las cosas que deben representarse deberán simularse, por lo que me parece incorrecto agregar PhysicsComponentcuando agrego RenderingComponentporque contiene una posición que RenderingComponentutiliza Pude verme fácilmente terminando con una red de componentes interconectados, lo que significa que todos / la mayoría deben agregarse a cada entidad.
George Duckett el
Tuve una situación similar en realidad :). Tengo un PhysicsBodyComponent y un SimpleSpatialComponent. Ambos proporcionan posición, ángulo y tamaño. Pero el primero participa en la simulación física y tiene propiedades relevantes adicionales, y el segundo solo contiene esos datos espaciales. Si tiene su propio motor físico, incluso puede heredar el primero de este último.
Den
"Pude verme fácilmente terminando con una red de componentes interconectados, lo que significa que todos / la mayoría deben agregarse a cada entidad". Eso es porque no tienes un prototipo de juego real. Estamos hablando de algunos componentes básicos aquí. No es de extrañar que se usen en todas partes.
Den
1

Su instinto le está diciendo que el tener el ThingProperty, ThingComponenty ThingManagerpara cada Thingtipo de un componente poco exagerado. Creo que esta bien.

Pero, necesita alguna forma de realizar un seguimiento de los componentes relacionados en términos de qué sistemas los usan, a qué entidad pertenecen, etc.

TransformPropertyva a ser bastante común ¿Pero quién está a cargo de ello, el sistema de renderizado? El sistema de física? El sistema de sonido? ¿Por qué un Transformcomponente incluso necesitaría actualizarse?

La solución es eliminar cualquier tipo de código de sus propiedades fuera de getters, setters e inicializadores. Los componentes son datos, que los sistemas del juego utilizan para realizar diversas tareas como renderizado, IA, reproducción de sonido, movimiento, etc.

Lea sobre Artemis: http://piemaster.net/2011/07/entity-component-artemis/

Mire su código y verá que se basa en sistemas que declaran sus dependencias como listas de ComponentTypes. Escribe cada una de sus Systemclases y en el método constructor / init declara de qué tipos depende ese sistema.

Durante la configuración de niveles o cualquier otra cosa, crea sus entidades y les agrega componentes. Luego, le dice a esa entidad que informe a Artemis, y Artemis luego determina en función de la composición de esa entidad qué sistemas estarían interesados ​​en saber sobre esa entidad.

Luego, durante la fase de actualización de su ciclo, sus Systemcorreos electrónicos ahora tienen una lista de qué entidades actualizar. Ahora usted puede tener la granularidad de los componentes para que pueda idear sistemas locos, entidades construcción de una ModelComponent, TransformComponent, FliesLikeSupermanComponent, y SocketInfoComponent, y hacer algo extraño como marca un platillo volante que las moscas entre los clientes conectados a un juego de varios jugadores. De acuerdo, tal vez no sea eso, pero la idea es que mantiene las cosas desacopladas y flexibles.

Artemis no es perfecto, y los ejemplos en el sitio son un poco básicos, pero la separación de código y datos es poderosa. También es bueno para tu caché si lo haces bien. Artemis probablemente no lo haga bien en ese frente, pero es bueno aprender de eso.

michael.bartnett
fuente