Comment éviter l'objet divin de GameManager?

51

Je viens de lire une réponse à une question sur la structuration du code de jeu . Cela m'a fait me poser des questions sur la GameManagerclasse omniprésente et sur le fait qu'elle devient souvent un problème dans un environnement de production. Permettez-moi de décrire ceci.

Premièrement, il y a le prototypage. Personne ne se soucie d’écrire un bon code, nous essayons simplement d’exécuter quelque chose pour voir si le gameplay s’ajoute.

Ensuite, il y a un feu vert, et dans un effort pour nettoyer les choses, quelqu'un écrit un GameManager. Probablement pour tenir un tas de GameStates, peut-être pour stocker quelques-uns GameObjects, rien de grand, vraiment. Un mignon petit gestionnaire.

Dans le domaine paisible de la pré-production, le jeu se déroule bien. Les codeurs ont de bonnes nuits de sommeil et de nombreuses idées pour l'architecture avec d'excellents modèles de conception.

Ensuite, la production commence et bientôt, bien sûr, le temps presse. Le régime équilibré a disparu depuis longtemps, le traqueur de bogues craque avec des problèmes, les gens sont stressés et le jeu doit être publié hier.

À ce stade, généralement, il GameManagers’agit d’un véritable grand désastre (pour rester poli).

La raison en est simple. Après tout, lorsque vous écrivez un jeu, eh bien ... tout le code source est réellement là pour gérer le jeu . Il est facile d'ajouter simplement cette petite fonctionnalité supplémentaire ou correction de bug dans le répertoire GameManageroù tout le reste est déjà stocké. Lorsque le temps devient un problème, il est impossible d'écrire une classe séparée ou de diviser ce gestionnaire géant en sous-gestionnaires.

Bien sûr, il s'agit d'un anti-motif classique: l' objet divin . C'est une mauvaise chose, une douleur à fusionner, une douleur à maintenir, une douleur à comprendre, une douleur à transformer.

Que suggéreriez-vous pour empêcher cela?

MODIFIER

Je sais qu'il est tentant de blâmer le nommage. Bien sûr, créer une GameManagerclasse n’est pas une si bonne idée. Mais la même chose peut arriver à un nettoyé GameApplicationou GameStateMachineou GameSystemqui finit par être le favori du ruban adhésif à la fin d'un projet. Peu importe le nom, vous avez cette classe quelque part dans votre code de jeu, ce n'est qu'un embryon pour l'instant: vous ne savez pas encore quel monstre il va devenir. Donc, je ne m'attends pas à des réponses de "faute de nommer". Je veux un moyen d'éviter cela, un architure de codage et / ou un processus qu'une équipe peut suivre en sachant que cela se produira à un moment donné de la production. Il est tout simplement dommage de jeter un mois de corrections de bugs et de fonctionnalités de dernière minute, tout simplement parce qu’elles font toutes partie d’un seul et même gestionnaire inappréciable.

Laurent Couvidou
la source
Que ce soit appelé GameManager ou ControllerOfTheGame, ou ce que vous voulez appeler, évitez un cours chargé de contrôler la logique du jeu entier. Vous savez que vous le faites quand vous le faites, peu importe comment vous prévoyez l'appeler. Je pense que lors de ma dernière partie, j’avais un contrôleur de niveau qui avait juste pour but de conserver l’état du niveau de sauvegarde, mais si j’avais arrêté et réfléchi, il était évident que cet objet était conçu pour traiter plus de détails qu’il ne le devrait.
brandon
@brandon Permettez-moi de reformuler ma question: comment contrôlez-vous la logique de tout le jeu sans vous exposer à l' GameManagereffet que j'ai décrit plus haut?
Laurent Couvidou
Cela dépend de la logique. Les éléments évidents tels que joueur, ennemi, structure, etc. qui ont clairement leur propre logique sont mis dans leurs classes respectives. Ce que vous semblez demander le plus, c’est la gestion de l’état du jeu. Il y aura généralement une classe qui gardera une trace de l'état du jeu entier, mais d'après mon expérience, c'est l'une des dernières choses dont vous devez vous soucier lors du prototypage, et son objectif doit être très clair. Fondamentalement, vous devez faire une planification initiale avant le prototypage ou bien recommencer à zéro lorsque vous démarrez la production pour éviter d'utiliser des classes indésirables à des fins de test.
brandon
Chose intéressante, XNA crée ce désastre potentiel pour vous. Par défaut, une classe de jeu est créée pour tout gérer. Il contient les principales méthodes de mise à jour, de dessin, d'initialisation et de chargement. En fait, je suis en train de migrer vers un nouveau projet parce que mon jeu (pas si complexe) est devenu trop difficile à gérer avec tout ce qui est entassé dans cette classe. De plus, les tutoriels de Microsoft semblent l’encourager.
Fibericon

Réponses:

23

Ensuite, il y a un feu vert, et dans un effort pour nettoyer les choses, quelqu'un écrit un GameManager. Probablement pour tenir un tas de GameStates, peut-être pour stocker quelques GameObjects, rien d’énorme, vraiment. Un mignon petit gestionnaire.

Vous savez, alors que je lisais ceci, de petites alarmes se sont déclenchées dans ma tête. Un objet nommé "GameManager" ne sera jamais mignon ou petit. Et quelqu'un a fait ça pour nettoyer le code? À quoi ressemblait-il avant? OK, blague à part: le nom d'une classe devrait indiquer clairement ce que la classe fait, et cela devrait être une chose (alias: principe de responsabilité unique ).

En outre, vous pouvez toujours vous retrouver avec un objet tel que GameManager, mais il est clair qu'il existe à un niveau très élevé et qu'il devrait concerner des tâches de haut niveau. Maintenir un catalogue d'objets de jeu? Peut-être. Faciliter la communication entre les objets du jeu? Sûr. Calculer des collisions entre objets? Non! C'est aussi pour cette raison que le nom de Manager est mal vu - il est trop large et permet beaucoup d'abus sous cette bannière.

Une règle rapide sur la taille des classes: si vous rencontrez plusieurs centaines de lignes de code par classe, quelque chose commence à mal tourner. Sans être trop zélé, rien de plus, disons, 300 LOC, c’est une odeur de code pour moi, et si vous dépassez les 1 000, les sonneries d’avertissement devraient sonner. En croyant que 1000 lignes de code sont plus simples à comprendre que 4 classes bien structurées de 250 chacune, vous vous leurrez.

Lorsque le temps devient un problème, il est impossible d'écrire une classe séparée ou de diviser ce gestionnaire géant en sous-gestionnaires.

Je pense que ce n'est le cas que parce que le problème est autorisé à se propager au point que tout est un gâchis total. La pratique de la refactorisation est vraiment ce que vous recherchez - vous devez améliorer en permanence la conception du code, par incréments infimes .

Que suggéreriez-vous pour empêcher cela?

Le problème n’est pas d’ordre technologique, vous ne devez donc pas chercher de solution technique à ce problème. Le problème est le suivant: votre équipe a tendance à créer des morceaux de code monolithiques et à croire qu’il est bénéfique à moyen / long terme de fonctionner de la sorte. Il semble également que l'équipe manque d'un chef solide en architecture, qui dirigerait l'architecture du jeu (ou du moins, cette personne est trop occupée pour effectuer cette tâche). Fondamentalement, le seul moyen de sortir est de faire reconnaître aux membres de l’équipe que cette pensée est fausse. Il ne favorise personne. La qualité du produit va se détériorer et l'équipe ne passera que plus de nuits à réparer.

La bonne nouvelle est que les avantages immédiats et tangibles de l'écriture de code propre sont si importants que presque tous les développeurs réalisent leurs avantages très rapidement. Convaincre l’équipe de travailler de cette manière pendant un moment, les résultats feront le reste.

La partie difficile est que développer une idée de ce qui constitue un mauvais code (et un talent pour concevoir rapidement un meilleur design) est l’une des compétences les plus difficiles à acquérir en développement. Ma suggestion s'articule autour de l'espoir d'avoir dans l'équipe une personne suffisamment expérimentée pour le faire - il est beaucoup plus facile de convaincre les gens de cette façon.

Edit - Un peu plus d'infos:

En général, je ne pense pas que votre problème se limite au développement de jeux. À la base, c'est un problème d'ingénierie logicielle, d'où mes commentaires dans ce sens. Ce qui peut être différent, c’est la nature de l’industrie du développement de jeux, qu’elle soit davantage axée sur les résultats et sur les délais que d’autres types de développement, je ne suis pas sûre.

En ce qui concerne plus particulièrement le développement de jeux, la réponse acceptée à cette question sur StackOverflow concernant les conseils «en particulier d’architecture de jeu» est la suivante:

Suivez les principes solides de la conception orientée objet .

C'est essentiellement exactement ce que je dis. Lorsque je suis sous pression, je me retrouve également à écrire de gros morceaux de code, mais je me suis dit que c'était une dette technique. Ce qui a tendance à bien fonctionner pour moi est de passer la première moitié (ou les trois quarts) de la journée à produire une grande quantité de code de qualité moyenne, puis de prendre un peu de recul et d'y réfléchir pendant un certain temps; faire un peu de conception dans ma tête, ou sur papier / tableau blanc, sur la façon d’améliorer un peu le code. Souvent, je remarque du code répétitif et je suis en mesure de réduire le nombre total de lignes de code en décomposant, tout en améliorant la lisibilité. Ce temps investi a été rentabilisé si rapidement que le qualifier d '"investissement" semble bête - bien souvent, je ramasserai des bugs qui auraient pu perdre une demi-journée (une semaine plus tard), si je l'avais laissé continuer.

  • Corrigez les choses le même jour où vous les codez.
  • Vous serez heureux de l'avoir fait en quelques heures.

Venir à croire réellement ce qui précède est difficile; J'ai réussi à le faire pour mon propre travail uniquement parce que j'ai expérimenté, encore et encore, les effets. Même dans ce cas, il m'est toujours difficile de justifier la modification du code alors que je pourrais en produire davantage ... Donc, je comprends vraiment d'où vous venez. Malheureusement, ce conseil est peut-être trop générique et difficile à faire. J'y crois fortement, cependant! :)

Pour répondre à votre exemple spécifique:

Clean Code d' Oncle Bob résume à merveille le code de bonne qualité. Je suis d'accord avec presque tout de son contenu. Ainsi, lorsque je pense à votre exemple d'une classe de gestionnaire de 30 000 LOC, je ne peux pas vraiment accepter la partie "bonne raison". Je ne veux pas paraître offensant, mais penser de cette façon conduira au problème. Il n'y a pas de bonne raison d'avoir autant de code dans un seul fichier - c'est presque 1000 pages de texte! Tout avantage lié à la localité (vitesse d'exécution ou "simplicité" de la conception) sera immédiatement annulé par le fait que les développeurs sont complètement embourbés à vouloir naviguer dans cette monstruosité, et ce, même avant que nous discutions de la fusion, etc.

Si vous n'êtes pas convaincu, ma meilleure suggestion serait de prendre un exemplaire du livre ci-dessus et de le parcourir. Appliquer ce type de pensée à des personnes crée volontairement un code propre et explicite, bien structuré.

Daniel B
la source
Ce n'est pas un défaut que j'ai vu une fois. J'ai vu cela dans plusieurs équipes, pour plusieurs projets. J'ai vu beaucoup de personnes talentueuses et structurées qui luttaient avec cela. Lorsque vous êtes sous pression, la limite de 300 lignes de code n'est pas un souci de votre producteur. Ni le joueur d'ailleurs. Bien sûr, c'est gentil, mais le diable est dans les détails. Le pire cas de ce GameManagerque j'ai vu jusqu'à présent concernait environ 30 000 lignes de code, et je suis presque certain que la plupart d'entre elles l'ont été pour une très bonne raison. C'est extrême, bien sûr, mais ces choses se produisent dans le monde réel. Je demande un moyen de limiter cet GameManagereffet.
Laurent Couvidou
@lorancou - J'ai ajouté pas mal d'informations supplémentaires au message. C'était un peu trop pour un commentaire. J'espère que cela vous donnera une idée un peu plus, même si je ne peux vous indiquer aucun projet architectural concret. exemples.
Daniel B
Ah, je n'ai pas lu ce livre, donc c'est une bonne suggestion. Oh, et je ne voulais pas dire qu'il y avait une bonne raison d'ajouter du code dans une classe de milliers de lignes. Je voulais dire que tout le code dans ce genre d'objet divin fait quelque chose d'utile. J'ai probablement écrit ça un peu trop vite;)
Laurent Couvidou
@lorancou Hehe, c'est bon ... c'est comme ça que la folie :)
Daniel B
"La pratique de la refactorisation est vraiment ce que vous recherchez. Vous devez améliorer continuellement la conception du code, par incréments infimes." Je pense que vous avez un point très fort là-bas. Le désordre attire le désordre, c’est la vraie raison derrière tout cela, alors le désordre doit être combattu à long terme. Je vais accepter cette réponse, mais cela ne veut pas dire qu'il n'y a aucune valeur dans les autres!
Laurent Couvidou
7

Ce n'est pas vraiment un problème avec la programmation de jeux, mais avec la programmation en général.

tout le code source est réellement là pour gérer le jeu.

C'est pourquoi vous devriez froncer les sourcils à tout codeur orienté objet qui suggère des classes avec "Manager" dans le nom. Pour être juste, je pense qu'il est pratiquement impossible d'éviter les objets "semigod" dans le jeu, mais nous les appelons simplement "système".

Tout logiciel a tendance à se salir lorsque la date limite est proche. Cela fait partie du travail. Et il n’ya rien de mal en soi à la " programmation de type conduit ", en particulier lorsque vous devez expédier votre produit dans quelques heures. Vous ne pouvez pas vous débarrasser totalement de ce problème, vous pouvez simplement le réduire.

Bien évidemment, si vous êtes tenté de mettre des choses dans GameManager, cela signifie que vous n’avez pas une base de code très saine. Il pourrait y avoir deux problèmes:

  • Votre code est trop complexe. Trop de modèles, optimisation prématurée, personne ne comprend plus le flux. Essayez d'utiliser plus de TDD lors du développement si vous le pouvez, car c'est une très bonne solution pour lutter contre la complexité.

  • Votre code n'est pas bien structuré. Vous utilisez (ab) en utilisant la variable globals, les classes ne sont pas couplées de manière lâche, il n'y a pas d'interface, de système d'événement, etc.

Si le code semble correct, vous devrez simplement vous rappeler, pour chaque projet, "ce qui ne va pas" et l'éviter la prochaine fois.

Enfin, il pourrait aussi s'agir d'un problème de gestion d'équipe: y avait-il assez de temps? Est-ce que tout le monde était au courant de l'architecture que vous souhaitiez? Qui diable a permis un commit avec une classe contenant le mot "Manager"?

Raveline
la source
Il n'y a rien de mal à programmer du ruban adhésif, je vous le dis. Mais je suis sûr que les classes de direction sont omniprésentes dans les moteurs de jeu, du moins je les ai beaucoup vues. Ils sont utiles tant qu'ils ne deviennent pas trop gros ... Qu'est-ce qui ne va pas avec un SceneManagerou un NetworkManager? Je ne vois pas pourquoi nous devrions les empêcher ni comment le remplacement de -Manager par -System peut aider. Je suis à la recherche de suggestions pour une architecture de remplacement de ce qui entre habituellement dans une GameManagerconfiguration moins risquée.
Laurent Couvidou
Quel est le problème avec un SceneManager? Eh bien, quelle est la différence entre une scène et un SceneManager? Un réseau et un gestionnaire de réseau? Sur l’autre aspect: je ne pense pas que ce soit un problème d’architecture, mais si vous pouviez donner un exemple concret de quelque chose qui est dans GameManager et qui ne devrait pas être là, il serait plus facile de suggérer une solution.
Raveline
OK, alors oubliez le truc -Manager. Disons que vous avez une classe qui gère vos états de jeu, appelons-le GameStatesCollection. Au début, vous aurez juste quelques états de jeu et des moyens de basculer entre eux. Ensuite, il y a les écrans de chargement qui viennent compliquer tout cela. Ensuite, certains programmeurs doivent faire face à un bogue lorsque l’utilisateur éjecte le disque lorsque vous passez de Level_A à Level_B, et le seul moyen de le réparer sans passer une demi-journée de refactoring est in GameStatesCollection. Boom, un objet divin.
Laurent Couvidou
5

Alors, apportant une solution possible du monde plus Enterprise-y: avez-vous vérifié l'inversion d'injection de contrôle / dépendance?

Fondamentalement, vous obtenez ce cadre qui contient tout ce qui se trouve dans votre jeu. C’est elle qui sait comment tout relier et quelles sont les relations entre vos objets. Aucun de vos objets n'instancie quoi que ce soit à l'intérieur de leurs propres constructeurs. Plutôt que de créer ce dont ils ont besoin, ils demandent ce dont ils ont besoin dans le cadre d'IoC; à la création, le framework IoC "sait" quel objet est nécessaire pour satisfaire la dépendance et le remplit. Couplage lâche FTW.

Lorsque votre classe EnemyTreasure demande au conteneur un objet GameLevel, le framework sait quelle instance lui donner. Comment sait-il? Peut-être qu'il l'instanciait plus tôt, peut-être qu'il demandait à GameLevelFactory tout proche. Le fait est que EnemyTreasure ne sait pas d'où provient l'instance GameLevel. Il sait seulement que sa dépendance est maintenant satisfaite et qu'il peut continuer à vivre.

Oh, je vois que votre classe PlayerSprite implémente IUpdateable. C'est formidable, car la structure inscrit automatiquement chaque objet IUpdateable au minuteur, qui est l'endroit où se trouve la boucle de jeu principale. Chaque minuterie :: tick () appelle automatiquement toutes les méthodes IUpdatedable :: update (). Facile, non?

De manière réaliste, vous n'avez pas besoin de ce cadre bighugeohmygod pour le faire à votre place: vous pouvez en faire les parties importantes vous-même. Le fait est que si vous créez des objets de type -Manager, il est probable que vous ne répartissez pas correctement vos responsabilités entre vos classes ou que certaines classes manquent quelque part.

drhayes
la source
Intéressant, la première fois que j'ai entendu parler d'IoC. Cela pourrait être trop énorme pour la programmation de jeux, mais je vérifierai!
Laurent Couvidou
IoC, N’avions-nous pas simplement appelé cela du bon sens?
Brice
Si on leur donne une chance sur deux, un ingénieur va créer un cadre à partir de n'importe quoi. (= Par ailleurs, je ne préconise pas l'utilisation du cadre, je préconise le modèle architectural.
drhayes
3

Dans le passé, je finis généralement avec une classe de dieu si je fais ce qui suit:

  • asseyez-vous devant l'éditeur de jeu / l'IDE / le bloc-notes avant d'avoir écrit un diagramme de classes (ou juste un croquis des objets principaux)
  • Commencez avec une idée très vague du jeu et codez-le immédiatement, puis commencez à itérer de nouvelles idées au fur et à mesure
  • créer un fichier de classe appelé GameManager lorsque je ne crée pas un jeu basé sur l'état comme une stratégie au tour par tour dans lequel GameManager est en réalité un objet de domaine
  • ne vous souciez pas de l'organisation du code au début

Cela peut être controversé, ce qui me fera rire si c’est vrai, mais je ne peux penser à une seule fois, même dans le prototypage, où vous ne devriez pas écrire un diagramme de classes très basique avant d’écrire un prototype jouable. J'ai tendance à tester des idées techniques avant de planifier, mais c'est à peu près tout. Donc, si je voulais créer un portail, j’écrirais du code très basique pour que les portails fonctionnent, puis je dirais que c’est un bon moment pour une planification mineure, alors je peux travailler sur le prototype jouable.

Brandon
la source
1

Je sais que cette question est ancienne maintenant, mais je pensais ajouter mes 2 centimes.

Lors de la conception de jeux, j'ai presque toujours un objet Game. Cependant, il est de très haut niveau et ne "fait rien" en soi.

Par exemple, il indique à la classe Graphics de rendre, mais n'a rien à voir avec le processus de rendu. Il peut demander au LevelManager de charger un nouveau niveau, mais cela n’a rien à voir avec le processus de chargement.

En bref, j'utilise un objet semi-divin pour orchestrer l'utilisation des autres classes.

OMGtechy
la source
2
Ce n'est pas du tout très dieu - cela s'appelle de l'abstraction. :)
Vaughan Hilts