¿Cómo se construyen típicamente los componentes físicos o gráficos en un sistema orientado a componentes?

19

He pasado las últimas 48 horas leyendo sobre los sistemas Object Component, y siento que estoy lo suficientemente preparado para comenzar a implementarlo. Obtuve las clases de objeto y componente base creadas, pero ahora que necesito comenzar a crear los componentes reales, estoy un poco confundido. Cuando pienso en ellos en términos de HealthComponent o algo que básicamente sería una propiedad, tiene mucho sentido. Cuando es algo más general como un componente de Física / Gráficos, me confundo un poco.

Mi clase de Objeto se ve así hasta ahora (si nota algún cambio que deba hacer, avíseme, aún nuevo en esto) ...

typedef unsigned int ID;

class GameObject
{
public:

    GameObject(ID id, Ogre::String name = "");
    ~GameObject();

    ID &getID();
    Ogre::String &getName();

    virtual void update() = 0;

    // Component Functions
    void addComponent(Component *component);
    void removeComponent(Ogre::String familyName);

    template<typename T>
    T* getComponent(Ogre::String familyName)
    {
        return dynamic_cast<T*>(m_components[familyName]);
    }

protected:

    // Properties
    ID m_ID;
    Ogre::String m_Name;
    float m_flVelocity;
    Ogre::Vector3 m_vecPosition;

    // Components
    std::map<std::string,Component*> m_components;
    std::map<std::string,Component*>::iterator m_componentItr;
};

Ahora, el problema con el que me estoy encontrando es ¿qué pondría la población general en Componentes como Física / Gráficos? Para Ogre (mi motor de renderizado), los Objetos visibles consistirán en múltiples Ogre :: SceneNode (posiblemente múltiples) para adjuntarlos a la escena, Ogre :: Entity (posiblemente múltiples) para mostrar las mallas visibles, y así sucesivamente. ¿Sería mejor simplemente agregar múltiples GraphicComponent al objeto y dejar que cada GraphicComponent maneje un SceneNode / Entity o es la idea de tener uno de cada Componente necesario?

Para Física estoy aún más confundido. Supongo que tal vez cree un RigidBody y realice un seguimiento de mass / interia / etc. Tendría sentido. Pero tengo problemas para pensar cómo poner realmente detalles específicos en un Componente.

Una vez que termine un par de estos componentes "Requeridos", creo que tendrá mucho más sentido. A partir de ahora, aunque todavía estoy un poco perplejo.

Aidan Knight
fuente

Respuestas:

32

En primer lugar, cuando crea sistemas basados ​​en componentes, no tiene que adoptar el enfoque de convertir todo en un componente. De hecho, generalmente no deberías, es algo así como un error neófito. En mi experiencia, la mejor manera de vincular los sistemas de representación y física en una arquitectura basada en componentes es hacer que esos componentes sean poco más que contenedores tontos para la representación real o el objeto físico primitivo.

Esto tiene la ventaja adicional de mantener los sistemas de renderizado y física desacoplados del sistema de componentes.

Para los sistemas de física, por ejemplo, el simulador de física generalmente mantiene una gran lista de objetos de cuerpo rígido que manipula cada vez que funciona. Su sistema de componentes no se mete con eso: su componente de física simplemente almacena una referencia al cuerpo rígido que representa los aspectos físicos del objeto al que pertenece el componente, y traduce cualquier mensaje del sistema de componentes en manipulaciones apropiadas del cuerpo rígido.

Del mismo modo con el renderizado: su renderizador tendrá algo que representa los datos de la instancia que se representará, y su componente visual simplemente se aferrará a una referencia a eso.

Es relativamente sencillo, por cualquier razón, parece confundir a las personas, pero eso es a menudo porque lo piensan demasiado. No hay mucha magia allí. Y dado que es más importante hacer las cosas en lugar de agonizar por el diseño perfecto, debe considerar un enfoque en el que al menos algunos de los componentes tengan interfaces definidas y sean miembros directos del objeto del juego, como ha sugerido momboco. Es un enfoque igual de válido y tiende a ser más fácil de asimilar.

Otros comentarios

Esto no está relacionado con sus preguntas directas, pero estas son cosas que noté al mirar su código:

¿Por qué estás mezclando Ogre :: String y std :: string en tu objeto de juego, que aparentemente es un objeto no gráfico y, por lo tanto, no debería depender de Ogre? Sugeriría eliminar todas las referencias directas a Ogre, excepto en el componente de representación en sí, donde debe hacer su transformación.

update () es un virtual puro, lo que implica que uno debe subclasificar GameObject, que en realidad es exactamente lo contrario de la dirección en la que desea ir con una arquitectura de componentes. El punto principal del uso de componentes es preferir la agregación de funcionalidad en lugar de la herencia.

Podría beneficiarse de algún uso de const(en particular cuando regresa por referencia). Además, no estoy seguro de que realmente quieras que la velocidad sea escalar (o si esta, y la posición, deben estar presentes en el objeto del juego en el tipo de metodología de componentes demasiado genérica que parece que estás buscando).

Su enfoque de utilizar un gran mapa de componentes y una update()llamada en el objeto del juego es bastante subóptimo (y un escollo común para los primeros que construyen este tipo de sistemas). Hace que la actualización de la caché sea muy pobre durante la actualización y no le permite aprovechar la concurrencia y la tendencia hacia el proceso SIMD de grandes lotes de datos o comportamiento a la vez. A menudo es mejor usar un diseño en el que el objeto del juego no actualice sus componentes, sino que el subsistema responsable del componente en sí los actualice todos a la vez. Esto podría valer la pena leerlo.


fuente
Esta respuesta fue excelente. Todavía no he descubierto una manera de espaciar los párrafos en los comentarios (ingrese la clave solo envía) pero abordaré estas respuestas. Su descripción del sistema Objeto / Componente tenía mucho sentido. Entonces, para aclarar, en el caso de un PhysicsComponent, en el constructor del componente, ¿podría llamar a la función CreateRigidBody () de mi PhysicsManager y luego almacenar una referencia a eso en el componente?
Aidan Knight
En cuanto a la parte de "Otros comentarios", gracias por esto. Mezclé cadenas porque generalmente prefiero usar todas las funciones de Ogre como base para mis proyectos que usan Ogre, pero comencé a cambiarlo en el sistema Objeto / Componente ya que carecía de mapa / par / etc. Sin embargo, tienes razón y los he convertido a los miembros estándar para separarlo de Ogre.
Aidan Knight
1
+1 Esa es la primera respuesta sensata con respecto a un modelo basado en componentes que leí aquí hace un tiempo, un buen contraste contra la generalización
excesiva
55
Permite una mejor coherencia (tanto de los datos como de la caché de instrucciones) y le ofrece la posibilidad de descargar casi trivialmente las actualizaciones en distintos hilos o procesos de trabajo (SPU, por ejemplo). En algunos casos, los componentes ni siquiera tendrían que actualizarse de todos modos, lo que significa que puede evitar la sobrecarga de una llamada no operativa a un método update (). También lo ayuda a construir sistemas orientados a datos: se trata de un procesamiento masivo, que aprovecha las tendencias modernas en la arquitectura de la CPU.
2
+1 para "es más importante hacer las cosas en lugar de agonizar por el diseño perfecto"
Trasplazio Garzuglio
5

La arquitectura de componentes es una alternativa para evitar las grandes jerarquías que se logran heredando todos los elementos de un mismo nodo, y los problemas de acoplamiento que conduce.

Está mostrando una arquitectura de componentes con componentes "genéricos". Tiene la lógica básica para conectar y desconectar componentes "genéricos". Una arquitectura con componentes genéricos es más difícil de lograr porque no sabes qué componente es. Los beneficios son que este enfoque es extensible.

Se logra una arquitectura de componentes válida con componentes definidos. Con definido quiero decir, la interfaz está definida, no la implementación. Por ejemplo, GameObject puede tener un IPhysicInstance, Transformation, IMesh, IAnimationController. Esto tiene la ventaja de que conoce la interfaz y GameObject sabe cómo manejar cada componente.

Respondiendo al término generalización excesiva

Creo que los conceptos no son claros. Cuando digo componente, no significa que todos los componentes de un objeto de juego deben heredar de una interfaz de componente o algo similar. Sin embargo, este es el enfoque para los componentes genéricos, creo que es el concepto que usted nombra como componente.

La arquitectura de componentes es otra de las arquitecturas que existen para el modelo de objetos en tiempo de ejecución. No es el único y no es el mejor para todos los casos, pero la experiencia ha demostrado que tiene muchos beneficios, como un bajo acoplamiento.

La característica principal que distingue la arquitectura del componente es convertir la relación is-a en has-a. Por ejemplo, una arquitectura de objeto es-a:

es una arquitectura

En lugar de una arquitectura de objeto has-a (componentes):

tiene una arquitectura

También hay arquitecturas centradas en la propiedad:

Por ejemplo, una arquitectura de objeto normal es así:

  • Objeto1

    • Posición = (0, 0, 0)
    • Orientación = (0, 43, 0)
  • Object2

    • Posición = (10, 0, 0)
    • Orientación = (0, 0, 30)

Una arquitectura centrada en la propiedad:

  • Posición

    • Objeto1 = (0, 0, 0)
    • Objeto2 = (10, 0, 0)
  • Orientación

    • Objeto1 = (0, 43, 0)
    • Objeto2 = (0, 0, 30)

¿Es mejor la arquitectura del modelo de objetos? En la perspectiva del rendimiento, es mejor centrado en la propiedad porque es más amigable con la caché.

El componente se refiere a la relación is-a en lugar de has-a. Un componente puede ser una matriz de transformación, un cuaternión, etc. Pero la relación con el objeto del juego es una relación es-a, el objeto del juego no tiene la transformación porque se hereda. El objeto del juego tiene (relación has-a) la transformación. La transformación es entonces un componente.

El objeto del juego es como un centro. si desea un componente que siempre esté ahí, entonces tal vez el componente (como la matriz) debería estar dentro del objeto y no un puntero.

No existe un objeto de juego perfecto para todos los casos, depende del motor, las bibliotecas que utiliza el motor. Tal vez quiero una cámara que no tenga malla. El problema con la arquitectura is-a es que si el objeto del juego tiene una malla, entonces la cámara que hereda del objeto del juego debe tener una malla. Con los componentes, la cámara tiene una transformación, un controlador para el movimiento y todo lo que necesita.

Para obtener más información, el capítulo "14.2. Arquitectura del modelo de objetos en tiempo de ejecución" del libro "Arquitectura del motor de juego" de "Jason Gregory" trata específicamente este tema.

Los diagramas UML se extraen de allí

momboco
fuente
No estoy de acuerdo con el último párrafo, hay una tendencia a generalizar en exceso. El modelo basado en componentes no significa que todas y cada una de las funcionalidades deben ser un componente, aún así uno debe considerar las relaciones de funcionalidades y datos. Un AnimationController sin Mesh es inútil, así que colóquelo donde pertenece, en la malla. Un Game-Object sin transformación no es un Game-Object, pertenece por definición al mundo 3D, así que colóquelo como un miembro normal en el Game-Object. Las reglas normales de la funcionalidad de encapsulación y los datos aún se aplican.
Maik Semder
@Maik Semder: Estoy de acuerdo con usted en la generalización excesiva en términos generales, pero creo que el término componente no está claro. He editado mi respuesta. Léelo y dame tus impresiones.
momboco
@momboco, más repite menos los mismos puntos;) Transform y AnimationController todavía se describen como componentes, lo que en mi opinión es un uso excesivo del modelo de componentes. La clave es definir qué se debe poner en un componente y qué no:
Maik Semder
Si se necesita algo para que el Game-Object (GO) funcione en primer lugar, en otras palabras, si no es opcional, sino obligatorio, entonces no es un componente. Se supone que los componentes son opcionales. La transformación es un buen ejemplo, no es opcional para un GO, un GO sin una transformación no tiene sentido. Por otro lado, no todos los GO necesitan física, por lo que puede ser un componente.
Maik Semder
Si hay una relación fuerte entre 2 funciones, como entre AnimationController (AC) y Mesh, entonces no rompa esta relación presionando el AC en un componente, en contraste, el Mesh es un componente perfecto, pero un AC pertenece en la malla.
Maik Semder