Comment organiser un moteur de jeu en C ++? Mon utilisation de l'héritage est-elle une bonne idée?


11

Je suis débutant aussi bien en développement de jeux qu'en programmation.

J'essaie d'apprendre un principe dans la construction d'un moteur de jeu.
Je veux créer un jeu simple, j'en suis au point où j'essaie d'implémenter le moteur de jeu.

J'ai donc pensé que mon moteur de jeu devrait contrôler ces choses:

- Moving the objects in the scene
- Checking the collisions
- Adjusting movements based on collisions
- Passing the polygons to the rendering engine

J'ai conçu mes objets comme ceci:

class GlObject{
private:
    idEnum ObjId;
    //other identifiers
public:
    void move_obj(); //the movements are the same for all the objects (nextpos = pos + vel)
    void rotate_obj(); //rotations are the same for every objects too
    virtual Polygon generate_mesh() = 0; // the polygons are different
}

et j'ai 4 objets différents dans mon jeu: avion, obstacle, joueur, balle et je les ai conçus comme ceci:

class Player : public GlObject{ 
private:
    std::string name;
public:
    Player();
    Bullet fire() const; //this method is unique to the class player
    void generate_mesh();
}

Maintenant, dans le moteur de jeu, je veux avoir une liste d'objets générale où je peux par exemple vérifier la collision, déplacer des objets, etc., mais je veux aussi que le moteur de jeu prenne les commandes de l'utilisateur pour contrôler le joueur ...

Est-ce une bonne idée?

class GameEngine{
private:
    std::vector<GlObject*> objects; //an array containg all the object present
    Player* hPlayer; //hPlayer is to be read as human player, and is a pointer to hold the reference to an object inside the array
public:
    GameEngine();
    //other stuff
}

le constructeur GameEngine sera comme ceci:

GameEngine::GameEngine(){
    hPlayer = new Player;
    objects.push_back(hPlayer);
}

Le fait que j'utilise un pointeur est parce que j'ai besoin d'appeler le fire()qui est unique à l'objet Player.

Ma question est donc: est-ce une bonne idée? Mon utilisation de l'héritage est-elle mauvaise ici?



Je les ai vus après avoir posté, et en fait, la composition est une excellente idée! J'ai toujours peur de finir par utiliser c avec des classes au lieu d'avoir un vrai design c ++!
Pella86

Je pense qu'il y a bien plus de raisons d'utiliser la composition que l'héritage ... Ma réponse donne quelques exemples autour des avantages de flexibilité qu'elle procure. Pour un bon exemple de mise en œuvre, consultez les articles que j'ai liés.
Coyote

Réponses:


12

Cela dépend de ce que vous entendez par «bon». Cela fera certainement le travail. Il a beaucoup de côtés positifs et négatifs. Vous ne les trouverez que lorsque votre moteur sera beaucoup plus complexe. Voir cette question sur SO , pour une discussion sur le sujet.

Il y a beaucoup d'opinions de toute façon, mais si votre code moteur est encore à ce stade précoce, il sera difficile de vous expliquer pourquoi il pourrait être mauvais.

En général, Scott Meyers a de grandes choses à dire sur l'héritage. Si vous êtes encore à ce stade du développement, lisez ce livre (Effective C ++) avant de continuer. C'est un livre fantastique, et est une exigence pour tous les codeurs travaillant dans notre entreprise. Mais pour résumer le conseil: si vous écrivez une classe et envisagez d'utiliser l'héritage, soyez absolument clair que la relation entre la classe et son ancêtre est une relation «est une». C'est-à-dire que chaque B est un A. N'utilisez pas simplement l'héritage comme un moyen facile de donner à B l'accès aux fonctionnalités fournies par A, cela entraînera de graves problèmes architecturaux, et il existe d'autres façons de le faire.

Mettre tout simplement; pour un moteur de toute taille, vous constaterez rapidement que les objets tombent rarement dans une structure hiérarchique soignée pour laquelle l'héritage fonctionne. Utilisez-le quand cela est approprié, mais ne tombez pas dans le piège de l'utiliser pour tout - apprenez d'autres façons de relier les objets.


J'appuie l'opinion sur l'héritage. Ma règle d' or personnelle est la suivante: ne le faites pas à moins que vous ne deviez le faire ou le faire d'une autre manière serait stupide. En d'autres termes, l'héritage est une mauvaise idée dans 99% des cas (au moins :)). En outre, il peut être judicieux de séparer la logique du jeu (rotation / déplacement) du rendu (generate_mesh ()). Une autre chose est fire () - envisagez d'avoir une liste d'actions et une fonction d'action générique (my_action) - de cette façon, vous ne vous retrouverez pas avec un bazillion de fonctions différentes réparties dans toutes les classes. Dans les deux cas, restez simple et remaniez impitoyablement lorsque vous rencontrez des problèmes.
Gilead

8

Pour les données logiques et les fonctions de vos objets de jeu (entités), je recommanderais fortement d'utiliser la composition plutôt que l'héritage. Un bon début serait l'approche basée sur les composants. Il est bien décrit dans cet article de programmation Cowboy qui décrit comment cette architecture peut être implémentée et comment elle a profité à la franchise Tony Hawk.

Aussi cet article sur les systèmes d'entité m'a inspiré quand je l' ai lu.

L'héritage est un excellent outil pour le polymorphisme. À l'exception de projets très simples, vous devez éviter de l'utiliser comme un moyen de structurer les entités et vos logiques de jeu. Si vous optez pour l'héritage, vous finirez certainement par devoir répliquer et conserver des copies du même code pour implémenter des fonctions qui ne peuvent pas être héritées d'un parent commun. Et vous commencerez probablement à polluer vos classes avec les logiques et les données les plus couramment utilisées pour éviter de répliquer trop de code.

Les composants sont très pratiques dans de nombreuses situations. Vous pouvez en faire bien plus en les utilisant:

Flexibilité avec vos types d'entités : Il y a des moments dans le développement de jeux où certaines entités évoluent soudainement et pendant que le concepteur de jeux prend la décision, le développeur devra les implémenter. Il suffit de quelques étapes pour ajouter un comportement existant sans répliquer de code. Les systèmes basés sur des composants permettent à plus de modifications d'être effectuées par des non-programmeurs. Par exemple, chaque type d'entité au lieu d'être représenté par une classe peut être représenté par un fichier de description qui contient tous les noms de composants et les paramètres nécessaires pour configurer le type. Cela permet aux non-programmeurs d'effectuer et de tester les modifications sans recompiler le jeu. Il suffit de modifier les fichiers de description pour ajouter, supprimer ou modifier les composants utilisés par chaque type d'entité.

Flexibilité avec des instances particulières et des scénarios spécifiques : Dans certains cas, vous avez besoin que votre joueur soit en mesure d'équiper des armes ou des objets, vous pouvez ajouter un composant d'inventaire à votre joueur. lors du développement de votre jeu vous aurez besoin d'autres entités pour avoir un inventaire (ennemis par exemple). Et à un moment donné, vous avez dans votre scénario une situation où un arbre est censé contenir une sorte d’élément caché. Avec les composants, vous pouvez ajouter un composant d'inventaire même aux entités dont le type n'est pas conçu à l'origine pour en avoir un sans effort de programmation supplémentaire.

Flexibilité au moment de l'exécution : les composants offrent un moyen très efficace de modifier le comportement des entités au moment de l'exécution. Vous pouvez ajouter et supprimer des composants lors de l'exécution et ainsi créer ou supprimer des comportements et des propriétés en cas de besoin. Ils vous permettent également de séparer différents types de données en groupes qui peuvent (parfois) être calculés séparément en parallèle sur plusieurs GPU.

Flexibilité dans le temps : les composants peuvent être utilisés pour maintenir la compatibilité entre les versions. Par exemple, lorsque des modifications sont apportées entre les versions d'un jeu, vous pouvez (pas toujours) simplement ajouter de nouveaux composants qui implémentent les nouveaux comportements et modifications. Ensuite, le jeu instanciera les bons composants attachés à vos entités en fonction du fichier de sauvegarde chargé, du pair connecté ou du serveur auquel le joueur se joint. Cela est extrêmement pratique dans les scénarios où vous ne pouvez pas contrôler les dates de sortie de la nouvelle version sur toutes les plateformes (Steam, Mac App Store, iPhone, Android ...).

Pour une meilleure compréhension, jetez un œil aux questions balisées [ basé sur les composants] et [système d'entité] .


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.