Estoy diseñando un motor de juego para un juego de disparos 2D multijugador de arriba hacia abajo, que quiero que sea razonablemente reutilizable para otros juegos de disparos de arriba hacia abajo. En este momento estoy pensando en cómo debería diseñarse algo así como un sistema de entidades. Primero pensé en esto:
Tengo una clase llamada EntityManager. Debe implementar un método llamado Update y otro llamado Draw. La razón por la que separo Logic y Rendering es porque entonces puedo omitir el método Draw si ejecuto un servidor independiente.
EntityManager posee una lista de objetos de tipo BaseEntity. Cada entidad posee una lista de componentes como EntityModel (la representación dibujable de una entidad), EntityNetworkInterface y EntityPhysicalBody.
EntityManager también posee una lista de administradores de componentes como EntityRenderManager, EntityNetworkManager y EntityPhysicsManager. Cada administrador de componentes mantiene referencias a los componentes de la entidad. Hay varias razones para mover este código fuera de la propia clase de la entidad y hacerlo colectivamente. Por ejemplo, estoy usando una biblioteca de física externa, Box2D, para el juego. En Box2D, primero agrega los cuerpos y las formas a un mundo (propiedad de EntityPhysicsManager en este caso) y agrega devoluciones de llamada de colisión (que se enviarían al objeto de entidad en mi sistema). Luego ejecuta una función que simula todo en el sistema. Me resulta difícil encontrar una mejor solución para hacerlo que hacerlo en un administrador de componentes externo como este.
La creación de entidades se realiza así: EntityManager implementa el método RegisterEntity (entityClass, factory) que registra cómo crear una entidad de esa clase. También implementa el método CreateEntity (entityClass) que devolvería un objeto de tipo BaseEntity.
Ahora viene mi problema: ¿cómo se registraría la referencia a un componente a los administradores de componentes? No tengo idea de cómo haría referencia a los gerentes de componentes de una fábrica / cierre.
fuente
Respuestas:
Los sistemas deben almacenar un par de valores clave de entidad a componente en algún tipo de mapa, objeto de diccionario o matriz asociativa (según el idioma utilizado). Además, cuando crea su Objeto de entidad, no me preocuparía almacenarlo en un administrador a menos que necesite poder cancelar el registro de cualquiera de los Sistemas. Una entidad es un compuesto de componentes, pero no debe manejar ninguna de las actualizaciones de componentes. Eso debería ser manejado por los Sistemas. En su lugar, trate su Entidad como una clave asignada a todos los componentes que contiene en los sistemas, así como un centro de comunicación para que esos componentes se comuniquen entre sí.
La gran parte de los modelos Entity-Component-System es que puede implementar la capacidad de pasar mensajes de un componente al resto de los componentes de una entidad con bastante facilidad. Esto permite que un componente hable con otro componente sin saber realmente quién es ese componente o cómo manejar el componente que está cambiando. En su lugar, pasa un mensaje y permite que el componente se cambie a sí mismo (si existe)
Por ejemplo, un sistema de posición no tendría mucho código, solo haría un seguimiento de los objetos de entidad asignados a sus componentes de posición. Pero cuando cambia una posición, pueden enviar un mensaje a la Entidad involucrada, que a su vez se entrega a todos los componentes de esa entidad. Una posición cambia por cualquier razón? El sistema de posición envía a la entidad un mensaje que dice que la posición cambió, y en algún lugar, el componente de representación de imagen de esa entidad recibe ese mensaje y se actualiza donde se dibujará a continuación.
Por el contrario, un sistema de física necesita saber qué están haciendo todos sus objetos; Debe poder ver todos los objetos del mundo para probar colisiones. Cuando ocurre una colisión, actualiza el componente de dirección de la Entidad enviando algún tipo de "Mensaje de Cambio de Dirección" a la entidad en lugar de referirse directamente al componente de la Entidad. Esto desacopla al administrador de la necesidad de saber cómo cambiar las direcciones mediante el uso de un mensaje en lugar de depender de un componente específico que esté allí (que puede no estar allí en absoluto, en cuyo caso el mensaje simplemente caería en oídos sordos en lugar de algún error que ocurre porque un objeto esperado estaba ausente).
Notará una gran ventaja de esto ya que mencionó que tiene una interfaz de red. Un componente de red escuchará todos los mensajes entrantes que todos los demás deberían conocer. Le encantan los chismes. Luego, cuando el sistema de red se actualiza, los componentes de red envían esos mensajes a otros sistemas de red en otras máquinas cliente, que luego reenvían esos mensajes a todos los demás componentes para actualizar las posiciones de los jugadores, etc. Es posible que se necesite una lógica especial para que solo ciertas entidades puedan enviar mensajes a través de la red, pero esa es la belleza del sistema, puede hacer que controle esa lógica al registrar las cosas correctas.
En breve:
La entidad es una composición de componentes que pueden recibir mensajes. La entidad puede recibir mensajes, delegando dichos mensajes a todos sus componentes para actualizarlos. (Mensaje de posición cambiada, Dirección de cambio de velocidad, etc.) Es como un buzón central en el que todos los componentes pueden escucharse entre sí en lugar de hablar directamente entre sí.
El componente es una pequeña parte de una entidad que almacena algún estado de la entidad. Estos son capaces de analizar ciertos mensajes y lanzar otros mensajes. Por ejemplo, un "Componente de dirección" solo se preocuparía por los "Mensajes de cambio de dirección" pero no por los "Mensajes de cambio de posición". Los componentes actualizan su propio estado en función de los mensajes y luego actualizan los estados de otros componentes enviando mensajes desde su Sistema.
El sistema gestiona todos los componentes de un determinado tipo y es responsable de actualizar dichos componentes en cada cuadro, así como de enviar mensajes de los componentes que gestionan a las Entidades a las que pertenecen los Componentes.
Los sistemas podrían actualizar todos sus componentes en paralelo y almacenar todos los mensajes a medida que avanzan. Luego, cuando se completa la ejecución de todos los métodos de actualización de los Sistemas, le pide a cada sistema que envíe sus mensajes en un orden específico. Los controles primero posiblemente, seguidos de Física, seguidos de dirección, posición, representación, etc. Importa en qué orden se envían ya que un Cambio de Dirección de Física siempre debe pesar un cambio de dirección basado en el control.
Espero que esto ayude. Es un gran patrón de diseño, pero es ridículamente poderoso si se hace correctamente.
fuente
Estoy usando un sistema similar en mi motor y la forma en que lo hice es que cada entidad contiene una lista de componentes. Desde el EntityManager, puedo consultar cada una de las Entidades y ver cuáles contienen un Componente dado. Ejemplo:
Obviamente, este no es el código exacto (en realidad necesitaría una función de plantilla para verificar los diferentes tipos de componentes, en lugar de usar
typeof
), pero el concepto está ahí. Luego puede tomar esos resultados, hacer referencia al componente que está buscando y registrarlo en su fábrica. Esto evita cualquier acoplamiento directo entre los Componentes y sus administradores.fuente
typedef long long int Entity
; un Componente es un registro (puede implementarse como una clase de objeto, o simplemente astruct
) que tiene una referencia a la Entidad a la que está adjunto; y un Sistema sería un método o una colección de métodos. El modelo ECS no es muy compatible con el modelo OOP, aunque un Componente puede ser un Objeto (en su mayoría) de solo datos, y un Sistema un Objeto singleton de solo código cuyo estado vive en componentes ... aunque los sistemas "híbridos" son más comunes que los "puros", pierden muchos de los beneficios innatos.1) Su método Factory debe pasar una referencia al EntityManager que lo llamó (usaré C # como ejemplo):
2) Haga que CreateEntity también reciba una identificación (por ejemplo, una cadena, un entero, depende de usted) además de la clase / tipo de la entidad, y registre automáticamente la entidad creada en un Diccionario usando esa identificación como clave:
3) Agregue un Getter a EntityManager para obtener cualquier entidad por ID:
Y eso es todo lo que necesita para hacer referencia a cualquier ComponentManager desde su método Factory. Por ejemplo:
Además de Id, también puede usar algún tipo de propiedad Type (una enumeración personalizada, o simplemente confiar en el sistema de tipos del idioma), y crear un captador que devuelva todas las BaseEntities de un tipo determinado.
fuente
typedef unsigned long long int EntityID;
:; lo ideal es que cada sistema pueda vivir en una CPU o host por separado, y solo requiera buscar componentes que sean relevantes / activos en ese sistema. Con un objeto Entity, uno podría tener que crear una instancia del mismo objeto Entity en cada host, lo que dificulta el escalado. Un modelo de entidad-componente-sistema puro divide el procesamiento entre nodos (procesos, CPU o hosts) por sistema, en lugar de por entidad, por lo general.