¿Organizar un sistema de entidades con gerentes de componentes externos?

13

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.

Gustav
fuente
No sé si quizás este sea un sistema híbrido, pero parece que sus "gerentes" son lo que generalmente he escuchado como "sistemas"; es decir, la entidad es una ID abstracta; un componente es un conjunto de datos; y lo que usted llama un "gerente" es lo que generalmente se denomina un "Sistema". ¿Estoy interpretando el vocabulario correctamente?
BRPocock
Mi pregunta aquí puede ser de interés: Componentes del juego, administradores de juegos y propiedades de objetos
George Duckett
Eche un vistazo a gamadu.com/artemis y vea si sus métodos responden a su pregunta.
Patrick Hughes
1
No hay una sola forma de diseñar un sistema de entidad, ya que hay poco consenso sobre su definición. Lo que @BRPocock describe y también lo que usa Artemis se ha descrito más en profundidad en este blog: t-machine.org/index.php/category/entity-systems junto con un wiki: entity-systems.wikidot.com
user8363

Respuestas:

6

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.

C0M37
fuente
0

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:

class Component
{
    private uint ID;
    // etc...
}

class Entity
{
    List<Component> Components;
    // etc...
    public bool Contains(Type type)
    {
        foreach(Component comp in Components)
        {
            if(typeof(comp) == type)
                return true;
        }
        return false;
    }
}

class EntityManager
{
    List<Entity> Entities;
    // etc...
    public List<Entity> GetEntitiesOfType(Type type)
    {
        List<Entity> results = new List<Entity>();
        foreach(Entity entity in Entities)
        {
            if(entity.Contains(type))
                results.Add(entity);
        }
        return results;
    }
}

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.

Mike Cluck
fuente
3
Advertencia: en el punto en que su entidad contiene datos, es un objeto, no una entidad ... Uno pierde la mayoría de los beneficios paralelos (¿sic?) De ECS en esta estructura. Los sistemas E / C / S "puros" son relacionales, no están orientados a objetos ... No es que sea necesariamente "malo" para algunos casos, pero ciertamente es "romper el modelo relacional"
BRPocock
2
No estoy seguro de entenderte. Mi comprensión (y corríjame si me equivoco) es que el Entity-Component-System básico tiene una clase Entity que contiene Componentes y podría tener una ID, nombre o algún identificador. Creo que podemos tener un malentendido en lo que quiero decir con "tipo" de entidad. Cuando digo Entidad "tipo", me refiero a los tipos de Componente. Es decir, una entidad es del tipo "Sprite" si contiene un componente Sprite.
Mike Cluck
1
En un sistema de entidad / componente puro, una entidad suele ser atómica: por ejemplo typedef long long int Entity; un Componente es un registro (puede implementarse como una clase de objeto, o simplemente a struct) 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.
BRPocock
2
@BRPocock re sistemas de entidades "puras". Creo que una entidad como objeto está perfectamente bien, no tiene que ser una identificación simple. Una cosa es la representación serializada, otra la disposición en memoria de un objeto / un concepto / una entidad. Mientras pueda mantener el manejo de datos, uno no debería estar vinculado a un código no idiomático solo porque es la forma "pura".
Raine
1
@BRPocock esta es una advertencia justa, pero para sistemas de entidades similares a "t-machine" Entiendo por qué, pero esas no son las únicas formas de modelar entidades basadas en componentes. Los actores son una alternativa interesante. Tiendo a apreciarlos más, especialmente para entidades puramente lógicas.
Raine
0

1) Su método Factory debe pasar una referencia al EntityManager que lo llamó (usaré C # como ejemplo):

delegate BaseEntity EntityFactory(EntityManager manager);

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:

class EntityManager
{
    // Rest of class omitted

    BaseEntity CreateEntity(string id, Type entityClass)
    {
        BaseEntity entity = factories[entityClass](this);
        registry.Add(id, entity);
        return entity;
    }

    Dictionary<Id, BaseEntity> registry;
}

3) Agregue un Getter a EntityManager para obtener cualquier entidad por ID:

class EntityManager
{
    // Rest of class omitted

    BaseEntity GetEntity(string id)
    {
        return registry[id];
    }
}

Y eso es todo lo que necesita para hacer referencia a cualquier ComponentManager desde su método Factory. Por ejemplo:

BaseEntity CreateSomeSortOfEntity(EntityManager manager)
{
    // Create and configure entity
    BaseEntity entity = new BaseEntity();
    RenderComponent renderComponent = new RenderComponent();
    entity.AddComponent(renderComponent);

    // Get a reference to the render manager and register component
    RenderEntityManager renderer = manager.GetEntity("RenderEntityManager") as RenderEntityManager;
    if(renderer != null)
        renderer.Register(renderComponent)

    return entity;
}

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.

David Gouveia
fuente
1
No para ser pedante, sino de nuevo ... en un sistema de entidad (relacional) puro, las entidades no tienen ningún tipo, excepto el que se les imparte en virtud de sus componentes ...
BRPocock
@BRPocock: ¿Podría crear un ejemplo que siga la pura virtud?
Zolomon
1
@Raine Quizás, no tengo experiencia de primera mano con esto, pero eso es lo que leí. Y hay optimizaciones que puede implementar para reducir el tiempo dedicado a buscar componentes por id. En cuanto a la coherencia de caché, creo que tiene sentido ya que está almacenando datos del mismo tipo de forma contigua en la memoria, especialmente cuando sus componentes son propiedades livianas o simples. He leído que una sola falla de caché en la PS3 puede ser tan costosa como mil instrucciones de CPU, y este enfoque de almacenar datos de forma similar de forma contigua es una técnica de optimización muy común en el desarrollo de juegos modernos.
David Gouveia
2
En ref: sistema de entidad "puro": la ID de entidad es típicamente algo así como 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.
BRPocock
1
@DavidGouveia mencionó "optimizaciones ... buscando entidades por ID". De hecho, los (pocos) sistemas que he implementado de esta manera, tienden a no hacerlo. Con mayor frecuencia, seleccione Componentes por algún patrón que indique que son de interés para un Sistema en particular, utilizando Entidades (ID) solo para uniones de componentes cruzados.
BRPocock