Système d'entités composants - Mises à jour et ordres d'appel

10

Afin d'obtenir des composants pour pouvoir mettre à jour chaque trame (et laisser cette fonctionnalité hors des composants qui n'en ont pas besoin), j'ai eu l'idée de créer un composant UpdateComponent. D'autres composants comme MovableComponent(qui détient la vitesse) hériteraient de la IUpdatableclasse abstraite. Cela oblige MovableComponentà implémenter une Update(gametime dt)méthode et une autre RegisterWithUpdater()qui donne UpdateComponentun pointeur vers le MovableComponent. De nombreux composants pourraient le faire et UpdateComponentpourraient ensuite appeler toutes leurs Update(gametime dt)méthodes sans avoir à se soucier de qui ou de ce qu'ils sont.

Mes questions sont:

  1. Cela semble-t-il quelque chose de normal ou utilisé par quelqu'un? Je ne trouve rien sur le sujet.
  2. Comment pourrais-je maintenir l'ordre des composants comme la physique puis changer de position? Est-ce même nécessaire?
  3. Quels sont les autres moyens de garantir que les composants qui doivent être traités à chaque image sont effectivement traités?

EDIT
Je pense que je vais réfléchir à la façon de donner au gestionnaire d'entités une liste de types pouvant être mis à jour. Ensuite, TOUS les composants de ce type peuvent être mis à jour plutôt que gérés par entité (qui ne sont de toute façon que des index dans mon système).

Encore. Mes questions restent valables pour moi. Je ne sais pas si c'est raisonnable / normal, ou ce que les autres ont tendance à faire.

De plus, les gens d' Insomniac sont géniaux! /ÉDITER

Code réduit pour l'exemple précédent:

class IUpdatable
{
public:
    virtual void Update(float dt) = 0;
protected:
    virtual void RegisterAsUpdatable() = 0;
};

class Component
{
    ...
};

class MovableComponent: public Component, public IUpdatable
{
public:
    ...
    virtual void Update(float dt);
private:
    ...
    virtual void RegisterWithUpdater();
};

class UpdateComponent: public Component
{
public:
    ...
    void UpdateAll();
    void RegisterUpdatable(Component* component);
    void RemoveUpdatable(Component* component);
private:
    ...
    std::set<Component*> updatables_;
};
ptpaterson
la source
Avez-vous un exemple pour un composant qui ne peut pas être mis à jour? Cela semble assez inutile. Placez la fonction de mise à jour en tant que fonction virtuelle dans le composant. Il suffit ensuite de mettre à jour tous les composants de l'entité, pas besoin du "UpdateComponent"
Maik Semder
Certains composants ressemblent davantage à des détenteurs de données. Comme PositionComponent. De nombreux objets peuvent avoir une position, mais s'ils sont immobiles, ce qui pourrait être une majorité, tous ces appels virtuels supplémentaires pourraient s'accumuler. Ou dites un HealthComponent. Cela n'a pas besoin de faire quoi que ce soit à chaque image, il suffit de le modifier quand il le faut. Peut-être que la surcharge n'est pas si mauvaise, mais mon UpdateComponent était une tentative de ne pas avoir Update () dans CHAQUE composant.
ptpaterson
5
Un composant qui ne contient que des données et aucune fonctionnalité pour les modifier dans le temps n'est par définition pas un composant. Il doit y en avoir fonctionnellement dans le composant qui change avec le temps, donc a besoin d'une fonction de mise à jour. Si votre composant n'a pas besoin d'une fonction de mise à jour, vous savez que ce n'est pas un composant et vous devez repenser la conception. Une fonction de mise à jour dans le composant de santé est logique, par exemple, vous voulez que votre PNJ guérisse des dégâts sur une certaine période.
Maik Semder
@Maik Mon point n'était PAS que ce sont des composants qui ne changeront / ne pourront jamais changer. Je suis d'accord, c'est idiot. Ils n'ont tout simplement pas besoin de mettre à jour chaque image, mais plutôt d'être notifiés pour modifier leurs informations si nécessaire. Dans le cas de la santé au fil du temps, il y aurait un composant bonus de santé qui a une mise à jour. Je ne pense pas qu'il y ait de raison de combiner les deux.
ptpaterson
Je voudrais également noter que le code que j'ai publié n'a que ce qui est nécessaire pour expliquer le concept UpdateComponent. Il exclut toute autre forme de communication entre les composants.
ptpaterson

Réponses:

16

L'un des principaux avantages d'un système de composants est la possibilité de tirer parti des modèles de mise en cache - bonne icache et prédiction car vous exécutez le même code plusieurs fois, bon dcache car vous pouvez allouer les objets dans des pools homogènes et parce que les vtables, si tout, restez au chaud.

De la façon dont vous avez structuré vos composants, cet avantage disparaît complètement et peut en fait devenir un handicap de performance par rapport à un système basé sur l'héritage, car vous effectuez beaucoup plus d'appels virtuels et plus d'objets avec vtables.

Ce que vous devez faire est de stocker des pools par type et d'itérer chaque type indépendamment pour effectuer des mises à jour.

Cela semble-t-il quelque chose de normal ou utilisé par quelqu'un? Je ne trouve rien sur le sujet.

Ce n'est pas aussi courant dans les grands jeux car ce n'est pas avantageux. C'est courant dans de nombreux jeux, mais ce n'est pas techniquement intéressant, donc personne n'écrit à ce sujet.

Comment pourrais-je maintenir l'ordre des composants comme la physique puis changer de position? Est-ce même nécessaire?

Le code dans des langages comme C ++ a un moyen naturel pour ordonner l'exécution: tapez les instructions dans cet ordre.

for (PhysicsComponent *c : physics_components)
    c->update(dt);
for (PositionComponent *c : position_components)
    c->update(dt);

En réalité, cela n'a pas de sens car aucun système physique robuste n'est structuré de cette façon - vous ne pouvez pas mettre à jour un seul objet physique. Au lieu de cela, le code ressemblerait davantage à:

physics_step();
for (PositionComponent *c : position_components)
    c->update(dt);
// Updates the position data from the physics data.

la source
Merci. J'ai commencé tout ce projet en ayant à l'esprit les données (pas les mêmes que celles basées sur les données), en évitant les erreurs de cache et autres. Mais une fois que j'ai commencé à coder, j'ai décidé de faire quelque chose qui fonctionnait avec dot com. Il s'avère que cela a rendu les choses plus difficiles pour moi. Et pour le sérieux, cet article insomniaque était super!
ptpaterson
@Joe +1 pour une très bonne réponse. Bien que j'aimerais savoir ce qu'est une icache et une décache. Merci
Ray Dey
Cache d'instructions et cache de données. Tout texte d'introduction sur l'architecture informatique devrait les couvrir.
@ptpaterson: Notez qu'il y a une petite erreur dans la présentation Insomniac - la classe de composants doit contenir son index de liste, ou vous avez besoin d'un mappage séparé des indices de pool aux indices de liste.
3

Je pense que ce dont vous parlez est raisonnable et assez courant. Cela pourrait vous donner plus d'informations.

munificent
la source
Je pense que je suis tombé sur cet article il y a quelques semaines. J'ai pu coder ensemble quelques versions très simples d'un système d'entités basé sur des composants, chacune très différente lorsque j'essaye des choses différentes. Essayer de rassembler tous les articles et exemples en quelque chose que je veux et que je peux faire. Merci!
ptpaterson
0

J'aime ces approches:

En bref: évitez de conserver le comportement de mise à jour dans les composants. Les composants ne sont pas un comportement. Le comportement (y compris les mises à jour) peut être implémenté dans certains sous-systèmes à instance unique. Cette approche pourrait également aider à traiter par lots des comportements similaires pour plusieurs composants (peut-être en utilisant les instructions parallel_for ou SIMD sur les données des composants).

L'idée IUpdatable et la méthode Update (gametime dt) semblent un peu trop restrictives et introduisent des dépendances supplémentaires. Cela peut être bien si vous n'utilisez pas l'approche des sous-systèmes, mais si vous les utilisez, IUpdatable est un niveau de hiérarchie redondant. Après tout, le MovingSystem doit savoir qu'il doit mettre à jour les composants Location et / ou Velocity directement pour toutes les entités qui ont ces composants, il n'y a donc pas besoin d'un composant IUpdatable intermédiaire.

Mais vous pouvez utiliser le composant Updatable comme mécanisme pour ignorer la mise à jour de certaines entités bien qu'elles aient des composants Location et / ou Velocity. Le composant Updatable pourrait avoir un indicateur booléen qui peut être défini falseet qui signalerait à chaque sous-système prenant en charge Updatable que cette entité particulière ne devrait actuellement pas être mise à jour (bien que dans un tel contexte, Freezable semble être le nom le plus approprié pour le composant).

JustAMartin
la source