Ich habe gerade eine Antwort auf eine Frage zur Strukturierung des Spielcodes gelesen . Ich wunderte mich über die allgegenwärtige GameManager
Klasse 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 GameManager
ein 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 GameManager
ohnehin 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 GameManager
ist es keine so gute Idee , eine Klasse zu schaffen . Aber das gleiche kann zu einem sauberen passieren GameApplication
oder GameStateMachine
oder GameSystem
dass 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.
quelle
GameManager
oben beschriebenen Effekt auszusetzen ?Antworten:
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.
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 .
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":
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.
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.
quelle
GameManager
ich 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. IchGameManager
frage nach einer Möglichkeit, diesen Effekt zu begrenzen .Dies ist kein wirkliches Problem bei der Spielprogrammierung, sondern bei der Programmierung im Allgemeinen.
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?
quelle
SceneManager
oder aNetworkManager
? 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 eineGameManager
, etwas weniger Code-bloat-anfällige geht.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 inGameStatesCollection
. Boom, ein göttliches Objekt.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.
quelle
In der Vergangenheit habe ich in der Regel eine Gottesklasse, wenn ich Folgendes mache:
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.
quelle
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.
quelle