Comment les composants physiques ou graphiques sont-ils généralement intégrés dans un système orienté composants?

19

J'ai passé les 48 dernières heures à lire sur les systèmes de composants d'objet et je sens que je suis assez prêt pour commencer à l'implémenter. J'ai créé les classes d'objet et de composant de base, mais maintenant que je dois commencer à créer les composants réels, je suis un peu confus. Quand je pense à eux en termes de HealthComponent ou quelque chose qui ne serait fondamentalement qu'une propriété, c'est parfaitement logique. Quand c'est quelque chose de plus général en tant que composant physique / graphique, je suis un peu confus.

Ma classe d'objet ressemble à ceci jusqu'à présent (si vous remarquez des changements que je devrais faire s'il vous plaît faites le moi savoir, encore nouveau à cela) ...

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;
};

Maintenant, le problème que je rencontre est ce que la population générale mettrait dans des composants tels que la physique / graphique? Pour Ogre (mon moteur de rendu), les objets visibles seront constitués de plusieurs Ogre :: SceneNode (éventuellement plusieurs) pour le joindre à la scène, Ogre :: Entity (éventuellement plusieurs) pour afficher les maillages visibles, etc. Serait-il préférable d'ajouter simplement plusieurs GraphicComponent à l'objet et de laisser chaque GraphicComponent gérer un SceneNode / Entity ou l'idée d'avoir un de chaque composant est-elle nécessaire?

Pour la physique, je suis encore plus confus. Je suppose que peut-être créer un RigidBody et garder une trace de la masse / interia / etc. serait logique. Mais j'ai du mal à penser à comment réellement mettre des détails dans un composant.

Une fois que j'aurai fait quelques-uns de ces composants "requis", je pense que cela aura beaucoup plus de sens. En ce moment même si je suis encore un peu perplexe.

Aidan Knight
la source

Réponses:

32

Tout, vous d' abord lorsque les systèmes à base de composants construction, vous ne devez prendre l'approche de transformer tout en un composant. En fait, vous ne devriez généralement pas - c'est quelque chose d'une erreur néophyte. D'après mon expérience, la meilleure façon de lier les systèmes de rendu et de physique dans une architecture basée sur les composants est de faire de ces composants un peu plus que des conteneurs muets pour le véritable objet primitif de rendu ou de physique.

Cela a l'avantage supplémentaire de maintenir les systèmes de rendu et de physique découplés du système de composants.

Pour les systèmes de physique, par exemple, le simulateur de physique conserve généralement une grande liste d'objets corporels rigides qu'il manipule à chaque fois qu'il se déclenche. Votre système de composants ne dérange pas cela - votre composant physique stocke simplement une référence au corps rigide qui représente les aspects physiques de l'objet auquel le composant appartient, et traduit tous les messages du système de composants en manipulations appropriées du corps rigide.

De même avec le rendu - votre moteur de rendu aura quelque chose qui représente les données d'instance à rendre, et votre composant visuel conserve simplement une référence à cela.

C'est relativement simple - pour une raison quelconque, cela semble dérouter les gens, mais c'est souvent parce qu'ils y réfléchissent trop. Il n'y a pas beaucoup de magie là-bas. Et comme il est plus important de faire avancer les choses plutôt que d'agoniser sur la conception parfaite, vous devriez fortement envisager une approche où au moins certains des composants ont des interfaces définies et sont des membres directs de l'objet de jeu, comme l'a suggéré momboco. C'est une approche tout aussi valable et elle a tendance à être plus facile à comprendre.

Autres commentaires

Ceci n'est pas lié à vos questions directes, mais ce sont des choses que j'ai remarquées en regardant votre code:

Pourquoi mélangez-vous Ogre :: String et std :: string dans votre objet de jeu, qui est apparemment un objet non graphique et ne devrait donc pas dépendre d'Ogre? Je suggère de supprimer toutes les références directes à Ogre, sauf dans le composant de rendu lui-même, où vous devez effectuer votre transformation.

update () est un pur virtuel, ce qui implique qu'il faut sous-classer GameObject, ce qui est en fait exactement à l'opposé de la direction dans laquelle vous voulez aller avec une architecture de composant. Le point principal de l'utilisation des composants est de préférer l'agrégation de fonctionnalités plutôt que l'héritage.

Vous pourriez bénéficier d'une certaine utilisation de const(en particulier lorsque vous revenez par référence). De plus, je ne suis pas sûr que vous vouliez vraiment que la vélocité soit un scalaire (ou si elle, et sa position, devraient être présentes dans l'objet de jeu dans le type de méthodologie de composant trop générique que vous semblez rechercher).

Votre approche consistant à utiliser une grande carte de composants et un update()appel dans l'objet de jeu est tout à fait sous-optimale (et un écueil courant pour ceux qui construisent ce type de systèmes pour la première fois). Cela rend la cohérence du cache très médiocre pendant la mise à jour et ne vous permet pas de profiter de la simultanéité et de la tendance vers un processus de style SIMD de grands lots de données ou de comportement à la fois. Il est souvent préférable d'utiliser une conception où l'objet de jeu ne met pas à jour ses composants, mais plutôt le sous-système responsable du composant lui-même les met à jour en même temps. Cela pourrait valoir la peine d'être lu.


la source
Cette réponse était excellente. Je n'ai toujours pas trouvé de moyen d'espacer les paragraphes dans les commentaires (la touche Entrée ne fait que soumettre), mais je vais aborder ces réponses. Votre description du système objet / composant avait beaucoup de sens. Donc, juste pour clarifier, dans le cas d'un composant physique, dans le constructeur du composant, je pourrais simplement appeler la fonction CreateRigidBody () de mon PhysicsManager, puis stocker une référence à celle-ci dans le composant?
Aidan Knight
En ce qui concerne la partie "Autres commentaires", merci pour cela. J'ai mélangé des chaînes parce que je préfère généralement utiliser toutes les fonctions Ogre comme base pour mes projets qui utilisent Ogre, mais j'ai commencé à le basculer dans le système Object / Component car il manquait map / pair / etc. Vous avez raison cependant et je les ai convertis en membres std pour les séparer d'Ogre.
Aidan Knight
1
+1 C'est la première réponse sensée concernant un modèle basé sur les composants que j'ai lu ici depuis
longtemps
5
Il permet une meilleure cohérence (à la fois des données et dans le cache d'instructions) et vous offre la possibilité de décharger les mises à jour de manière presque triviale sur des threads distincts ou un processus de travail (SPU, par exemple). Dans certains cas, les composants n'ont même pas besoin d'être mis à jour de toute façon, ce qui signifie que vous pouvez éviter la surcharge d'un appel sans opération à une méthode update (). Vous aide également à créer des systèmes orientés données - il s'agit d'un traitement en masse, qui tire parti des tendances modernes de l'architecture CPU.
2
+1 pour "il est plus important de faire avancer les choses plutôt que d'agoniser sur le design parfait"
Trasplazio Garzuglio
5

L'architecture des composants est une alternative pour éviter les grandes hiérarchies obtenues héritant de tous les éléments d'un même noeud, et les problèmes de couplage qui en découlent.

Vous montrez une architecture de composants avec des composants "génériques". Vous avez la logique de base pour attacher et détacher des composants "génériques". Une architecture avec des composants génériques est plus difficile à réaliser car vous ne savez pas de quel composant il s'agit. Les avantages sont que cette approche est extensible.

Une architecture de composant valide est obtenue avec des composants définis. Avec défini, je veux dire, l'interface est définie, pas la mise en œuvre. Par exemple, GameObject peut avoir une IPhysicInstance, Transformation, IMesh, IAnimationController. Cela a l'avantage de connaître l'interface et le GameObject sait comment gérer chaque composant.

Répondre au terme de généralisation excessive

Je pense que les concepts ne sont pas clairs. Quand je dis composant, cela ne signifie pas que tous les composants d'un objet de jeu doivent hériter d'une interface de composant ou quelque chose de similaire. Cependant, c'est l'approche pour les composants génériques, je pense que c'est le concept que vous nommez en tant que composant.

L'architecture des composants est une autre des architectures qui existent pour le modèle d'objet d'exécution. Ce n'est pas le seul et ce n'est pas le meilleur pour tous les cas, mais l'expérience a démontré que cela présente de nombreux avantages, comme un faible couplage.

La principale caractéristique qui distingue l'architecture des composants est de convertir la relation is-a en has-a. Par exemple, une architecture d'objet est-a:

est une architecture

Au lieu d'une architecture d'objet has-a (composants):

architecture has-a

Il existe également des architectures centrées sur la propriété:

Par exemple, une architecture d'objet normale est comme ceci:

  • Object1

    • Position = (0, 0, 0)
    • Orientation = (0, 43, 0)
  • Object2

    • Position = (10, 0, 0)
    • Orientation = (0, 0, 30)

Une architecture centrée sur la propriété:

  • Position

    • Object1 = (0, 0, 0)
    • Object2 = (10, 0, 0)
  • Orientation

    • Object1 = (0, 43, 0)
    • Object2 = (0, 0, 30)

C'est mieux l'architecture du modèle objet? Du point de vue des performances, il vaut mieux la propriété centrée car elle est plus compatible avec le cache.

Le composant fait référence à la relation is-a au lieu de has-a. Un composant peut être une matrice de transformation, un quaternion, etc. Mais la relation avec l'objet-jeu est une relation is-a, l'objet-jeu n'a pas la transformation car il est hérité. L'objet-jeu a (relation has-a) la transformation. La transformation est alors un composant.

L'objet de jeu est comme un hub. si vous voulez un composant qui est toujours là, alors peut-être que le composant (comme la matrice) devrait être à l'intérieur de l'objet et non un pointeur.

Il n'y a pas d'objet de jeu parfait dans tous les cas, cela dépend du moteur, des bibliothèques que le moteur utilise. Peut-être que je veux un appareil photo sans maillage. Le problème avec l'architecture is-a est que si l'objet de jeu a un maillage, la caméra qui hérite de l'objet de jeu doit avoir un maillage. Avec des composants, la caméra a une transformation, un contrôleur pour le mouvement et tout ce dont vous avez besoin.

Pour plus d'informations, le chapitre "14.2. Runtime Object Model Architecture" du livre "Game Engine Architecture" de "Jason Gregory" traite spécifiquement de ce sujet.

Les diagrammes UML en sont extraits

momboco
la source
Je ne suis pas d'accord avec le dernier paragraphe, il y a une tendance à une généralisation excessive. Le modèle basé sur les composants ne signifie pas que chaque fonctionnalité doit être un composant, il faut tout de même tenir compte des relations entre les fonctionnalités et les données. Un AnimationController sans maillage est inutile, alors mettez-le à sa place dans le maillage. Un Game-Object sans transformation n'est pas un Game-Object, il appartient par définition au monde 3D, alors mettez-le en tant que membre normal dans le Game-Object. Les règles normales de la fonctionnalité d'encapsulation et des données s'appliquent toujours.
Maik Semder
@Maik Semder: Je suis d'accord avec vous sur la généralisation excessive en termes généraux, mais je pense que le terme composant n'est pas clair. J'ai édité ma réponse. Lisez-le et donnez-moi vos impressions.
momboco
@momboco bien, vous répétez moins les mêmes points;) Transform et AnimationController sont toujours décrits comme des composants, ce qui à mon avis est une surutilisation du modèle de composant. La clé est de définir ce qui doit être mis en composante et non:
Maik Semder
Si quelque chose est nécessaire pour que le Game-Object (GO) fonctionne en premier lieu, en d'autres termes, s'il n'est pas facultatif, mais obligatoire, ce n'est pas un composant. Les composants sont censés être facultatifs. Le Transform est un bon exemple, il n'est pas du tout optionnel pour un GO, un GO sans Transform n'a aucun sens. D'un autre côté, tous les GO n'ont pas besoin de physique, ce qui peut donc être un composant.
Maik Semder
S'il y a une relation forte entre 2 fonctionnalités, comme entre AnimationController (AC) et Mesh, alors ne rompez en aucun cas cette relation en appuyant sur l'AC dans un composant, en revanche le Mesh est un composant parfait, mais un AC appartient dans le maillage.
Maik Semder