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 GameManager
classe 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 GameManager
s’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 GameManager
où 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 GameManager
classe n’est pas une si bonne idée. Mais la même chose peut arriver à un nettoyé GameApplication
ou GameStateMachine
ou GameSystem
qui 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.
la source
GameManager
effet que j'ai décrit plus haut?Réponses:
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.
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 .
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:
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.
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é.
la source
GameManager
que 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 cetGameManager
effet.Ce n'est pas vraiment un problème avec la programmation de jeux, mais avec la programmation en général.
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"?
la source
SceneManager
ou unNetworkManager
? 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 uneGameManager
configuration moins risquée.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 inGameStatesCollection
. Boom, un objet divin.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.
la source
Dans le passé, je finis généralement avec une classe de dieu si je fais ce qui suit:
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.
la source
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.
la source