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 attachComponent
mé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 InputHandlerComponent
qui 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.
la source
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]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 : IMessage
mé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
UserInputMessage
image 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.
la source