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:
void Update(UpdateContext updateContext, MultiInputState currentInput)
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:
void PlayerJoin(int playerIndex, string playerName, UpdateContext updateContext)
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:
virtual void RegisterToDraw(SortedDrawList sortedDrawList, Definitions definitions)
virtual void RegisterShadow(ShadowCasterList shadowCasterList, Definitions definitions)
Et puis, une fois qu'il sait quoi dessiner, appelle automatiquement:
virtual void Draw(DrawContext drawContext, int tag)
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:
public class DrawableGameComponent
{
public virtual void Update(GameTime gameTime);
public virtual void Draw(GameTime gameTime);
public int UpdateOrder { get; set; }
public int DrawOrder { get; set; }
}
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:
public class Actor
{
public virtual void Update(GameTime gameTime);
public virtual void Draw(GameTime gameTime);
}
List<Actor> actors = new List<Actor>();
foreach(var actor in actors)
actor.Update(gameTime);
foreach(var actor in actors)
actor.Draw(gameTime);
(Il est simple d'ajouter le tri selon DrawOrder
et UpdateOrder
. Une façon simple est de gérer deux listes et de les utiliser List<T>.Sort()
. Il est également trivial de gérer Load
/ 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'un UpdateContext
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 queServices
lorsque vous créez des types de contenu personnalisés pour ContentManager
.)