Conseils pour la gestion des messages du système d'entité basé sur les composants

8

J'essaie d'implémenter un système d'entités basé sur des composants mais je suis un peu confus sur la façon dont je dois gérer la messagerie. Il y a deux problèmes que je voudrais résoudre pour pouvoir tester le système. Voici le code que j'ai jusqu'à présent,

La classe Entity:

class Entity{
public:
    Entity(unsigned int id):
    id_(id)
    {};
    void handleMessage(BaseMessage &message){
        for(auto element: components_){
            element.second->handleMessage(message);
        }
    }
    template<class T>
    void attachComponent(T *component){
        //Consider making safer in case someone tries to attach same component type twice
        components_[typeid(T).hash_code()] = component;
    }
    template<class T>
    void detachComponent(void){
        components_.erase(typeid(T).hash_code());
    }
    template<class T>
    T* getComponent(void)const{
        return *components_.find(typeid(T).hash_code());
    }
    unsigned int getInstanceID(void)const{
        return id_;
    }
private:
    unsigned int id_;
    std::map<size_t, BaseComponent*> components_;
};

Les classes Composant de base et Message:

class BaseComponent{
public:
    virtual void handleMessage(BaseMessage &message){};
};

class BaseMessage{
public:
    virtual int getType(void) = 0;
};

1. Gestion du type de message

Ma première question est de savoir comment gérer les différents types de messages (dérivés de BaseMessage).

J'ai pensé à deux façons de gérer les types de messages des types de messages dérivés. L'une consiste à générer un hachage (c'est-à-dire en utilisant FNV) à partir d'une chaîne qui nomme le type de message et à utiliser ce hachage pour déterminer le type de message. Ainsi, la handleMessage(BaseMessage &message)fonction extraira d'abord ce hachage du message, puis effectuera un static_cast vers le type approprié.

La deuxième méthode consiste à utiliser un modèle comme suit (similaire aux attachComponentméthodes de la classe d'entité),

template<class T>
handleMessage(T& message){};

et faites des spécialisations pour chaque type de message que le composant spécifique va faire.

Y a-t-il des inconvénients à utiliser la deuxième méthode? Qu'en est-il des performances, pourquoi ne vois-je pas ce type d'utilisation plus souvent?

2. Gestion des entrées

Ma deuxième question est quelle serait la manière optimale (en termes de latence et de facilité d'utilisation) de gérer les entrées?

Ma pensée était de créer un InputHandlerComponentqui s'inscrit avec la classe de clavier pour écouter des pressions de touches spécifiques définies éventuellement dans un fichier. Par exemple

keyboard.register( player.getComponent<InputHandler>() , 'W')

J'aimerais qu'il y ait un guide plus concis des systèmes basés sur les composants, mais je suppose qu'il existe de nombreuses façons différentes de faire les mêmes choses. J'ai d'autres questions, mais je pense qu'il serait plus sage d'essayer d'abord de mettre en œuvre ce que je peux.

Grieverheart
la source

Réponses:

3

Dans un système d'entités typique, vous laissez les systèmes avoir toute la logique pour que votre jeu et vos composants stockent uniquement des données , et vos entités ne sont qu'un simple identifiant. La gestion des entrées serait beaucoup plus facile de cette façon et sans parler de la flexibilité de votre jeu.

Il existe de nombreuses façons de gérer les événements, tels que: le modèle d'observateur ou les signaux / emplacements. Vous pouvez gérer les messages avec des modèles / fonctions virtuelles ou des pointeurs de fonction, mais il serait probablement plus facile d'utiliser des signaux boost :: , même si ce n'est pas si bon pour les performances. Si vous voulez des performances *, je vous suggère d'utiliser des modèles et des fonctions virtuelles ou des pointeurs de fonction.

* J'ai remarqué que votre code pouvait vraiment être optimisé. Il existe des alternatives à l' typeidopérateur, qui sont en fait plus rapides que l'utilisation typeid. Telles que l'utilisation de modèles / macros et d'entiers simples pour définir l'ID d'une classe.

Vous pouvez regarder mon système d'entité si vous avez besoin d'inspiration (qui est inspiré du framework Java Artemis ). Bien que je n'aie pas mis en œuvre un moyen de gérer les événements (autres que les événements liés aux entités), j'ai laissé cela à l'utilisateur, mais après avoir étudié la bibliothèque entityx , que j'ai trouvée sur reddit. J'ai pensé que je pourrais être en mesure d'ajouter un système d'événements à ma bibliothèque. Veuillez noter que mon système d'entités n'est pas complet à 100% sur les fonctionnalités que je veux, mais il fonctionne et a des performances décentes (mais je pourrais optimiser, en particulier avec la façon dont je stocke les entités).

Voici ce que vous pourriez éventuellement faire pour gérer les événements (inspiré de entityx ):

BaseReceiver / BaseListener

  • Une classe de base pour stocker des écouteurs / récepteurs.

Récepteur / auditeur

  • Il s'agit d'une classe de modèle et vous oblige à remplacer la fonction virtuelle recieve(const T&), où T est l'information sur l'événement.
  • Les classes qui souhaitent être notifiées par des événements qui envoient des informations spécifiques lorsqu'un événement se produit doivent hériter de cette classe.

Gestionnaire d'événements

  • Émet / déclenche des événements
  • Dispose d'une liste d'objets Receivers / Listeners qui seront notifiés par les événements déclenchés

J'ai implémenté cette conception en C ++, tout à l'heure, sans utiliser de signaux boost :: . Vous pouvez le voir ici .

Avantages

  • Typé statiquement
  • Fonctions virtuelles (plus rapides que les signaux boost ::, ça devrait l'être)
  • Erreurs de compilation si vous n'avez pas émis correctement les notifications
  • Compatible C ++ 98 (je crois)

Les inconvénients

  • Vous oblige à définir des foncteurs (vous ne pouvez pas gérer les événements dans une fonction globale)
  • Pas de file d'attente d'événements; il suffit de vous inscrire et de tirer. (ce qui n'est peut-être pas ce que vous voulez)
  • Pas d'appels directs (ne devrait pas être si mauvais pour les événements)
  • Pas de gestion d'événements multiples sur la même classe (cela pourrait être modifié afin qu'il autorise plusieurs événements, via l'héritage multiple, mais peut prendre un certain temps à implémenter)

De plus, j'ai un exemple dans mon système d'entités, qui traite du rendu et de la gestion des entrées, c'est assez simple mais il présente de nombreux concepts pour comprendre ma bibliothèque.

miguel.martin
la source
Pouvez-vous donner un exemple d'un moyen plus rapide de découvrir des types sans typeid?
Luke B.16
"Dans un système d'entité typique, vous laissez les systèmes avoir toute la logique pour que votre jeu et vos composants ne stockent que des données" - il n'y a pas de système d'entité "typique".
Den
@LukeB. Regardez le code source de mon système d'entité, je ne l'utilise pas typeid(regardez Component et ComponentContainer). En gros, je stocke le "type" d'un composant comme un entier, j'ai un entier global que j'incrémente par type de composant. Et je stocke les composants dans un tableau 2D, où le premier index est l'ID de l'entité et le 2e index est l'ID du type de composant, c'est-à-dire les composants [entityId] [componentTypeId]
miguel.martin
@ miguel.martin. Merci pour votre réponse. Je pense que ma confusion vient principalement du fait qu'il existe plusieurs façons de mettre en œuvre le système d'entités et pour moi tous ont leurs inconvénients et leurs avantages. Par exemple, dans l'approche que vous utilisez, vous devez enregistrer de nouveaux types de composants à l'aide de CRTP, ce que je n'aime pas personnellement car cette exigence n'est pas explicite. Je pense que je vais devoir reconsidérer ma mise en œuvre.
Grieverheart
1
@MarsonMao fixed
miguel.martin
1

Je travaille sur un système d'entités basé sur des composants en C #, mais les idées et modèles généraux s'appliquent toujours.

La façon dont je gère les types de messages est que chaque sous-classe de composant appelle la RequestMessage<T>(Action<T> action) where T : IMessageméthode protégée de Component . En anglais, cela signifie que la sous-classe de composant demande un type de message spécifique et fournit une méthode à appeler lorsque le composant reçoit un message de ce type.

Cette méthode est stockée et lorsque le composant reçoit un message, il utilise la réflexion pour obtenir le type du message, qui est ensuite utilisé pour rechercher la méthode associée et l'invoquer.

Vous pouvez remplacer la réflexion par tout autre système que vous souhaitez, les hachages sont votre meilleur choix. Regardez dans le modèle de répartition et de visiteurs multiples pour d'autres idées.

En ce qui concerne la contribution, j'ai choisi de faire quelque chose de complètement différent de ce que vous pensez. Un gestionnaire d'entrée convertira séparément les entrées clavier / souris / manette de jeu en une énumération d'indicateurs (champ de bits) d'actions possibles (MoveForward, MoveBackwards, StrafeLeft, Use, etc.) en fonction des paramètres utilisateur / de ce que le joueur a branché sur l'ordinateur. Chaque composant obtient une UserInputMessageimage qui contient à la fois le champ de bits et l'axe de vision du joueur en tant que vecteur 3D (orientant le développement vers un jeu de tir à la première personne). Cela permet également aux joueurs de changer facilement leurs raccourcis clavier.

Comme vous l'avez dit à la fin de votre question, il existe de nombreuses façons différentes de créer un système d'entités basé sur des composants. J'ai fortement orienté mon système vers le jeu que je fais, donc naturellement certaines des choses que je fais peuvent ne pas avoir de sens dans le contexte, par exemple, d'un RTS, mais c'est toujours quelque chose qui a été mis en œuvre et qui fonctionne pour moi jusqu'à présent.

Robert Rouhani
la source