J'ai lu à de nombreux endroits que DrawableGameComponents devrait être enregistré pour des choses comme des "niveaux" ou une sorte de gestionnaires au lieu de les utiliser, par exemple, pour des personnages ou des tuiles (comme ce type le dit ici ). Mais je ne comprends pas pourquoi il en est ainsi. J'ai lu ce post et cela avait beaucoup de sens pour moi, mais ce sont la minorité.
Habituellement, je ne ferais pas trop attention à des choses comme celles-ci, mais dans ce cas, je voudrais savoir pourquoi la majorité apparente pense que ce n'est pas la voie à suivre. Peut-être que je manque quelque chose.
xna
game-component
Kenji Kina
la source
la source
DrawableGameComponent
vous enferme dans un seul ensemble de signatures de méthode et un modèle de données conservé spécifique. Ce n'est généralement pas le bon choix au départ, et le verrouillage gêne considérablement l'évolution du code à l'avenir. "Mais laissez-moi vous dire ce qui se passe ici ...DrawableGameComponent
fait partie de l'API XNA de base (encore une fois: principalement pour des raisons historiques). Les programmeurs novices l'utilisent car il est "là" et cela "fonctionne" et ils ne savent pas mieux. Ils voient ma réponse leur disant qu'ils ont « tort » et - en raison de la nature de la psychologie humaine - rejettent cela et choisissent l'autre «côté». Ce qui se trouve être la non-réponse argumentative de VeraShackle; qui s'est avéré prendre de l'ampleur parce que je ne l'ai pas appelé immédiatement (voir ces commentaires). J'estime que ma réfutation éventuelle reste suffisante.Réponses:
Les "composants du jeu" ont été une partie très précoce de la conception XNA 1.0. Ils étaient censés fonctionner comme des contrôles pour WinForms. L'idée était que vous pouviez les glisser-déposer dans votre jeu en utilisant une interface graphique (un peu comme le bit pour ajouter des contrôles non visuels, comme des minuteries, etc.) et leur définir des propriétés.
Vous pourriez donc avoir des composants comme peut-être un appareil photo ou un ... compteur FPS? ... ou ...?
Tu vois? Malheureusement, cette idée ne fonctionne pas vraiment pour les jeux du monde réel. Le type de composants que vous souhaitez pour un jeu n'est pas vraiment réutilisable. Vous pouvez avoir un composant "joueur", mais il sera très différent du composant "joueur" de tous les autres jeux.
J'imagine que c'est pour cette raison, et pour un simple manque de temps, que l'interface graphique a été tirée avant XNA 1.0.
Mais l'API est toujours utilisable ... Voici pourquoi vous ne devriez pas l'utiliser:
Fondamentalement, cela se résume à ce qui est le plus facile à écrire, à lire, à déboguer et à maintenir en tant que développeur de jeux. Faire quelque chose comme ça dans votre code de chargement:
Est beaucoup moins clair que de simplement faire ceci:
Surtout quand, dans un jeu du monde réel, vous pourriez vous retrouver avec quelque chose comme ça:
(Certes, vous pouvez partager la caméra et spriteBatch en utilisant une
Services
architecture similaire . Mais c'est aussi une mauvaise idée pour la même raison.)Cette architecture - ou son absence - est tellement plus légère et flexible!
Je peux facilement changer l'ordre de dessin ou ajouter plusieurs fenêtres ou ajouter un effet cible de rendu, simplement en changeant quelques lignes. Pour le faire dans l'autre système, je dois penser à ce que font tous ces composants - répartis sur plusieurs fichiers - et dans quel ordre.
C'est un peu comme la différence entre la programmation déclarative et la programmation impérative. Il s'avère que, pour le développement de jeux, la programmation impérative est beaucoup plus préférable.
la source
DrawableGameComponent
.player.DrawOrder = 100;
méthode plutôt que celle impérative pour l'ordre des tirages. Je termine un jeu Flixel en ce moment, qui dessine des objets dans l'ordre dans lequel vous les ajoutez à la scène. Le système FlxGroup atténue une partie de cela, mais avoir une sorte de tri d'index Z intégré aurait été bien.Hmmm. Je dois contester celui-ci pour un peu d'équilibre ici, je le crains.
Il semble y avoir 5 accusations dans la réponse d'Andrew, alors je vais essayer de traiter chacune à son tour:
1) Les composants sont une conception obsolète et abandonnée.
L'idée du modèle de conception des composants existait bien avant XNA - en substance, c'est simplement une décision de faire des objets, plutôt que l'objet de jeu sur-archivé, la maison de leur propre comportement de mise à jour et de dessin. Pour les objets de sa collection de composants, le jeu appellera ces méthodes (et quelques autres) automatiquement au moment approprié - de manière cruciale, l'objet du jeu n'a lui-même à `` connaître '' aucun de ce code. Si vous souhaitez réutiliser vos classes de jeu dans différents jeux (et donc différents objets de jeu), ce découplage d'objets est toujours fondamentalement une bonne idée. Un rapide coup d'œil aux dernières démos (produites par des professionnels) de XNA4.0 de l'AppHub confirme mon impression que Microsoft est toujours (à juste titre à mon avis) fermement derrière celui-ci.
2) Andrew ne peut pas penser à plus de 2 classes de jeu réutilisables.
Je peux. En fait je peux penser aux charges! Je viens de vérifier ma bibliothèque partagée actuelle, et elle comprend plus de 80 composants et services réutilisables . Pour vous donner une saveur, vous pourriez avoir:
Toute idée de jeu particulière aura besoin d'au moins 5 à 10 éléments de cet ensemble - c'est garanti - et ils peuvent être entièrement fonctionnels dans un tout nouveau jeu avec une seule ligne de code.
3) Le contrôle de l'ordre de tirage par l'ordre des appels de tirage explicites est préférable à l'utilisation de la propriété DrawOrder du composant.
Eh bien, je suis essentiellement d'accord avec Andrew sur celui-ci. MAIS, si vous laissez toutes les propriétés DrawOrder de votre composant par défaut, vous pouvez toujours contrôler explicitement leur ordre de dessin par l'ordre dans lequel vous les ajoutez à la collection. Ceci est vraiment identique en termes de «visibilité» à l'ordre explicite de leurs appels de tirage manuel.
4) Appeler vous-même une charge des méthodes Draw d'un objet est plus «léger» que de les appeler par une méthode de framework existante.
Pourquoi? De plus, dans tous les projets, sauf les plus triviaux, votre logique de mise à jour et votre code Draw éclipseront totalement votre méthode d'appel en termes de performances en tout cas.
5) Placer votre code dans un fichier est préférable, lors du débogage, que de l'avoir dans le fichier de l'objet individuel.
Eh bien, tout d'abord, lorsque je débogue dans Visual Studio, l'emplacement d'un point d'arrêt en termes de fichiers sur le disque est presque invisible de nos jours - l'IDE traite de l'emplacement sans aucun drame. Mais considérez également un instant que vous avez un problème avec votre dessin Skybox. Passer à la méthode Draw de Skybox est-il vraiment plus onéreux que de localiser le code de dessin Skybox dans la méthode de dessin maître (vraisemblablement très longue) dans l'objet Game? Non, pas vraiment - en fait, je dirais que c'est tout le contraire.
Donc, en résumé - veuillez examiner attentivement les arguments d'Andrew ici. Je ne pense pas qu'ils donnent réellement des raisons substantielles d'écrire du code de jeu enchevêtré. Les avantages du modèle de composant découplé (utilisé judicieusement) sont énormes.
la source
Drawable
)GameComponent
pour fournir l' architecture de votre jeu, en raison de la perte de contrôle explicite sur tirage / commande de mise à jour (vous êtes d' accord avec dans # 3) la rigidité de leurs interfaces (que vous n'abordez pas).Je suis un peu en désaccord avec Andrew, mais je pense aussi qu'il y a un temps et un lieu pour tout. Je n'utiliserais pas un
Drawable(GameComponent)
pour quelque chose d'aussi simple qu'un Sprite ou un Ennemi. Cependant, je l'utiliserais pour unSpriteManager
ouEnemyManager
.Pour en revenir à ce qu'Andrew a dit à propos de l'ordre de tirage et de mise à jour, je pense que ce
Sprite.DrawOrder
serait très déroutant pour de nombreux sprites. En outre, il est relativement facile de mettre en place unDrawOrder
etUpdateOrder
pour un petit nombre (ou raisonnable) de gestionnaires qui le sont(Drawable)GameComponents
.Je pense que Microsoft les aurait supprimés s'ils avaient vraiment été rigides et que personne ne les avait utilisés. Mais ils ne l'ont pas parce qu'ils fonctionnent parfaitement bien, si le rôle est bon. Je les utilise moi-même pour développer des
Game.Services
mises à jour en arrière-plan.la source
J'y pensais aussi, et je me suis dit: par exemple, pour voler l'exemple dans votre lien, dans un jeu RTS, vous pourriez faire de chaque unité une instance de composant de jeu. D'accord.
Mais vous pouvez également créer un composant UnitManager, qui conserve une liste d'unités, les dessine et les met à jour si nécessaire. J'imagine que la raison pour laquelle vous voulez transformer vos unités en composants de jeu en premier lieu est de compartimenter le code, alors cette solution atteindrait-elle adéquatement cet objectif?
la source
Dans les commentaires, j'ai posté une version condensée de ma réponse ancienne réponse:
L'utilisation
DrawableGameComponent
vous enferme dans un seul ensemble de signatures de méthode et dans un modèle de données conservé spécifique. Ce n'est généralement pas le bon choix au départ, et le verrouillage gêne considérablement l'évolution du code à l'avenir.Ici, je vais essayer d'illustrer pourquoi c'est le cas en publiant du code de mon projet actuel.
L'objet racine qui maintient l'état du monde du jeu a une
Update
méthode qui ressemble à ceci:Il existe un certain nombre de points d'entrée vers
Update
, selon que le jeu fonctionne ou non sur le réseau. Il est possible, par exemple, que l'état du jeu soit restauré à un point précédent dans le temps, puis simulé de nouveau.Il existe également des méthodes de mise à jour supplémentaires, telles que:
Dans l'état du jeu, il y a une liste d'acteurs à mettre à jour. Mais c'est plus qu'un simple
Update
appel. Il y a des passes supplémentaires avant et après pour gérer la physique. Il y a aussi des trucs de fantaisie pour la génération / destruction différée des acteurs, ainsi que des transitions de niveau.En dehors de l'état du jeu, il existe un système d'interface utilisateur qui doit être géré de manière indépendante. Mais certains écrans de l'interface utilisateur doivent également pouvoir s'exécuter dans un contexte réseau.
Pour le dessin, en raison de la nature du jeu, il existe un système de tri et d'élimination personnalisé qui prend les entrées de ces méthodes:
Et puis, une fois qu'il sait quoi dessiner, appelle automatiquement:
Le
tag
paramètre est obligatoire car certains acteurs peuvent s'accrocher à d'autres acteurs - et doivent donc pouvoir dessiner à la fois au-dessus et en dessous de l'acteur tenu. Le système de tri garantit que cela se produit dans le bon ordre.Il y a aussi le système de menus à dessiner. Et un système hiérarchique pour dessiner l'interface utilisateur, autour du HUD, autour du jeu.
Comparez maintenant ces exigences avec ce qui
DrawableGameComponent
fournit:Il n'y a aucun moyen raisonnable de passer d'un système utilisant
DrawableGameComponent
le système décrit ci-dessus. (Et ce ne sont pas des exigences inhabituelles pour un jeu d'une complexité même modeste).De plus, il est très important de noter que ces exigences ne sont pas simplement apparues au début du projet. Ils ont évolué au cours de plusieurs mois de travail de développement.
Ce que les gens font généralement, s'ils commencent sur la
DrawableGameComponent
route, c'est qu'ils commencent à construire sur des hacks:Au lieu d'ajouter des méthodes, ils effectuent une conversion moche de leur propre type de base (pourquoi ne pas simplement l'utiliser directement?).
Au lieu de remplacer le code pour trier et appeler
Draw
/Update
, ils font des choses très lentes pour changer les numéros de commande à la volée.Et le pire de tout: au lieu d'ajouter des paramètres aux méthodes, ils commencent à "passer" des "paramètres" dans des emplacements de mémoire qui ne sont pas la pile (l'utilisation de
Services
pour cela est populaire). C'est compliqué, sujet aux erreurs, lent, fragile, rarement thread-safe et susceptible de devenir un problème si vous ajoutez des réseaux, créez des outils externes, etc.Cela rend également la réutilisation du code plus difficile , car maintenant vos dépendances sont cachées à l'intérieur du "composant", au lieu d'être publiées à la limite de l'API.
Ou, ils voient presque à quel point tout cela est fou, et commencent à faire des "managers"
DrawableGameComponent
pour contourner ces problèmes. Sauf qu'ils ont toujours ces problèmes aux frontières du "manager".Invariablement, ces «gestionnaires» pourraient facilement être remplacés par une série d'appels de méthode dans l'ordre souhaité. Tout le reste est trop compliqué.
Bien sûr, une fois que vous commencez à utiliser ces hacks, il faut du vrai travail pour les annuler une fois que vous réalisez que vous en avez besoin de plus que ce qui est
DrawableGameComponent
fourni. Vous devenez " enfermé ". Et la tentation est souvent de continuer à empiler des hacks - ce qui, en pratique, vous enlèvera et limitera les types de jeux que vous pouvez créer à des jeux relativement simplistes.Beaucoup de gens semblent arriver à ce point et avoir une réaction viscérale - peut-être parce qu'ils utilisent
DrawableGameComponent
et ne veulent pas accepter d'avoir "tort". Ils s'accrochent donc au fait qu'il est au moins possible de le faire "fonctionner".Bien sûr, cela peut être fait pour "fonctionner"! Nous sommes des programmeurs - faire fonctionner les choses sous des contraintes inhabituelles est pratiquement notre travail. Mais cette retenue n'est pas nécessaire, utile ou rationnelle ...
Ce qui est particulièrement ahurissant est à ce sujet , il est étonnamment trivial à pas de côté cette question ensemble. Ici, je vais même vous donner le code:
(Il est simple d'ajouter le tri selon
DrawOrder
etUpdateOrder
. Une façon simple est de gérer deux listes et de les utiliserList<T>.Sort()
. Il est également trivial de gérerLoad
/UnloadContent
.)Peu importe ce que ce code est réellement . Ce qui est important, c'est que je puisse le modifier trivialement .
Quand je me rends compte que j'ai besoin d'un système de chronométrage différent
gameTime
, ou j'ai besoin de plus de paramètres (ou d'unUpdateContext
objet entier avec environ 15 choses dedans), ou j'ai besoin d'un ordre d'opérations complètement différent pour le dessin, ou j'ai besoin de méthodes supplémentaires pour différentes passes de mise à jour, je peux simplement aller de l'avant et le faire .Et je peux le faire immédiatement. Je n'ai pas besoin de me préoccuper de choisir
DrawableGameComponent
mon code. Ou pire: piratez-le d'une manière qui rendra encore plus difficile la prochaine fois qu'un tel besoin se manifestera.Il n'y a vraiment qu'un seul scénario où c'est un bon choix à utiliser
DrawableGameComponent
: et c'est si vous créez et publiez littéralement un middleware de type glisser-déposer pour XNA. C'était son but d'origine. Ce que - j'ajouterai - personne ne fait!(De même, cela n'a vraiment de sens que
Services
lorsque vous créez des types de contenu personnalisés pourContentManager
.)la source
DrawableGameComponent
certains composants, en particulier pour des choses comme les écrans de menu (menus, beaucoup de boutons, options et choses), ou les superpositions FPS / debug vous avez mentionné. Dans ces cas, vous n'avez généralement pas besoin d'un contrôle fin sur l'ordre de tirage et vous vous retrouvez avec moins de code que vous devez écrire manuellement (ce qui est une bonne chose, sauf si vous devez écrire ce code). Mais je suppose que c'est à peu près le scénario de glisser-déposer dont vous parliez.