¿Por qué es una mala idea almacenar métodos en entidades y componentes? (Junto con algunas otras preguntas del sistema de entidades).

16

Este es un seguimiento de esta pregunta, que respondí, pero esta aborda un tema mucho más específico.

Esta respuesta me ayudó a entender Entity Systems incluso mejor que el artículo.

Leí el (sí, el) artículo sobre Entity Systems, y me dijo lo siguiente:

Las entidades son solo una identificación y una matriz de componentes (los artículos dicen que almacenar entidades en componentes no es una buena manera de hacer las cosas, pero no proporciona una alternativa).
Los componentes son datos que indican lo que se puede hacer con una determinada entidad.
Los sistemas son los "métodos", realizan la manipulación de datos en entidades.

Esto parece realmente práctico en muchas situaciones, pero la parte de que los componentes son solo clases de datos me está molestando. Por ejemplo, ¿cómo podría implementar mi clase Vector2D (posición) en un sistema de entidades?

La clase Vector2D contiene datos: coordenadas xey, pero también tiene métodos , que son cruciales para su utilidad y distinguen la clase de una matriz de dos elementos. Ejemplo métodos son: add(), rotate(point, r, angle), substract(), normalize(), y todo otro estándar, métodos útiles, y absolutamente necesarios que las posiciones (que son instancias de la clase Vector2D) debería tener.

Si el componente fuera solo un titular de datos, ¡no podría tener estos métodos!

Una solución que probablemente podría aparecer sería implementarlos dentro de los sistemas, pero eso parece muy contrario a la intuición. Estos métodos son cosas que quiero realizar ahora , que estén completos y listos para usar. ¡No quiero esperar a MovementSystemque lean un conjunto caro de mensajes que le indican que realice un cálculo sobre la posición de una entidad!

Y, el artículo establece muy claramente que solo los sistemas deberían tener alguna funcionalidad, y la única explicación para eso, que pude encontrar, fue "evitar la POO". En primer lugar, no entiendo por qué debería abstenerme de utilizar métodos en entidades y componentes. La sobrecarga de memoria es prácticamente la misma, y ​​cuando se combina con sistemas, estos deberían ser muy fáciles de implementar y combinar de maneras interesantes. Los sistemas, por ejemplo, solo podían proporcionar lógica básica a entidades / componentes, que conocen la implementación por sí mismos. Si me preguntas, esto es básicamente tomar las cosas buenas de ES y OOP, algo que no se puede hacer de acuerdo con el autor del artículo, pero para mí parece una buena práctica.

Piensa en ello de esta manera; Hay muchos tipos diferentes de objetos dibujables en un juego. Plain Old imágenes, animaciones ( update(), getCurrentFrame(), etc.), combinaciones de estos tipos primitivos, y todos ellos podrían simplemente proporcionar un draw()método para hacer que el sistema, que entonces no necesita preocuparse por cómo se implementa el sprite de una entidad, sólo se sobre la interfaz (dibujar) y la posición. Y luego, solo necesitaría un sistema de animación que llamara métodos específicos de animación que no tienen nada que ver con el renderizado.

Y solo otra cosa ... ¿Existe realmente una alternativa a las matrices cuando se trata de almacenar componentes? No veo otro lugar para almacenar componentes que no sean matrices dentro de una clase de entidad ...

Tal vez, este es un mejor enfoque: almacenar componentes como propiedades simples de entidades. Por ejemplo, un componente de posición estaría pegado entity.position.

La única otra forma sería tener algún tipo de tabla de búsqueda extraña dentro de los sistemas, que haga referencia a diferentes entidades. Pero eso parece muy ineficiente y más complicado de desarrollar que simplemente almacenar componentes en la entidad.

jcora
fuente
Alexandre, ¿estás haciendo muchas ediciones para obtener otra insignia? Porque eso es travieso travieso, sigue golpeando una tonelada de hilos antiguos.
jhocking

Respuestas:

25

Creo que está totalmente bien tener métodos simples para acceder, actualizar o manipular los datos en los componentes. Creo que la funcionalidad que debe quedar fuera de los componentes es la funcionalidad lógica. Las funciones de utilidad están bien. Recuerde, el sistema de entidad-componente es solo una guía, no reglas estrictas que debe seguir. No salgas de tu camino para seguirlos. Si crees que tiene más sentido hacerlo de una manera, entonces hazlo de esa manera :)

EDITAR

Para aclarar, su objetivo no es evitar la POO . Eso sería bastante difícil en la mayoría de los idiomas comunes utilizados en estos días. Estás tratando de minimizar la herencia , que es un gran aspecto de la POO, pero no es obligatorio. Desea deshacerse de la herencia de Objeto-> Objeto móvil-> Criatura-> Bípedo-> Tipo humano.

Sin embargo, ¡está bien tener algo de herencia! Se trata de un lenguaje que está fuertemente influenciado por la herencia, es muy difícil no usarlo. Por ejemplo, puede tener una Componentclase o interfaz que todos sus otros componentes extiendan o implementen. El mismo trato con tu Systemclase. Esto hace las cosas mucho más fáciles. Le recomiendo que eche un vistazo al marco de Artemis . Es de código abierto y tiene algunos proyectos de ejemplo. Abre esas cosas y mira cómo funciona.

Para Artemis, las entidades se almacenan en una matriz, simple. Sin embargo, sus componentes se almacenan en una matriz o matrices (separadas de las entidades). La matriz de nivel superior agrupa la matriz de nivel inferior por tipo de componente. Por lo tanto, cada tipo de componente tiene su propia matriz. La matriz de nivel inferior está indexada por ID de entidad. (Ahora no estoy seguro si lo haría de esa manera, pero así es como se hace aquí). Artemis reutiliza las ID de entidad, por lo que la ID de entidad máxima no se hace mayor que su número actual de entidades, pero aún puede tener matrices dispersas si el componente no es un componente de uso frecuente. De todos modos, no lo separaré demasiado. Este método para almacenar entidades y sus componentes parece funcionar. Creo que sería un buen primer paso para implementar su propio sistema.

Las entidades y componentes se almacenan en un administrador separado.

La estrategia que menciona, hacer que las entidades almacenen sus propios componentes ( entity.position), es algo en contra del tema del componente de la entidad, pero es totalmente aceptable si cree que tiene más sentido.

MichaelHouse
fuente
1
Hmm, eso simplifica enormemente la situación, ¡gracias! Pensé que estaba sucediendo algo mágico de "te vas a arrepentir más tarde", ¡y no pude verlo!
jcora
1
Na, los uso totalmente en el sistema de componentes de mi entidad. Incluso tengo algunos componentes que heredan de un padre común, jadeo . Creo que lo único que lamentaría es si intentara evitar no usar métodos como ese. Se trata de hacer lo que tiene más sentido para ti. Si tiene sentido usar la herencia o poner algunos métodos en un componente, hágalo.
MichaelHouse
2
He aprendido de mi última respuesta sobre este tema. Descargo de responsabilidad: no digo que esta sea la forma de hacerlo. :)
MichaelHouse
1
Sí, sé lo desalentador que puede ser aprender un nuevo paradigma. ¡Afortunadamente, puedes usar aspectos del viejo paradigma para facilitar las cosas! He actualizado mi respuesta con información de almacenamiento. Si nos fijamos en Artemis, echa un vistazo a EntityManagercomo se almacenan las cosas.
MichaelHouse
1
¡Agradable! Será un motor bastante dulce cuando esté terminado. ¡Suerte con ello! Gracias por hacer preguntas interesantes.
MichaelHouse
10

"Ese" artículo no es uno con el que estoy particularmente de acuerdo, por lo que creo que mi respuesta será algo crítica.

Esto parece realmente práctico en muchas situaciones, pero la parte de que los componentes son solo clases de datos me está molestando. Por ejemplo, ¿cómo podría implementar mi clase Vector2D (posición) en un sistema de entidades?

La idea no es garantizar que nada en su programa sea otra cosa que un ID de entidad, componente o sistema; es garantizar que los datos y el comportamiento de la entidad se creen a través de la composición de objetos en lugar de usar un árbol de herencia complejo o peor aún tratar de pon todas las funcionalidades posibles en un solo objeto. Para implementar estos componentes y sistemas, seguramente tendrá datos normales como vectores que, en la mayoría de los idiomas, se representan mejor como una clase.

Ignore el bit en el artículo que sugiere que esto no es OOP, es tan OOP como cualquier otro enfoque. Cuando la mayoría de los compiladores o los tiempos de ejecución del lenguaje implementan métodos de objeto, es básicamente como cualquier otra función, excepto que hay un argumento oculto llamado thiso self, que es un puntero a un lugar en la memoria donde se almacenan los datos de ese objeto. En un sistema basado en componentes, la ID de entidad se puede utilizar para encontrar dónde están los componentes relevantes (y, por lo tanto, los datos) para una entidad determinada. Por lo tanto, la ID de la entidad es equivalente a un puntero this / self, y los conceptos son básicamente la misma cosa, solo se reorganizaron un poco.

Y, el artículo establece muy claramente que solo los sistemas deberían tener alguna funcionalidad, y la única explicación para eso, que pude encontrar, fue "evitar la POO". En primer lugar, no entiendo por qué debería abstenerme de utilizar métodos en entidades y componentes.

Bueno. Los métodos son una forma efectiva de organizar su código. Lo importante que debe eliminarse de la idea de "evitar POO" es evitar el uso de la herencia en todas partes para ampliar la funcionalidad. En cambio, divida la funcionalidad en componentes que se puedan combinar para hacer lo mismo.

Piensa en ello de esta manera; Hay muchos tipos diferentes de objetos dibujables en un juego. Imágenes antiguas simples, animaciones (update (), getCurrentFrame (), etc.), combinaciones de estos tipos primitivos, y todas ellas simplemente podrían proporcionar un método draw () para el sistema de render [...]

La idea de un sistema basado en componentes es que no tendrías clases separadas para estos, sino que tendrías una sola clase de Objeto / Entidad, y la imagen sería un Objeto / Entidad que tiene un ImageRenderer, las Animaciones serían un Objeto / Entidad que tiene un AnimationRenderer, etc. Los sistemas relevantes sabrían cómo representar estos componentes y, por lo tanto, no necesitaría ninguna clase base con un método Draw ().

[...] que entonces no necesita preocuparse por cómo se implementa el sprite de una entidad, solo por la interfaz (dibujar) y la posición. Y luego, solo necesitaría un sistema de animación que llamara métodos específicos de animación que no tienen nada que ver con el renderizado.

Claro, pero esto no funciona bien con los componentes. Tienes 3 opciones:

  • Cada componente implementa esta interfaz y tiene un método Draw (), incluso si no se dibuja nada. Si hiciera esto para cada funcionalidad, los componentes se verían bastante feos.
  • Solo los componentes que tienen algo que dibujar implementan la interfaz, pero ¿quién decide a qué componentes llamar Draw ()? ¿Un sistema tiene que consultar de alguna manera cada componente para ver qué interfaz es compatible? Eso sería propenso a errores y potencialmente difícil de implementar en algunos idiomas.
  • Los componentes solo son manejados por su propio sistema (que es la idea en el artículo vinculado). En cuyo caso, la interfaz es irrelevante porque un sistema sabe exactamente con qué clase o tipo de objeto está trabajando.

Y solo otra cosa ... ¿Existe realmente una alternativa a las matrices cuando se trata de almacenar componentes? No veo otro lugar para almacenar componentes que no sean matrices dentro de una clase de entidad ...

Puede almacenar los componentes en el sistema. La matriz no es el problema, sino dónde almacena el componente.

Kylotan
fuente
+1 Gracias por otro punto de vista. ¡Es bueno obtener algunos cuando se trata de un tema tan ambiguo! Si está almacenando componentes en el sistema, ¿eso significa que los componentes solo pueden ser modificados por un sistema? Por ejemplo, el sistema de dibujo y el sistema de movimiento accederían al componente de posición. ¿Dónde lo guardas?
MichaelHouse
Bueno, solo almacenarían un puntero a esos componentes, que pueden, siempre que me preocupe, estar en algún lugar ... Además, ¿por qué almacenarían componentes en los sistemas? ¿Hay alguna ventaja en eso?
jcora
¿Estoy en lo correcto, @Kylotan? Así es como lo haría, parece lógico ...
jcora
En el ejemplo de Adam / T-Machine, la intención es que haya 1 sistema por componente, pero un sistema ciertamente podría acceder y modificar otros componentes. (Esto dificulta los beneficios de subprocesamiento múltiple de los componentes, pero eso es un asunto diferente.)
Kylotan
1
El almacenamiento de componentes en el sistema permite una mejor localidad de referencia para ese sistema: ese sistema solo (generalmente) funciona con esos datos, entonces, ¿por qué recorrer toda la memoria de su computadora de entidad a entidad para obtenerlos? También ayuda con la concurrencia, ya que podría poner un sistema completo y sus datos en un núcleo o procesador (o incluso en una computadora separada, en MMO). Nuevamente, estos beneficios disminuyen cuando 1 sistema accede a más de 1 tipo de componente, por lo que debe tenerse en cuenta al decidir dónde dividir las responsabilidades del componente / sistema.
Kylotan
2

Un vector son datos. Las funciones se parecen más a las funciones de utilidad: no son específicas de esa instancia de los datos, se pueden aplicar a todos los vectores de forma independiente. Una buena manera de pensarlo es: ¿pueden reescribirse estas funciones como métodos estáticos? Si es así, es solo utilidad.

Matt Kemp
fuente
Lo sé, pero el problema es que los métodos de llamada son más rápidos y un sistema puede realizarlos en el acto o cualquier otra cosa que pueda necesitar manipular la posición de una entidad. Le expliqué que, échale un vistazo, también, creo que la pregunta tiene mucho más que esto.
jcora