Alternatives flexibles à de nombreuses petites classes polymorphes (à utiliser comme propriétés, messages ou événements) C ++

9

Il y a deux classes dans mon jeu qui sont vraiment utiles, mais qui deviennent lentement une douleur. Message et propriété (la propriété est essentiellement un composant).

Ils dérivent tous les deux d'une classe de base et contiennent un identifiant statique afin que les systèmes ne puissent prêter attention qu'à ceux qu'ils souhaitent. Ça marche très bien ... sauf ...

Je crée constamment de nouveaux types de messages et de propriétés au fur et à mesure que j'étends mon jeu. Chaque fois, j'ai besoin d'écrire 2 fichiers (hpp et cpp) et une tonne de passe-partout pour obtenir essentiellement un classID et un ou deux types de données standard ou un pointeur.

Cela commence à faire du jeu et à tester de nouvelles idées une véritable corvée. Je souhaite quand je veux créer un nouveau type de message ou de propriété, je veux pouvoir simplement taper quelque chose comme

ShootableProperty:  int gunType, float shotspeed;

ItemCollectedMessage:  int itemType;

au lieu de créer un en-tête et un fichier cpp, écrire un constructeur, y compris la classe parente, etc.

C'est environ 20 à 40 lignes (y compris les gardes, et tout) juste pour faire ce qui est logiquement 1 ou 2 lignes dans mon esprit.

Existe-t-il un modèle de programmation pour contourner ce problème?

Qu'en est-il des scripts (dont je ne sais rien) ... existe-t-il un moyen de définir un tas de classes qui sont presque les mêmes?


Voici exactement à quoi ressemble une classe:

// Velocity.h

#ifndef VELOCITY_H_
#define VELOCITY_H_

#include "Properties.h"

#include <SFML/System/Vector2.hpp>

namespace LaB
{
namespace P
{

class Velocity: public LaB::Property
{
public:
    static const PropertyID id;

    Velocity(float vx = 0.0, float vy = 0.0)
    : velocity(vx,vy) {}
    Velocity(sf::Vector2f v) : velocity(v) {};

    sf::Vector2f velocity;
};

} /* namespace P */
} /* namespace LaB */
#endif /* LaB::P_VELOCITY_H_ */



// Velocity.cpp

#include "Properties/Velocity.h"

namespace LaB
{
namespace P
{

const PropertyID Velocity::id = Property::registerID();

} /* namespace P */
} /* namespace LaB */

Tout cela juste pour un vecteur 2D et un identifiant disant d'attendre un vecteur 2D. (Certes, certaines propriétés ont des éléments plus compliqués, mais c'est la même idée)

Bryan
la source
3
Jetez un oeil à cela, cela pourrait vous aider. gameprogrammingpatterns.com/type-object.html
1
Tout cela ressemble à des modèles qui pourraient aider, mais pas à l'initialisation statique. Cela peut certainement être rendu beaucoup plus facile avec dans ce cas, mais il est difficile de dire sans plus d'informations sur ce que vous voulez faire avec ces ID et pourquoi vous utilisez l'héritage. Utiliser l'héritage public mais pas de fonctions virtuelles est définitivement une odeur de code ... Ce n'est pas du polymorphisme!
ltjax
Avez-vous trouvé la bibliothèque tierce qui l'implémente?
Boris

Réponses:

1

C ++ est puissant, mais il est verbeux . Si ce que vous voulez, c'est beaucoup de petites classes polymorphes qui sont toutes différentes, alors oui, cela va prendre beaucoup de code source pour déclarer et définir. Il n'y a vraiment rien à y faire.

Maintenant, comme l'a dit ltjax, ce que vous faites ici n'est pas exactement du polymorphisme , du moins pour le code que vous avez fourni. Je ne peux pas voir une interface commune qui cacherait l'implémentation spécifique des sous-classes. Sauf peut-être pour l'ID de classe, mais cela est en fait redondant avec l'ID de classe réel: c'est son nom. Cela semble être juste un tas de classes contenant des données, rien de vraiment compliqué.

Mais cela ne résout pas votre problème: vous voulez créer beaucoup de messages et de propriétés avec le moins de code. Moins de code signifie moins de bogues, c'est donc une sage décision. Malheureusement pour vous, il n'y a pas de solution unique. Il est difficile de dire ce qui conviendrait le mieux à vos besoins sans savoir précisément ce que vous avez l'intention de faire avec ces messages et propriétés . Alors laissez-moi simplement exposer vos options:

  1. Utilisez des modèles C ++ . Les modèles sont un excellent moyen de laisser le compilateur "écrire du code" pour vous. Il devient de plus en plus important dans le monde C ++, à mesure que le langage évolue pour mieux les prendre en charge (par exemple, vous pouvez désormais utiliser un nombre variable de paramètres de modèle ). Il existe toute une discipline dédiée à la génération automatisée de code via des modèles: la métaprogrammation de modèles . Le problème est: c'est difficile. Ne vous attendez pas à ce que les non-programmeurs puissent ajouter eux-mêmes de nouvelles propriétés si vous prévoyez d'utiliser une telle technique.

  2. Utilisez des macros C anciennes . C'est la vieille école, facile à abuser et sujette aux erreurs. Ils sont également très simples à créer. Les macros ne sont que des copies-pâtes glorifiées effectuées par le préprocesseur, elles sont donc tout à fait adaptées pour créer des tonnes de choses presque identiques avec de petites variations. Mais ne vous attendez pas à ce que d'autres programmeurs vous aiment pour les utiliser, car les macros sont souvent utilisées pour masquer les défauts dans la conception globale du programme. Parfois, cependant, ils sont utiles.

  3. Une autre option consiste à utiliser un outil externe pour générer le code pour vous. Cela a déjà été mentionné dans une réponse précédente , je ne vais donc pas m'étendre là-dessus.

  4. Il existe d'autres langages moins verbeux et qui vous permettront de créer des classes plus facilement. Donc, si vous avez déjà des liaisons de script (ou si vous prévoyez de les avoir), la définition de ces classes dans le script peut être une option. Leur accès à partir de C ++ sera cependant assez compliqué, et vous perdrez la plupart des avantages de la création simplifiée de classes. Donc c'est probablement viable seulement si vous prévoyez de faire la plupart de votre logique de jeu dans une autre langue.

  5. Enfin, dernier point mais non le moindre, vous devriez envisager d'utiliser une conception basée sur les données . Vous pouvez définir ces "classes" dans de simples fichiers texte en utilisant une disposition similaire à celle que vous avez proposée vous-même. Vous devrez créer un format et un analyseur personnalisés pour celui-ci, ou utiliser l'une des nombreuses options déjà disponibles (.ini, XML, JSON et ainsi de suite). Ensuite, du côté C ++, vous devrez créer un système qui prend en charge une collection de ces différents types d'objets. Ceci est presque équivalent à l'approche de script (et nécessite probablement encore plus de travail), sauf que vous pourrez l'adapter plus précisément à vos besoins. Et si vous le faites assez simplement, les non-programmeurs pourraient être capables de créer de nouvelles choses par eux-mêmes.

Laurent Couvidou
la source
0

Que diriez-vous d'un outil de génération de code pour vous aider?

par exemple, vous pouvez définir votre type de message et vos membres dans un petit fichier texte et demander à l'outil de génération de code de l'analyser et d'écrire tous les fichiers C ++ standard en tant qu'étape de pré-génération.

Il existe des solutions existantes comme ANTLR ou LEX / YACC, et il ne serait pas difficile de rouler la vôtre en fonction de la complexité de vos propriétés et messages.

Programmeur de jeux chroniques
la source
Juste une alternative à l'approche LEX / YACC, lorsque je dois générer des fichiers très similaires avec des modifications mineures, j'utilise un script python simple et petit et un fichier de modèle de code C ++ contenant des balises. Le script python recherche ces balises dans le modèle en les remplaçant par les jetons représentatifs de l'élément en cours de création.
victor
0

Je vous conseille de faire des recherches sur les tampons de protocole de Google ( http://code.google.com/p/protobuf/ ). Ils sont un moyen très intelligent de gérer les messages génériques. Il vous suffit de spécifier les propriétés dans un modèle struct-llike et un générateur de code fait tout le travail de génération des classes pour vous (Java, C ++ ou C #). Toutes les classes générées ont des analyseurs de texte et binaires, ce qui les rend bonnes pour l'initialisation et la sérialisation des messages basés sur du texte.

dsilva.vinicius
la source
Ma centrale de messagerie est au cœur de mon programme - savez-vous si les tampons de protocole entraînent une surcharge importante?
Bryan
Je ne pense pas qu'ils entraînent de gros frais généraux, car l'API est juste une classe Builder pour chaque message et la classe pour le message lui-même. Google les utilise pour le cœur de leur infrastructure. Par exemple, les entités Google App Engine sont toutes converties en tampons de protocole avant de persister. En cas de doute, je vous conseille d'implémenter un test de comparaison entre votre implémentation actuelle et les tampons de protocole. Si vous pensez que les frais généraux sont acceptables, utilisez-les.
dsilva.vinicius