Introducción
Los sistemas de entidad-componente son una técnica arquitectónica orientada a objetos.
No existe un consenso universal de lo que significa el término, igual que la programación orientada a objetos. Sin embargo, está claro que los sistemas de entidad-componente están destinados específicamente como una alternativa arquitectónica a la herencia . Jerarquías de herencia son naturales para expresar lo que un objeto es , pero en ciertos tipos de software (como los juegos), que en lugar expresarían lo que un objeto hace .
Es un modelo de objeto diferente al de "clases y herencia", al que probablemente esté acostumbrado a trabajar en C ++ o Java. Las entidades son tan expresivas como las clases, al igual que los prototipos como en JavaScript o Self: todos estos sistemas se pueden implementar en términos unos de otros.
Ejemplos
Vamos a decir que Player
es una entidad con Position
, Velocity
y KeyboardControlled
componentes, que hacen las cosas obvias.
entity Player:
Position
Velocity
KeyboardControlled
Sabemos que Position
debe verse afectado por Velocity
y Velocity
por KeyboardControlled
. La pregunta es cómo nos gustaría modelar esos efectos.
Entidades, Componentes y Sistemas
Supongamos que los componentes no tienen referencias entre sí; un Physics
sistema externo atraviesa todos los Velocity
componentes y actualiza los Position
de la entidad correspondiente; un Input
sistema atraviesa todos los KeyboardControlled
componentes y actualiza el Velocity
.
Player
+--------------------+
| Position | \
| | Physics
/ | Velocity | /
Input | |
\ | KeyboardControlled |
+--------------------+
Esto satisface los criterios:
Los sistemas ahora son responsables de manejar eventos y promulgar el comportamiento descrito por los componentes. También son responsables de manejar las interacciones entre entidades, como las colisiones.
Entidades y Componentes
Sin embargo, supongamos que los componentes de hacerlo tienen referencias a los otros. Ahora la entidad es simplemente un constructor que crea algunos componentes, los une y gestiona sus vidas:
class Player:
construct():
this.p = Position()
this.v = Velocity(this.p)
this.c = KeyboardControlled(this.v)
La entidad ahora puede enviar eventos de entrada y actualización directamente a sus componentes. Velocity
respondería a las actualizaciones y KeyboardControlled
respondería a las entradas. Esto aún satisface nuestros criterios:
Aquí las interacciones de los componentes son explícitas, no impuestas desde el exterior por un sistema. Los datos que describen un comportamiento (¿cuál es la cantidad de velocidad?) Y el código que lo promulga (¿qué es la velocidad?) Están acoplados, pero de manera natural. Los datos se pueden ver como parámetros para el comportamiento. Y algunos componentes no actúan en absoluto: a Position
es el comportamiento de estar en un lugar .
Las interacciones pueden manejarse a nivel de la entidad ("cuando una Player
colisión con un Enemy
...") o al nivel de componentes individuales ("cuando una entidad con una Life
colisión con una entidad con Strength
...").
Componentes
¿Cuál es la razón por la cual la entidad existe? Si es simplemente un constructor, entonces podemos reemplazarlo con una función que devuelve un conjunto de componentes. Si luego queremos consultar entidades por su tipo, también podemos tener un Tag
componente que nos permita hacer exactamente eso:
function Player():
t = Tag("Player")
p = Position()
v = Velocity(p)
c = KeyboardControlled(v)
return {t, p, v, c}
Las interacciones ahora deben ser manejadas por consultas abstractas, desacoplando completamente los eventos de los tipos de entidad. No hay más tipos de entidades para consultar: los Tag
datos arbitrarios probablemente se usan mejor para la depuración que la lógica del juego.
Conclusión
Las entidades no son funciones, reglas, actores o combinadores de flujo de datos. Son sustantivos que modelan fenómenos concretos; en otras palabras, son objetos. Es como dice Wikipedia: los sistemas de entidad-componente son un patrón de arquitectura de software para modelar objetos generales.
NO.¡Y estoy sorprendido de cuántas personas votaron de otra manera!
Paradigma
Está orientado a datos, también conocido como impulsado por datos, porque estamos hablando de la arquitectura y no del lenguaje en el que está escrito. Las arquitecturas son realizaciones de estilos o paradigmas de programación , que generalmente pueden ser desaconsejados en un lenguaje determinado.
¿Funcional?
Su comparación con la programación funcional / de procedimiento es una comparación relevante y significativa. Tenga en cuenta, sin embargo, que un lenguaje "funcional" es diferente del paradigma "procesal" . Y puede implementar un ECS en un lenguaje funcional como Haskell , que la gente ha hecho.
Donde ocurre la cohesión
Su observación es relevante y acertada :
ECS / ES no es EC / CE
Hay una diferencia entre las arquitecturas basadas en componentes, "Entity-Component" y "Entity-Component-System". Como este es un patrón de diseño en evolución, he visto que estas definiciones se usan indistintamente. Las arquitecturas "EC" o "CE" o "Componente-entidad" ponen el comportamiento en los componentes , mientras que las arquitecturas "ES" o "ECS" ponen el comportamiento en los sistemas . Aquí hay algunos artículos de ECS, los cuales usan una nomenclatura engañosa, pero transmiten la idea general:
Si está tratando de entender estos términos en 2015, asegúrese de que la referencia de alguien al "Sistema de componentes de la entidad" no signifique "arquitectura de componentes de la entidad".
fuente
Los sistemas de componentes de entidad (ECS) se pueden programar de manera funcional o de OOP, dependiendo de cómo se defina el sistema.
OOP manera:
He trabajado en juegos donde una entidad era un objeto compuesto de varios componentes. La entidad tiene una función de actualización que modifica el objeto en su lugar llamando a la actualización en todos sus componentes a su vez. Este es claramente un estilo OOP: el comportamiento está vinculado a los datos y los datos son mutables. Las entidades son objetos con constructores / destructores / actualizaciones.
Forma más funcional:
Una alternativa es que la entidad sea datos sin ningún método. Esta entidad puede existir por derecho propio o simplemente ser una identificación que está vinculada a varios componentes. De esta manera es posible (pero no se hace comúnmente) ser completamente funcional y tener entidades inmutables y sistemas puros que generen nuevos estados componentes.
Parece (por experiencia personal) que esta última forma está ganando más tracción y por buenas razones. La separación de los datos de la entidad del comportamiento da como resultado un código más flexible y reutilizable (imo). En particular, el uso de sistemas para actualizar componentes / entidades en lotes puede ser más eficaz y evita por completo las complejidades de la mensajería entre entidades que afectan a muchos OOP ECS.
TLDR: puede hacerlo de cualquier manera, pero diría que los beneficios de los sistemas de componentes de buena entidad se derivan de su naturaleza más funcional.
fuente
Los sistemas de componentes orientados a datos pueden coexistir con paradigmas orientados a objetos: - Los sistemas de componentes se prestan al polimorfismo. - Los componentes pueden ser tanto POD (datos antiguos simples) como TAMBIÉN objetos (con una clase y métodos), y todo sigue estando 'orientado a datos', siempre que los métodos de clase de componentes solo manipulen datos propiedad del objeto local.
Si elige esta ruta, le recomiendo que evite el uso de métodos virtuales, porque si los tiene, su componente ya no es solo datos de componentes, además de que esos métodos cuestan más para llamar, esto no es COM. Mantenga sus clases de componentes limpias de cualquier referencia a cualquier cosa externa, como regla.
El ejemplo sería vec2 o vec3, un contenedor de datos con algunos métodos para tocar esos datos, y nada más.
fuente
Pienso en ECS como fundamentalmente distinto de OOP y tiendo a verlo de la misma manera que usted, más cercano a la naturaleza funcional o especialmente de procedimiento con una separación muy clara de los datos de la funcionalidad. También hay algo parecido a la programación de un tipo que trata con bases de datos centrales. Por supuesto, soy la peor persona cuando se trata de definiciones formales. Solo me preocupa cómo tienden a ser las cosas, no cómo se definen conceptualmente.
Asumo un tipo de ECS donde los componentes agregan campos de datos y los hacen accesibles públicamente / globalmente, las entidades agregan componentes y los sistemas proporcionan funcionalidad / comportamiento en esos datos. Eso lleva a características arquitectónicas radicalmente difíciles de lo que normalmente llamaríamos una base de código orientada a objetos.
Y, por supuesto, hay un poco de confusión en los límites en la forma en que las personas diseñan / implementan un ECS, y hay un debate sobre qué constituye exactamente un ECS en primer lugar. Sin embargo, tales límites también están borrosos en el código escrito en lo que llamamos lenguajes funcionales o de procedimiento. Entre toda esta confusión, la constante fundamental de un ECS con una separación de datos de la funcionalidad me parece mucho más cercana a la programación funcional o de procedimiento que a la OOP.
Una de las principales razones por las que no creo que sea útil considerar que ECS pertenece a una clase de OOP es que la mayoría de las prácticas de SE asociadas con OOP giran en torno a la estabilidad de la interfaz pública, con funciones de modelado de interfaces públicas , no datos. La idea fundamental es que la mayor parte de las dependencias públicas fluyen hacia funciones abstractas, no hacia datos concretos. Y debido a eso, OOP tiende a hacer que sea muy costoso cambiar los comportamientos fundamentales de diseño, a la vez que es muy barato cambiar detalles concretos (como los datos y el código necesarios para implementar la funcionalidad).
ECS es radicalmente diferente a este respecto, considerando cómo se acoplan las cosas a medida que la mayor parte de las dependencias públicas fluyen hacia datos concretos: de sistemas a componentes. Como resultado, cualquier práctica de SE asociada con ECS giraría en torno a la estabilidad de los datos , porque las interfaces (componentes) más públicas y más utilizadas son en realidad solo datos.
Como resultado, un ECS hace que sea muy fácil hacer cosas como sustituir un motor de renderizado OpenGL por uno DirectX, incluso si los dos se implementan con una funcionalidad radicalmente diferente y no comparten los mismos diseños, siempre que el motor DX y GL tener acceso a los mismos datos estables. Mientras tanto, sería muy costoso y requeriría reescribir un montón de sistemas para cambiar, por ejemplo, la representación de datos de a
MotionComponent
.Eso es muy opuesto a lo que tradicionalmente asociamos con OOP, al menos en términos de características de acoplamiento y lo que constituye "interfaz pública" frente a "detalles de implementación privada". Por supuesto, en ambos casos los "detalles de implementación" son fáciles de cambiar, pero en ECS el diseño de los datos es costoso de cambiar (los datos no son detalles de implementación en ECS), y en OOP es el diseño de la funcionalidad lo que es costoso cambiar (el diseño de funciones no es un detalle de implementación en OOP). Esa es una idea muy diferente de los "detalles de implementación", y uno de los principales atractivos para mí de un ECS desde una perspectiva de mantenimiento fue que en mi dominio, los datos requeridos para hacer las cosas fueron más fáciles de estabilizar y diseñar correctamente de una vez por todas que las diversas cosas que podríamos hacer con esos datos (lo que cambiaría todo el tiempo a medida que los clientes cambiaran de opinión y surgieran nuevas sugerencias de usuarios). Como resultado, descubrí que los costos de mantenimiento se desplomaron cuando comenzamos a desviar las dependencias de las funciones abstractas hacia los datos crudos y centrales (pero aún con cuidado sobre qué sistemas acceden a qué componentes para permitir mantener invariantes en un grado razonable a pesar de todos los datos conceptualmente globalmente accesible).
Y al menos en mi caso, el SDK de ECS con la API y todos los componentes se implementan realmente en C y no se parecen a OOP. He encontrado que C es más que adecuado para tal propósito dada la falta inherente de OO en las arquitecturas ECS y el deseo de tener una arquitectura de complementos que pueda ser utilizada por la más amplia gama de lenguajes y compiladores. Los sistemas todavía se implementan en C ++ ya que C ++ hace que las cosas sean muy convenientes allí y los sistemas modelan la mayor parte de la complejidad y allí encuentro uso para muchas cosas que podrían considerarse más cercanas a OOP, pero eso es para detalles de implementación. El diseño arquitectónico en sí todavía se parece a un procedimiento muy C.
Así que creo que es algo confuso, al menos, tratar de decir que un ECS es OO por definición. Como mínimo, los fundamentos hacen cosas que son un giro completo de 180 grados de muchos de los principios fundamentales generalmente asociados con la POO, comenzando con la encapsulación y tal vez terminando con lo que se considerarían características de acoplamiento deseadas.
fuente