In Kommentaren habe ich eine gekürzte Version meiner alten Antwort gepostet:
Die Verwendung DrawableGameComponent
sperrt Sie in einen einzelnen Satz von Methodensignaturen und in ein bestimmtes beibehaltenes Datenmodell. Dies ist normalerweise anfangs die falsche Wahl, und das Lock-In behindert die zukünftige Entwicklung des Codes erheblich.
Hier versuche ich zu veranschaulichen, warum dies der Fall ist, indem ich Code aus meinem aktuellen Projekt veröffentliche.
Das Root-Objekt, das den Status der Spielwelt beibehält, hat eine Update
Methode, die folgendermaßen aussieht:
void Update(UpdateContext updateContext, MultiInputState currentInput)
Es gibt eine Reihe von Einstiegspunkten Update
, je nachdem, ob das Spiel im Netzwerk läuft oder nicht. Beispielsweise kann der Spielstatus auf einen vorherigen Zeitpunkt zurückgesetzt und dann erneut simuliert werden.
Es gibt auch einige zusätzliche update-ähnliche Methoden, wie zum Beispiel:
void PlayerJoin(int playerIndex, string playerName, UpdateContext updateContext)
Innerhalb des Spielstatus gibt es eine Liste der zu aktualisierenden Akteure. Dies ist jedoch mehr als ein einfacher Update
Anruf. Es gibt zusätzliche Pässe vor und nach der Physik. Es gibt auch einige ausgefallene Dinge für das zeitversetzte Laichen / Zerstören von Schauspielern sowie für Levelübergänge.
Außerhalb des Spielstatus gibt es ein UI-System, das unabhängig verwaltet werden muss. Einige Bildschirme in der Benutzeroberfläche müssen jedoch auch in einem Netzwerkkontext ausgeführt werden können.
Zum Zeichnen gibt es aufgrund der Art des Spiels ein benutzerdefiniertes Sortier- und Auslesesystem, das Eingaben von den folgenden Methoden übernimmt:
virtual void RegisterToDraw(SortedDrawList sortedDrawList, Definitions definitions)
virtual void RegisterShadow(ShadowCasterList shadowCasterList, Definitions definitions)
Und wenn es dann herausfindet, was zu zeichnen ist, ruft es automatisch auf:
virtual void Draw(DrawContext drawContext, int tag)
Das tag
Parameter ist erforderlich, da einige Akteure sich an andere Akteure halten können und daher sowohl über als auch unter dem gehaltenen Akteur zeichnen können müssen. Das Sortiersystem sorgt dafür, dass dies in der richtigen Reihenfolge geschieht.
Es gibt auch das Menüsystem zum Zeichnen. Und ein hierarchisches System zum Zeichnen der Benutzeroberfläche rund um das Spiel.
Jetzt vergleicht diese Anforderungen mit dem, was es DrawableGameComponent
:
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; }
}
Es gibt keine vernünftige Möglichkeit, von einem System mit DrawableGameComponent
zu dem oben beschriebenen System zu gelangen . (Und das sind keine ungewöhnlichen Anforderungen für ein Spiel von selbst bescheidener Komplexität).
Es ist auch sehr wichtig zu beachten, dass diese Anforderungen nicht einfach zu Beginn des Projekts entstanden sind. Sie haben sich über viele Monate der Entwicklungsarbeit entwickelt.
Normalerweise beginnen die Leute, wenn sie auf der DrawableGameComponent
Strecke beginnen, auf Hacks aufzubauen:
Anstatt Methoden hinzuzufügen, machen sie ein hässliches Downcasting für ihren eigenen Basistyp (warum nicht einfach das direkt verwenden?).
Anstatt den Code zum Sortieren und Aufrufen von Draw
/ zu ersetzen Update
, werden einige sehr langsame Aktionen ausgeführt, um die Bestellnummern im laufenden Betrieb zu ändern.
Und das Schlimmste von allem: Anstatt den Methoden Parameter hinzuzufügen, "übergeben" sie Parameter an Speicherorte, die nicht der Stack sind (die Verwendung Services
hierfür ist sehr beliebt). Dies ist kompliziert, fehleranfällig, langsam, zerbrechlich, selten threadsicher und wird wahrscheinlich zu einem Problem, wenn Sie Netzwerke hinzufügen, externe Tools erstellen usw.
Es macht auch Wiederverwendung von Code schwieriger , denn jetzt Ihre Abhängigkeiten innerhalb der „Komponente“ versteckt sind, statt auf der API - Grenze veröffentlicht.
Oder sie sehen fast , wie verrückt das alles ist, und beginnen, "Manager" DrawableGameComponent
zu tun , um diese Probleme zu umgehen. Außer sie haben noch diese Probleme an den "Manager" -Grenzen.
Diese "Manager" könnten immer leicht durch eine Reihe von Methodenaufrufen in der gewünschten Reihenfolge ersetzt werden. Alles andere ist zu kompliziert.
Sobald Sie mit dem Einsatz dieser Hacks beginnen, ist es natürlich sehr aufwändig, diese Hacks rückgängig zu machen , sobald Sie feststellen, dass Sie mehr benötigen, als Sie DrawableGameComponent
bereitstellen. Sie werden " eingesperrt ". Und die Versuchung besteht oft darin, sich weiterhin auf Hacks zu stapeln - was Sie in der Praxis stören und die Art der Spiele, die Sie machen können, auf relativ einfache Spiele beschränken wird.
Viele Menschen scheinen an diesem Punkt angelangt zu sein und eine viszerale Reaktion zu haben - möglicherweise, weil sie DrawableGameComponent
"Unrecht" verwenden und nicht akzeptieren wollen. Sie klammern sich also an die Tatsache, dass es zumindest möglich ist , es "funktionieren" zu lassen.
Natürlich kann es gemacht werden, um "zu arbeiten"! Wir sind Programmierer - es ist praktisch unsere Aufgabe, Dinge unter ungewöhnlichen Bedingungen zum Laufen zu bringen. Aber diese Zurückhaltung ist nicht notwendig, nützlich oder rational ...
Besonders verblüffend ist, dass es erstaunlich trivial ist, diese ganze Angelegenheit zu umgehen . Hier gebe ich dir sogar den 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);
(Es ist einfach, eine Sortierung nach DrawOrder
und hinzuzufügen UpdateOrder
. Eine einfache Möglichkeit besteht darin, zwei Listen zu verwalten und zu verwenden List<T>.Sort()
. Es ist auch trivial, mit Load
/ umzugehen UnloadContent
.)
Es ist nicht wichtig, was dieser Code tatsächlich ist . Wichtig ist, dass ich es einfach modifizieren kann .
Wenn ich feststelle, dass ich ein anderes Timing-System gameTime
benötige oder mehr Parameter (oder ein ganzes UpdateContext
Objekt mit etwa 15 Dingen) oder eine völlig andere Reihenfolge für das Zeichnen oder einige zusätzliche Methoden benötige für verschiedene Update - Pässe kann, gehe ich einfach weiter und das tun .
Und ich kann es sofort tun. Ich muss mich nicht darum kümmern DrawableGameComponent
, meinen Code herauszusuchen. Oder noch schlimmer: Hacken Sie es in eine Richtung, die es beim nächsten Mal noch schwieriger macht, wenn ein solches Bedürfnis besteht.
Es gibt wirklich nur ein Szenario, in dem dies eine gute Wahl DrawableGameComponent
ist: Wenn Sie buchstäblich Middleware im Drag-and-Drop-Komponentenstil für XNA erstellen und veröffentlichen. Welches war sein ursprünglicher Zweck. Welches - ich werde hinzufügen - niemand tut!
(Ebenso ist es nur sinnvollServices
, benutzerdefinierte Inhaltstypen für zu erstellen ContentManager
.)