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?
la source
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 undelegate
en 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.la source