Wie vermeide ich das GameManager-Gott-Objekt?

51

Ich habe gerade eine Antwort auf eine Frage zur Strukturierung des Spielcodes gelesen . Ich wunderte mich über die allgegenwärtige GameManagerKlasse und wie oft es in einer Produktionsumgebung zu Problemen kommt. Lassen Sie mich das beschreiben.

Erstens gibt es Prototyping. Niemand kümmert sich darum, tollen Code zu schreiben. Wir versuchen nur, etwas zum Laufen zu bringen, um zu sehen, ob das Gameplay stimmt.

Dann gibt es ein grünes Licht, und in dem Bestreben, Dinge aufzuräumen, schreibt jemand eine GameManager. Wahrscheinlich, um ein paar zu haben GameStates, vielleicht, um ein paar aufzubewahren GameObjects, eigentlich nichts Großes. Ein süßer, kleiner Manager.

Im friedlichen Bereich der Vorproduktion entwickelt sich das Spiel gut. Codierer haben eine gute Nachtruhe und viele Ideen, um das Ding mit großartigen Designmustern zu versehen.

Dann geht die Produktion an und schon bald ist Crunch-Time. Eine ausgewogene Ernährung ist längst vorbei, der Bug-Tracker ist voller Probleme, die Leute sind gestresst und das Spiel muss gestern veröffentlicht werden.

Zu diesem Zeitpunkt ist das normalerweise GameManagerein großes Durcheinander (um höflich zu bleiben).

Der Grund dafür ist einfach. Schließlich ist beim Schreiben eines Spiels der gesamte Quellcode vorhanden, um das Spiel zu verwalten . Es ist ganz einfach, diese kleine zusätzliche Funktion oder einen Bugfix in den Ordner einzufügen, in dem GameManagerohnehin alles andere bereits gespeichert ist. Wenn die Zeit zum Problem wird, besteht keine Möglichkeit, eine separate Klasse zu schreiben oder diesen riesigen Manager in Untermanager aufzuteilen.

Natürlich ist dies ein klassisches Anti-Muster: das Gott-Objekt . Es ist eine schlechte Sache, ein Schmerz zu verschmelzen, ein Schmerz zu bewahren, ein Schmerz zu verstehen, ein Schmerz zu transformieren.

Was würden Sie vorschlagen, um dies zu verhindern?

BEARBEITEN

Ich weiß, dass es verlockend ist, die Namensgebung zu beschuldigen. Natürlich GameManagerist es keine so gute Idee , eine Klasse zu schaffen . Aber das gleiche kann zu einem sauberen passieren GameApplicationoder GameStateMachineoder GameSystemdass endet der am Ende eines Projektes Kanal-Band-Favorit zu sein. Unabhängig von der Benennung haben Sie diese Klasse irgendwo in Ihrem Spielcode, es ist nur ein Embryo für den Moment: Sie wissen nur noch nicht, welches Monster es werden wird. Ich erwarte also keine "Schuld der Namensgebung" -Antworten. Ich möchte einen Weg finden, um dies zu verhindern, eine Codierungsarchitektur und / oder einen Prozess, dem ein Team folgen kann, wenn es weiß, dass dies irgendwann in der Produktion passieren wird. Es ist einfach zu schade, einen Monat Bugfixes und Last-Minute-Features wegzuwerfen, nur weil sie sich jetzt alle in einem riesigen, nicht mehr zu wartenden Manager befinden.

Laurent Couvidou
quelle
Egal, ob es GameManager oder ControllerOfTheGame heißt oder wie auch immer Sie es nennen möchten, ich meine, vermeiden Sie eine Klasse, die für die Steuerung der Logik für das gesamte Spiel verantwortlich ist. Sie wissen, dass Sie es tun, wenn Sie es tun, unabhängig davon, wie Sie es nennen möchten. Ich glaube, in meinem letzten Spiel hatte ich einen Level-Controller, der nur gestartet wurde, um den Level-Status zum Speichern beizubehalten, aber wenn ich angehalten und darüber nachgedacht hätte, wäre es offensichtlich, dass dieses Objekt mehr Details verarbeiten sollte, als es sollte.
Brandon
@brandon Lassen Sie mich meine Frage für Sie umformulieren: Wie steuern Sie die Logik des gesamten Spiels, ohne sich dem GameManageroben beschriebenen Effekt auszusetzen ?
Laurent Couvidou
Kommt auf die Logik an. Die offensichtlichen Dinge wie Spieler, Feind, Struktur usw., die eindeutig eine eigene Logik haben, werden in ihre jeweiligen Klassen eingeteilt. Was Sie anscheinend am meisten fragen, ist der Stand des Spiels. Es wird normalerweise eine Klasse geben, die den Status des gesamten Spiels verfolgt, aber meiner Erfahrung nach ist dies eines der letzten Dinge, über die Sie sich beim Prototyping Gedanken machen müssen, und der Zweck sollte klar sein. Grundsätzlich müssen Sie entweder vor dem Prototyping eine erste Planung durchführen oder bei Produktionsbeginn bei Null anfangen, um zu vermeiden, dass Junk-Klassen verwendet werden, die nur zum Testen gedacht sind.
Brandon
Interessanterweise schafft XNA diese potenzielle Katastrophe für Sie. Standardmäßig wird eine Spielklasse erstellt, um alles zu verwalten. Es enthält die Hauptmethoden zum Aktualisieren, Zeichnen, Initialisieren und Laden. Ich bin gerade dabei, zu einem neuen Projekt zu migrieren, weil mein (nicht so komplexes) Spiel zu schwierig geworden ist, mit allem, was in dieser Klasse steckt. Darüber hinaus scheinen Microsoft-Tutorials dies zu fördern.
Fibericon

Antworten:

23

Dann gibt es ein grünes Licht und in dem Bestreben, die Dinge zu bereinigen, schreibt jemand einen GameManager. Wahrscheinlich, um ein paar GameStates zu haben, vielleicht, um ein paar GameObjects zu speichern, eigentlich nichts Großes. Ein süßer, kleiner Manager.

Wissen Sie, als ich das las, ging mir ein kleiner Alarm durch den Kopf. Ein Objekt mit dem Namen "GameManager" wird niemals süß oder klein sein. Und jemand hat das getan, um den Code zu bereinigen? Wie sah es vorher aus? OK, Scherze beiseite: Der Name einer Klasse sollte ein klarer Hinweis darauf sein, was die Klasse tut, und dies sollte eine Sache sein (auch bekannt als Prinzip der einmaligen Verantwortung ).

Es kann auch vorkommen, dass Sie am Ende ein Objekt wie GameManager haben, das jedoch auf einer sehr hohen Ebene vorhanden ist und sich mit Aufgaben auf hoher Ebene befassen sollte. Einen Katalog mit Spielobjekten pflegen? Vielleicht. Erleichterung der Kommunikation zwischen Spielobjekten? Sicher. Berechnen Sie Kollisionen zwischen Objekten? Nein! Dies ist auch der Grund, warum der Name Manager verpönt ist - er ist zu weit gefasst und lässt unter diesem Banner viel Missbrauch zu.

Eine kurze Faustregel für Klassengrößen: Wenn Sie auf mehrere hundert Codezeilen pro Klasse stoßen, beginnt etwas schief zu laufen. Ohne übereifrig zu sein, ist irgendetwas über 300 LOC ein Codegeruch für mich, und wenn Sie über 1000 gehen, sollten Warnglocken losgehen. Indem Sie glauben, dass 1000 Codezeilen einfacher zu verstehen sind als 4 gut strukturierte Klassen zu je 250, täuschen Sie sich.

Wenn die Zeit zum Problem wird, besteht keine Möglichkeit, eine separate Klasse zu schreiben oder diesen riesigen Manager in Untermanager aufzuteilen.

Ich denke, das ist nur der Fall, weil das Problem sich so weit ausbreiten darf, dass alles völlig durcheinander ist. Die Praxis des Refactorings ist genau das, wonach Sie suchen - Sie müssen das Design des Codes in winzigen Schritten kontinuierlich verbessern .

Was würden Sie vorschlagen, um dies zu verhindern?

Das Problem ist kein technologisches Problem, daher sollten Sie nicht nach technologischen Lösungen suchen. Das Problem ist das Folgende: In Ihrem Team besteht die Tendenz, monolithische Codeteile zu erstellen, und die Überzeugung, dass es mittel- und langfristig von Vorteil ist, so zu arbeiten. Es scheint auch, dass dem Team ein starker architektonischer Vorsprung fehlt, der die Architektur des Spiels steuern würde (oder zumindest ist diese Person zu beschäftigt, um diese Aufgabe auszuführen). Grundsätzlich besteht der einzige Ausweg darin, dass die Teammitglieder erkennen, dass dieses Denken falsch ist. Es tut niemandem den Gefallen. Die Qualität des Produkts wird sich verschlechtern und das Team wird nur noch mehr Nächte damit verbringen, Dinge zu reparieren.

Die gute Nachricht ist, dass die unmittelbaren, greifbaren Vorteile des Schreibens von sauberem Code so groß sind, dass fast alle Entwickler ihre Vorteile sehr schnell erkennen. Überzeugen Sie das Team, eine Weile auf diese Weise zu arbeiten. Die Ergebnisse werden den Rest erledigen.

Der schwierige Teil ist, dass das Entwickeln eines Gefühls für das, was schlechten Code ausmacht (und das Talent, schnell ein besseres Design zu finden), eine der schwierigeren Fähigkeiten ist, die in der Entwicklung zu erlernen sind. Mein Vorschlag hängt mit der Hoffnung zusammen, dass Sie jemanden im Team haben, der das kann - es ist viel einfacher, die Leute auf diese Weise zu überzeugen.

Bearbeiten - Ein bisschen mehr Info:

Im Allgemeinen glaube ich nicht, dass sich Ihr Problem auf die Spieleentwicklung beschränkt. Im Kern handelt es sich um ein Software-Engineering-Problem, daher meine Kommentare in diese Richtung. Was möglicherweise anders ist, ist die Art der Spieleentwicklungsbranche, ob sie ergebnis- und terminorientierter ist als andere Entwicklungstypen, da bin ich mir nicht sicher.

Speziell für die Spieleentwicklung lautet die akzeptierte Antwort auf diese Frage zu StackOverflow in Bezug auf Tipps zur "Speziellen Spielarchitektur":

Befolgen Sie die Solid-Prinzipien des objektorientierten Designs ....

Dies ist im Wesentlichen genau das, was ich sage. Wenn ich unter Druck stehe, schreibe ich auch große Teile des Codes, aber mir ist klar, dass dies eine technische Schuld ist. Was für mich in der Regel gut funktioniert, ist, die erste Hälfte (oder drei Viertel) des Tages damit zu verbringen, eine große Menge von Code mittlerer Qualität zu produzieren, sich dann zurückzulehnen und eine Weile darüber nachzudenken. Mach ein bisschen Design in meinem Kopf oder auf Papier / Whiteboard, um den Code ein bisschen zu verbessern. Oft stelle ich fest, dass sich Code wiederholt, und kann die Gesamtzahl der Codezeilen tatsächlich reduzieren, indem ich Dinge aufteile, während ich gleichzeitig die Lesbarkeit verbessere. Diesmal macht sich die Investition so schnell bezahlt, dass es albern klingt, es als "Investition" zu bezeichnen - ziemlich oft stelle ich Fehler fest, die meinen halben Tag (eine Woche später) verschwendet hätten, wenn ich sie weitergehen lassen hätte.

  • Beheben Sie die Probleme am selben Tag, an dem Sie sie codieren.
  • Sie werden froh sein, dass Sie es innerhalb weniger Stunden getan haben.

Es ist schwierig zu glauben, dass dies tatsächlich der Fall ist. Ich habe es nur für meine eigene Arbeit geschafft, weil ich die Auswirkungen immer wieder erlebt habe. Trotzdem ist es für mich immer noch schwierig, das Reparieren von Code zu rechtfertigen, wenn ich mehr herausbringen könnte ... Also verstehe ich definitiv, woher Sie kommen. Leider ist dieser Rat vielleicht zu allgemein und nicht einfach zu befolgen. Ich glaube jedoch fest daran! :)

Um Ihr spezielles Beispiel zu beantworten:

Onkel Bobs Clean Code macht eine erstaunliche Arbeit darin, zusammenzufassen, wie guter Qualitätscode ist. Ich bin mit fast allen Inhalten einverstanden. Wenn ich also an Ihr Beispiel einer 30 000-LOC-Manager-Klasse denke, kann ich dem Teil "Gute Gründe" nicht wirklich zustimmen. Ich möchte nicht beleidigend klingen, aber so zu denken wird zum Problem führen. Es gibt keinen guten Grund, so viel Code in einer einzigen Datei zu haben - es sind fast 1000 Seiten Text! Jeder Vorteil der Lokalität (Ausführungsgeschwindigkeit oder "Einfachheit" des Designs) wird sofort zunichte gemacht, wenn die Entwickler völlig festgefahren sind, um durch diese Monstrosität zu navigieren, und das noch bevor wir über das Zusammenführen usw. sprechen.

Wenn Sie nicht überzeugt sind, ist mein bester Vorschlag, sich ein Exemplar des obigen Buches zu schnappen und es durchzublättern. Das Anwenden dieser Art des Denkens auf Menschen führt dazu, dass sie freiwillig sauberen, selbsterklärenden Code erstellen, der gut strukturiert ist.

Daniel B
quelle
Dies ist kein Fehler, den ich einmal gesehen habe. Ich habe dies in mehreren Teams für mehrere Projekte gesehen. Ich habe viele talentierte, strukturierte Leute gesehen, die damit zu kämpfen hatten. Wenn Sie unter Druck stehen, interessiert sich Ihr Produzent nicht für die Beschränkung auf 300 Codezeilen. Auch der Spieler übrigens nicht. Sicher, das ist nett und süß, aber der Teufel steckt im Detail. Der schlimmste Fall, den GameManagerich bisher gesehen habe, waren ungefähr 30.000 Codezeilen, und ich bin mir ziemlich sicher, dass das meiste davon aus einem sehr guten Grund hier war. Das ist natürlich extrem, aber diese Dinge passieren in der realen Welt. Ich GameManagerfrage nach einer Möglichkeit, diesen Effekt zu begrenzen .
Laurent Couvidou
@lorancou - Ich habe dem Beitrag einige zusätzliche Informationen hinzugefügt, es war ein bisschen zu viel für einen Kommentar, hoffentlich gibt es Ihnen ein bisschen mehr Ideen, auch wenn ich Sie nicht auf eine konkrete Architektur hinweisen kann Beispiele.
Daniel B
Ah, ich habe das Buch nicht gelesen, also ist das definitiv ein guter Vorschlag. Oh, und ich wollte nicht, dass es einen guten Grund gibt, Code in einer Klasse mit Tausenden von Zeilen hinzuzufügen. Ich meinte, dass der gesamte Code in dieser Art von Gottobjekt etwas Nützliches bewirkt . Ich habe das wahrscheinlich etwas zu schnell geschrieben;)
Laurent Couvidou
@lorancou Hehe, das ist gut ... so liegt Wahnsinn :)
Daniel B
"Die Praxis des Refactorings ist genau das, wonach Sie suchen - Sie müssen das Design des Codes in winzigen Schritten kontinuierlich verbessern." Ich denke, Sie haben dort eine sehr starke Position. Chaos zieht Chaos an, das ist der wahre Grund, weshalb langfristig gegen Chaos vorgegangen werden muss. Ich werde diese Antwort akzeptieren, aber das bedeutet nicht, dass die anderen keinen Wert haben!
Laurent Couvidou
7

Dies ist kein wirkliches Problem bei der Spielprogrammierung, sondern bei der Programmierung im Allgemeinen.

Der gesamte Quellcode ist eigentlich hier, um das Spiel zu verwalten.

Aus diesem Grund sollten Sie bei jedem objektorientierten Codierer die Stirn runzeln, der Klassen mit "Manager" im Namen vorschlägt. Um fair zu sein, denke ich, dass es praktisch unmöglich ist, "halbgöttliche" Objekte im Spiel zu vermeiden, aber wir nennen sie einfach "System".

Jede Software neigt dazu, schmutzig zu werden, wenn die Frist nahe ist. Das gehört zum Job. Und an der " Kanaltyp-Programmierung " liegt von Natur aus nichts falsch , insbesondere dann, wenn Sie Ihr Produkt in wenigen Stunden versenden müssen. Sie können dieses Problem nicht vollständig beseitigen, sondern nur reduzieren.

Wenn Sie versucht sind, Dinge in den GameManager zu integrieren, bedeutet dies natürlich, dass Sie nicht über eine sehr gesunde Codebasis verfügen. Es könnte zwei Probleme geben:

  • Ihr Code ist zu komplex. Zu viele Muster, vorzeitige Optimierung, niemand versteht mehr den Ablauf. Versuchen Sie, bei der Entwicklung mehr TDD zu verwenden, wenn Sie können, da dies eine sehr gute Lösung zur Bekämpfung der Komplexität ist.

  • Ihr Code ist nicht gut strukturiert. Sie verwenden (ab) Globals Variable, die Klassen sind nicht lose gekoppelt, es gibt keinerlei Schnittstelle, kein Ereignissystem und so weiter.

Wenn der Code in Ordnung zu sein scheint, müssen Sie sich für jedes Projekt merken, was schief gelaufen ist, und ihn beim nächsten Mal vermeiden.

Schließlich könnte es sich auch um ein Teammanagement-Problem handeln: Gab es genug Zeit? Wussten alle von der Architektur, für die Sie sich entschieden haben? Wer zum Teufel hat es erlaubt, eine Klasse mit dem Wort "Manager" zu verpflichten?

Raveline
quelle
An der Klebebandprogrammierung ist nichts auszusetzen, das gebe ich Ihnen. Aber ich bin mir ziemlich sicher, dass Manager-Klassen in Spiel-Engines allgegenwärtig sind, zumindest habe ich sie oft gesehen. Sie sind nützlich, solange sie nicht zu groß werden ... Was ist los mit a SceneManageroder a NetworkManager? Ich verstehe nicht, warum wir sie verhindern sollen oder wie es hilft, -Manager durch -System zu ersetzen. Ich bin auf der Suche nach Vorschlägen für eine Ersatzarchitektur für das, was normalerweise in eine GameManager, etwas weniger Code-bloat-anfällige geht.
Laurent Couvidou
Was ist los mit einem SceneManager? Was ist der Unterschied zwischen einer Scene und einem SceneManager? Ein Netzwerk und ein Netzwerkmanager? Zum anderen: Ich denke nicht, dass dies ein Architekturproblem ist, aber wenn Sie ein konkretes Beispiel für etwas geben könnten, das sich im GameManager befindet und nicht vorhanden sein sollte, wäre es einfacher, eine Lösung vorzuschlagen.
Raveline
OK, also vergiss die -Manager-Sache. Nehmen wir an, Sie haben eine Klasse, die Ihre Spielzustände verwaltet, nennen wir sie GameStatesCollection. In den Anfängen haben Sie nur ein paar Spielzustände und Möglichkeiten, zwischen ihnen zu wechseln. Dann gibt es die Ladebildschirme, die all dies erschweren. Dann muss sich ein Programmierer mit einem Fehler auseinandersetzen, wenn der Benutzer die Disc auswirft, während Sie von Level_A auf Level_B wechseln, und die einzige Möglichkeit, ihn zu beheben, ohne einen halben Tag für das Refactoring aufzuwenden, ist in GameStatesCollection. Boom, ein göttliches Objekt.
Laurent Couvidou
5

Wir bringen eine mögliche Lösung aus der Enterprise-y-Welt mit: Haben Sie die Inversion der Steuerungs- / Abhängigkeitsinjektion überprüft?

Grundsätzlich erhalten Sie dieses Framework , das ein Container für alles andere in Ihrem Spiel ist. Es, und nur es, weiß, wie man alles miteinander verbindet und wie die Beziehungen zwischen Ihren Objekten aussehen. Keines Ihrer Objekte instanziiert irgendetwas in ihren eigenen Konstruktoren. Anstatt das zu schaffen, was sie brauchen, fragen sie nach dem, was sie vom IoC-Framework brauchen. Bei der Erstellung "weiß" das IoC-Framework, welches Objekt benötigt wird, um die Abhängigkeit zu erfüllen, und füllt es aus. Lose Kupplung FTW.

Wenn Ihre EnemyTreasure-Klasse den Container nach einem GameLevel-Objekt fragt, weiß das Framework, welche Instanz es geben soll. Woher weiß es das? Vielleicht hat es es früher instanziiert, vielleicht fragt es einige GameLevelFactory in der Nähe. Der Punkt ist, dass EnemyTreasure nicht weiß, woher die GameLevel-Instanz stammt. Es weiß nur, dass seine Abhängigkeit jetzt erfüllt ist und es mit seinem Leben weitermachen kann.

Oh, ich sehe, dass Ihre PlayerSprite-Klasse IUpdateable implementiert. Das ist großartig, denn das Framework abonniert automatisch jedes IUpdateable-Objekt für den Timer, in dem sich die Hauptspielschleife befindet. Jedes Timer :: tick () ruft automatisch alle IUpdatedable :: update () -Methoden auf. Einfach richtig?

Realistisch gesehen brauchen Sie nicht dieses großartige Framework, um dies für Sie zu tun: Sie können die wichtigen Dinge selbst erledigen. Der Punkt ist, dass, wenn Sie feststellen, dass Sie Objekte vom Typ -Manager erstellen, die Wahrscheinlichkeit besteht, dass Sie Ihre Verantwortlichkeiten nicht richtig auf Ihre Klassen verteilen oder dass Ihnen irgendwo Klassen fehlen.

drhayes
quelle
Interessant, ich habe zum ersten Mal von IoC gehört. Dies könnte zu groß für Spieleprogrammierung sein, aber ich werde das überprüfen!
Laurent Couvidou
IoC, haben wir es nicht nur als gesunden Menschenverstand bezeichnet?
Brice
Bei einer halben Chance wird ein Ingenieur aus irgendetwas einen Rahmen machen. (= Außerdem befürworte ich nicht die Verwendung des Frameworks, ich befürworte das Architekturmuster.
Drhayes
3

In der Vergangenheit habe ich in der Regel eine Gottesklasse, wenn ich Folgendes mache:

  • Setzen Sie sich in den Spiele-Editor / die IDE / den Editor, bevor ich ein Klassendiagramm (oder nur eine Skizze der Hauptobjekte) geschrieben habe.
  • Beginnen Sie mit einer sehr vagen Idee des Spiels und codieren Sie sie sofort. Beginnen Sie dann mit der Wiederholung neuer Ideen, sobald sie auftauchen
  • Erstelle eine Klassendatei namens GameManager, wenn ich kein zustandsbasiertes Spiel wie eine rundenbasierte Strategie mache, bei der GameManager tatsächlich ein Domänenobjekt ist
  • Die Code-Organisation ist mir egal

Das mag umstritten sein, und ich werde lachen, wenn es so ist, aber ich kann mir kein einziges Mal vorstellen, dass Sie nicht ein einfaches Klassendiagramm schreiben sollten, bevor Sie einen spielbaren Prototyp schreiben. Ich neige dazu, technische Ideen vor der Planung zu testen, aber das war es auch schon. Wenn ich also ein Portal erstellen wollte, würde ich einen sehr einfachen Code schreiben, um die Portale zum Laufen zu bringen. Dann würde ich sagen, dass gute Zeit für eine kleine Planung ist. Dann könnte ich an dem spielbaren Prototyp arbeiten.

Brandon
quelle
1

Ich weiß, dass diese Frage jetzt alt ist, aber ich dachte, ich würde meine 2 Cent hinzufügen.

Beim Entwerfen von Spielen habe ich fast immer ein Spielobjekt. Es ist jedoch sehr hoch und macht selbst nichts.

Beispielsweise weist es die Graphics-Klasse an, zu rendern, hat jedoch nichts mit dem Rendervorgang zu tun. Der LevelManager wird möglicherweise aufgefordert, ein neues Level zu laden, hat jedoch nichts mit dem Ladevorgang zu tun.

Kurz gesagt, ich benutze ein halbgottähnliches Objekt, um die Verwendung der anderen Klassen zu orchestrieren.

OMGtechy
quelle
2
Das ist überhaupt nicht sehr gottähnlich - das nennt man Abstraktion. :)
Vaughan Hilts