Conseils sur l'architecture de jeu / les modèles de conception

16

Je travaille sur un RPG 2D depuis un certain temps maintenant, et j'ai réalisé que j'avais pris de mauvaises décisions de conception. Il y a quelques choses en particulier qui me causent des problèmes, alors je me demandais quels types de conceptions d'autres personnes utilisaient pour les surmonter ou utiliseraient.

Pour un peu d'histoire, j'ai commencé à travailler dessus pendant mon temps libre l'été dernier. Au départ, je faisais le jeu en C #, mais il y a environ 3 mois, j'ai décidé de passer au C ++. Je voulais avoir une bonne idée du C ++ car cela faisait un moment que je ne l'utilisais pas beaucoup, et j'ai pensé qu'un projet intéressant comme celui-ci serait une bonne motivation. J'ai beaucoup utilisé la bibliothèque boost et j'ai utilisé SFML pour les graphiques et FMOD pour l'audio.

J'ai un peu de code écrit, mais j'envisage de le supprimer et de recommencer.

Voici les principaux sujets de préoccupation que j'ai et que je voulais obtenir des opinions sur la manière appropriée dont d'autres les ont résolus ou les résoudraient.

1. Dépendances cycliques Quand je faisais le jeu en C #, je n'avais pas vraiment à m'inquiéter car ce n'était pas un problème là-bas. Passer au C ++, cela est devenu un problème assez important et m'a fait penser que j'avais peut-être mal conçu les choses. Je ne peux pas vraiment imaginer comment découpler mes cours et les faire faire ce que je veux. Voici quelques exemples d'une chaîne de dépendances:

J'ai une classe d'effet de statut. La classe a un certain nombre de méthodes (Apply / Unapply, Tick, etc.) pour appliquer ses effets à un personnage. Par exemple,

virtual void TickCharacter(Character::BaseCharacter* character, Battles::BattleField *field, int ticks = 1);

Cette fonction serait appelée à chaque fois que le personnage infligé avec l'effet de statut prend un tour. Il serait utilisé pour implémenter des effets tels que Regen, Poison, etc. Cependant, il introduit également des dépendances sur la classe BaseCharacter et la classe BattleField. Naturellement, la classe BaseCharacter doit garder une trace des effets d'état actuellement actifs sur eux, c'est donc une dépendance cyclique. Battlefield doit garder une trace des parties au combat, et la classe de groupe a une liste de BaseCharacters introduisant une autre dépendance cyclique.

2 - Événements

En C #, j'ai largement utilisé les délégués pour se connecter aux événements sur les personnages, les champs de bataille, etc. .) et les composants champ de bataille / graphiques se raccorderaient à ces délégués pour appliquer leurs effets. En C ++, j'ai fait quelque chose de similaire. Évidemment, il n'y a pas d'équivalent direct aux délégués C #, donc j'ai plutôt créé quelque chose comme ceci:

typedef boost::function<void(BaseCharacter*, int oldvalue, int newvalue)> StatChangeFunction;

et dans ma classe de personnage

std::map<std::string, StatChangeFunction> StatChangeEventHandlers;

chaque fois que la statistique du personnage changeait, je répétais et appelais chaque StatChangeFunction sur la carte. Bien que cela fonctionne, je crains que ce soit une mauvaise approche pour faire les choses.

3 - Graphiques

Ceci est la grande chose. Ce n'est pas lié à la bibliothèque graphique que j'utilise, mais c'est plus une chose conceptuelle. En C #, j'ai couplé des graphiques avec beaucoup de mes cours, ce qui, je le sais, est une idée terrible. Voulant le faire découplé cette fois, j'ai essayé une approche différente.

Afin d'implémenter mes graphismes, j'imaginais tout les graphismes liés au jeu comme une série d'écrans. C'est-à-dire qu'il y a un écran de titre, un écran d'état de personnage, un écran de carte, un écran d'inventaire, un écran de bataille, un écran GUI de bataille, et fondamentalement, je pourrais empiler ces écrans les uns sur les autres si nécessaire pour créer les graphiques du jeu. Quel que soit l'écran actif, il possède l'entrée du jeu.

J'ai conçu un gestionnaire d'écran qui pousserait et ferait éclater les écrans en fonction des commentaires des utilisateurs.

Par exemple, si vous étiez sur un écran de carte (un gestionnaire d'entrée / visualiseur pour une carte de tuile) et que vous appuyiez sur le bouton de démarrage, il émettrait un appel au gestionnaire d'écran pour pousser un écran du menu principal sur l'écran de la carte et marquer la carte écran à ne pas dessiner / mettre à jour. Le joueur naviguerait dans le menu, ce qui enverrait plus de commandes au gestionnaire d'écran, le cas échéant, pour pousser de nouveaux écrans sur la pile d'écran, puis les afficherait lorsque l'utilisateur changerait d'écran / annule. Enfin, lorsque le joueur quitte le menu principal, je le saute et reviens à l'écran de la carte, le remarque pour être dessiné / mis à jour et aller de là.

Les écrans de bataille seraient plus complexes. J'aurais un écran pour servir d'arrière-plan, un écran pour visualiser chaque partie dans la bataille et un écran pour visualiser l'interface utilisateur pour la bataille. L'interface utilisateur se raccorderait aux événements de personnage et les utiliserait pour déterminer quand mettre à jour / redessiner les composants de l'interface utilisateur. Enfin, chaque attaque disposant d'un script d'animation disponible appelle une couche supplémentaire pour s'animer avant de sortir de la pile d'écran. Dans ce cas, chaque couche est systématiquement marquée comme dessinable et modifiable et j'obtiens une pile d'écrans gérant mes graphiques de combat.

Bien que je n'aie pas encore réussi à faire fonctionner le gestionnaire d'écran, je pense que je peux le faire avec un peu de temps. Ma question à ce sujet est la suivante: est-ce une approche valable du tout? Si c'est un mauvais design, je veux le savoir maintenant avant d'investir trop de temps pour créer tous les écrans dont j'aurai besoin. Comment construisez-vous les graphismes de votre jeu?

user127817
la source

Réponses:

15

Dans l'ensemble, je ne dirais pas que tout ce que vous avez énuméré devrait vous faire abandonner le système et recommencer. C'est quelque chose que chaque programmeur veut faire environ 50 à 75% du chemin à travers n'importe quel projet sur lequel il travaille, mais cela conduit à un cycle de développement sans fin et à ne rien finir. Donc, à cette fin, certains reviennent sur chaque section.

  1. Cela peut être un problème mais est généralement plus gênant qu'autre chose. Utilisez-vous #pragma une fois ou #ifndef MY_HEADER_FILE_H #define MY_HEADER_FILE_H ... #endif en haut ou autour de vos fichiers .h respectivement? De cette façon, le fichier .h n'existe qu'une seule fois dans chaque étendue? Si vous l'êtes, ma recommandation consiste alors à supprimer toutes les instructions #include et à les compiler, en les ajoutant au besoin pour recompiler le jeu.

  2. Je suis fan de ces types de systèmes et je ne vois rien de mal à cela. Ce qui est un événement en C # est généralement remplacé par un système d'événement ou un système de messagerie (vous pouvez rechercher les informations ici pour plus d'informations). La clé ici est de les garder au minimum lorsque des choses doivent se produire, ce qui semble déjà être le cas, ne devrait pas y avoir de soucis minimaux ici.

  3. Cela me semble également sur la bonne voie et c'est ce que je fais pour mes propres moteurs, à la fois personnellement et professionnellement. Cela fait du système de menus un système d'état qui a soit le menu racine (avant le début du jeu), soit le HUD du joueur comme écran `` racine '' affiché, selon la façon dont vous l'avez configuré.

Donc, pour résumer, je ne vois rien redémarrer digne de ce que vous rencontrez. Vous voudrez peut-être un remplacement du système d'événement plus formel sur la route, mais cela viendra à temps. Les inclusions cycliques sont un obstacle que tous les programmeurs C / C ++ doivent constamment franchir, et travailler pour découpler les graphiques semble tous être des «prochaines étapes» logiques.

J'espère que cela t'aides!

James
la source
#ifdef n'aide pas les problèmes d'inclusion circulaire.
Le canard communiste
Je couvrais juste ma base en m'attendant à ce que cela soit là avant de retrouver les inclusions cycliques. Peut être une toute autre marmite de poisson lorsque vous avez plusieurs définitions de symboles, par opposition à un fichier qui doit inclure un fichier qui comprend lui-même. (bien que d'après ce qu'il a décrit si les inclusions sont dans les fichiers .CPP et non dans les fichiers .H, il devrait être d'accord avec deux objets de base se connaissant)
James
Merci pour les conseils :) Heureux de savoir que je suis sur la bonne voie
user127817
4

Vos dépendances cycliques ne devraient pas être un problème tant que vous déclarez les classes où vous pouvez dans les fichiers d'en-tête et #incluez-les réellement dans les fichiers .cpp (ou autre).

Pour le système événementiel, deux suggestions:

1) Si vous souhaitez conserver le modèle que vous utilisez maintenant, envisagez de passer à boost :: unordered_map au lieu de std :: map. Le mappage avec des chaînes comme clés est lent, d'autant plus que .NET fait de belles choses sous le capot pour accélérer les choses. L'utilisation d'unordered_map hache les chaînes afin que les comparaisons soient généralement plus rapides.

2) Pensez à passer à quelque chose de plus puissant comme les signaux boost ::. Si vous faites cela, vous pouvez faire de belles choses comme rendre vos objets de jeu traçables en dérivant de boost :: signaux :: trackable, et laisser le destructeur se charger de tout nettoyer au lieu de devoir vous désinscrire manuellement du système d'événements. Vous pouvez également avoir plusieurs signaux pointant vers chaque emplacement (ou vice versa, je ne me souviens pas de la nomenclature exacte), donc c'est très similaire à faire +=sur un delegateen C #. Le plus gros problème avec boost :: signaux est qu'il doit être compilé, ce ne sont pas seulement des en-têtes, donc en fonction de votre plate-forme, il pourrait être difficile de le mettre en service.

Tetrad
la source