Ich habe an vielen Stellen gelesen, dass DrawableGameComponents für Dinge wie "Level" oder irgendeine Art von Managern gespeichert werden sollten, anstatt sie beispielsweise für Charaktere oder Kacheln zu verwenden (wie dieser Typ hier sagt ). Aber ich verstehe nicht, warum das so ist. Ich habe diesen Beitrag gelesen und es hat mir sehr viel Sinn gemacht, aber das sind die Minderheiten.
Normalerweise würde ich solchen Dingen nicht allzu viel Aufmerksamkeit schenken, aber in diesem Fall würde ich gerne wissen, warum die offensichtliche Mehrheit glaubt, dass dies nicht der richtige Weg ist. Vielleicht fehlt mir etwas.
xna
game-component
Kenji Kina
quelle
quelle
DrawableGameComponent
sperrt Sie in eine einzelne Gruppe von Methodensignaturen und ein bestimmtes beibehaltenes Datenmodell. Dies ist anfangs in der Regel die falsche Wahl, und die Sperrung behindert die zukünftige Entwicklung des Codes erheblich. "Aber lassen Sie mich Ihnen sagen, was hier passiert ...DrawableGameComponent
ist Teil der Kern-XNA-API (wieder: meist aus historischen Gründen). Anfänger benutzen es, weil es "da" ist und "funktioniert" und sie es nicht besser wissen. Sie sehen, wie meine Antwort ihnen sagt, dass sie " falsch " sind und - aufgrund der Natur der menschlichen Psychologie - das ablehnen und die andere "Seite" auswählen. Das ist VeraShackles argumentative Nichtantwort; Das hat Momentum aufgenommen, weil ich es nicht sofort gerufen habe (siehe diese Kommentare). Ich habe das Gefühl, dass meine eventuelle Widerlegung dort ausreicht.Antworten:
"Game Components" waren ein sehr früher Bestandteil des XNA 1.0-Designs. Sie sollten wie Steuerelemente für WinForms funktionieren. Die Idee war, dass Sie sie per Drag-and-Drop in Ihr Spiel ziehen können, indem Sie eine grafische Benutzeroberfläche verwenden (ähnlich dem Bit zum Hinzufügen nicht-visueller Steuerelemente wie Timer usw.) und Eigenschaften für sie festlegen.
Sie könnten also Komponenten wie eine Kamera oder einen ... FPS-Zähler haben? ... oder ...?
Siehst du? Leider funktioniert diese Idee bei realen Spielen nicht wirklich. Die Komponenten, die Sie für ein Spiel benötigen, können nicht wirklich wiederverwendet werden. Möglicherweise haben Sie eine "Player" -Komponente, diese unterscheidet sich jedoch erheblich von der "Player" -Komponente jedes anderen Spiels.
Ich stelle mir aus diesem Grund und aus Zeitgründen vor, dass die grafische Benutzeroberfläche vor XNA 1.0 erstellt wurde.
Die API ist jedoch weiterhin verwendbar. Deshalb sollten Sie sie nicht verwenden:
Grundsätzlich kommt es darauf an, was als Spieleentwickler am einfachsten zu schreiben und zu lesen, zu debuggen und zu warten ist. Machen Sie so etwas in Ihrem Ladecode:
Ist viel weniger klar als dies einfach zu tun:
Vor allem, wenn Sie in einem realen Spiel am Ende etwas Ähnliches vorfinden:
(Zugegeben, Sie könnten die Kamera und SpriteBatch mit der ähnlichen
Services
Architektur gemeinsam nutzen. Aber das ist auch aus dem gleichen Grund eine schlechte Idee.)Diese Architektur - oder deren Fehlen - ist viel leichter und flexibler!
Ich kann die Zeichenreihenfolge einfach ändern, mehrere Ansichtsfenster hinzufügen oder einen Rendereffekt hinzufügen, indem ich nur ein paar Zeilen ändere. Um es in dem anderen System zu tun, muss ich darüber nachdenken, was all diese Komponenten - verteilt auf mehrere Dateien - tun und in welcher Reihenfolge.
Es ist wie der Unterschied zwischen deklarativer und imperativer Programmierung. Es stellt sich heraus, dass für die Entwicklung von Spielen eine zwingende Programmierung weitaus vorzuziehen ist.
quelle
DrawableGameComponent
.player.DrawOrder = 100;
Methode der Zeichnungsreihenfolge vorzuziehen . Ich beende gerade ein Flixel-Spiel, bei dem Objekte in der Reihenfolge gezeichnet werden, in der Sie sie der Szene hinzufügen. Das FlxGroup-System verringert einige dieser Probleme, aber es wäre schön gewesen, eine Art Z-Index-Sortierung eingebaut zu haben.Hmmm. Ich fürchte, ich habe diesen herausgefordert, um ein bisschen Gleichgewicht hier zu haben.
In Andrews Antwort scheinen sich 5 Anklagen zu befinden, also werde ich versuchen, sie nacheinander zu behandeln:
1) Komponenten sind veraltet und aufgegeben.
Die Idee des Entwurfsmusters von Komponenten existierte lange vor XNA - im Wesentlichen ist es einfach eine Entscheidung, Objekte anstelle des übergeordneten Spielobjekts zum Zuhause ihres eigenen Update- und Draw-Verhaltens zu machen. Für Objekte in seiner Komponentensammlung ruft das Spiel diese Methoden (und einige andere) zum entsprechenden Zeitpunkt automatisch auf - entscheidend ist, dass das Spielobjekt selbst keinen dieser Codes „kennen“ muss. Wenn Sie Ihre Spielklassen in verschiedenen Spielen (und damit verschiedenen Spielobjekten) wiederverwenden möchten, ist diese Entkopplung von Objekten grundsätzlich immer noch eine gute Idee. Ein kurzer Blick auf die neuesten (professionell produzierten) Demos von XNA4.0 aus dem AppHub bestätigt meinen Eindruck, dass Microsoft immer noch (zu Recht in meinen Augen) fest dahinter steckt.
2) Andrew kann sich nicht mehr als zwei wiederverwendbare Spielklassen vorstellen.
Ich kann. Tatsächlich kann ich an Lasten denken! Ich habe gerade meine aktuelle freigegebene Bibliothek überprüft und sie umfasst mehr als 80 wiederverwendbare Komponenten und Dienste. Um Ihnen einen Vorgeschmack zu geben, könnten Sie Folgendes haben:
Für eine bestimmte Spielidee sind mindestens 5-10 Dinge aus diesem Satz erforderlich - garantiert - und sie können in einem völlig neuen Spiel mit einer Codezeile voll funktionsfähig sein.
3) Die Steuerung der Zeichnungsreihenfolge durch die Reihenfolge der expliziten Zeichnungsaufrufe ist der Verwendung der DrawOrder-Eigenschaft der Komponente vorzuziehen.
Nun, ich stimme Andrew in diesem Punkt im Grunde zu. ABER, wenn Sie alle DrawOrder-Eigenschaften Ihrer Komponente als Standardeinstellungen belassen, können Sie ihre Zeichenreihenfolge weiterhin explizit durch die Reihenfolge steuern, in der Sie sie der Auflistung hinzufügen. Dies ist in Bezug auf die Sichtbarkeit wirklich identisch mit der expliziten Reihenfolge ihrer manuellen Ziehungsaufrufe.
4) Das Aufrufen einer Reihe von Draw-Methoden eines Objekts ist einfacher als das Aufrufen durch eine vorhandene Framework-Methode.
Warum? Außerdem überschatten Ihre Aktualisierungslogik und Ihr Draw-Code in allen Projekten, mit Ausnahme der trivialsten, Ihre Methodenaufrufe in Bezug auf Leistungseinbußen in jedem Fall völlig.
5) Das Platzieren Ihres Codes in einer Datei ist beim Debuggen vorzuziehen, anstatt ihn in der Datei des einzelnen Objekts zu haben.
Nun, erstens, wenn ich in Visual Studio debugge, ist der Speicherort eines Haltepunkts in Bezug auf Dateien auf der Festplatte heutzutage fast unsichtbar - die IDE behandelt den Speicherort ohne jegliches Drama. Bedenken Sie aber auch für einen Moment, dass Sie ein Problem mit Ihrer Skybox-Zeichnung haben. Ist das Wechseln zur Draw-Methode der Skybox wirklich aufwändiger als das Auffinden des Skybox-Zeichencodes in der (vermutlich sehr langen) Master- Draw-Methode im Game-Objekt? Nein, nicht wirklich - in der Tat würde ich behaupten, dass es das Gegenteil ist.
Also, zusammenfassend - schauen Sie sich bitte Andrews Argumente hier genau an. Ich glaube nicht, dass sie tatsächlich eine wesentliche Begründung für das Schreiben von verwickeltem Spielcode geben. Die positiven Seiten des entkoppelten Komponentenmusters (mit Bedacht verwendet) sind massiv.
quelle
Drawable
)GameComponent
, um Ihre Spielarchitektur bereitzustellen, da die explizite Kontrolle über die Zeichnungs- / Aktualisierungsreihenfolge (mit der Sie in # 3 einverstanden sind) in Bezug auf die Steifigkeit verloren gegangen ist ihrer Schnittstellen (die Sie nicht ansprechen).Ich bin ein bisschen anderer Meinung als Andrew, aber ich denke auch, dass es für alles eine Zeit und einen Ort gibt. Ich würde a nicht
Drawable(GameComponent)
für etwas verwenden, das so einfach ist wie ein Sprite oder ein Enemy. Ich würde es jedoch für einSpriteManager
oder verwendenEnemyManager
.Wenn ich noch einmal auf Andrews Ausführungen zur Reihenfolge des Zeichnens und Aktualisierens zurückkomme,
Sprite.DrawOrder
wäre das für viele Sprites sehr verwirrend. Darüber hinaus ist es relativ einfach, eineDrawOrder
undUpdateOrder
für eine kleine (oder angemessene) Anzahl von Managern einzurichten(Drawable)GameComponents
.Ich denke, dass Microsoft sie beseitigt hätte, wenn sie wirklich so starr gewesen wären und niemand sie benutzt hätte. Aber sie haben es nicht, weil sie perfekt funktionieren, wenn die Rolle stimmt. Ich selbst benutze sie, um zu entwickeln
Game.Services
, die im Hintergrund aktualisiert werden.quelle
Ich habe auch darüber nachgedacht, und mir ist der Gedanke gekommen: Um zum Beispiel das Beispiel in Ihrem Link zu stehlen, könnten Sie in einem RTS-Spiel jede Einheit zu einer Instanz einer Spielkomponente machen. Okay.
Sie können aber auch eine UnitManager-Komponente erstellen, in der eine Liste der Einheiten gespeichert, gezeichnet und bei Bedarf aktualisiert wird. Ich denke, der Grund, warum Sie Ihre Einheiten in erster Linie zu Spielkomponenten machen wollen, ist die Unterteilung des Codes. Würde diese Lösung dieses Ziel also angemessen erreichen?
quelle
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: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:
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:
Und wenn es dann herausfindet, was zu zeichnen ist, ruft es automatisch auf:
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
: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 ersetzenUpdate
, 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:
(Es ist einfach, eine Sortierung nach
DrawOrder
und hinzuzufügenUpdateOrder
. Eine einfache Möglichkeit besteht darin, zwei Listen zu verwalten und zu verwendenList<T>.Sort()
. Es ist auch trivial, mitLoad
/ umzugehenUnloadContent
.)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 ganzesUpdateContext
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 sinnvoll
Services
, benutzerdefinierte Inhaltstypen für zu erstellenContentManager
.)quelle
DrawableGameComponent
für bestimmte Komponenten erben , insbesondere für Dinge wie Menübildschirme (Menüs, viele Schaltflächen, Optionen und Dinge) oder die FPS / Debug-Überlagerungen du erwähntest. In diesen Fällen benötigen Sie normalerweise keine genaue Kontrolle über die Zeichenreihenfolge und Sie müssen weniger Code manuell schreiben (was eine gute Sache ist, es sei denn, Sie müssen diesen Code schreiben). Aber ich denke, das ist so ziemlich das Drag-and-Drop-Szenario, das Sie erwähnt haben.