User Tools

Site Tools


z:code:gamemanager

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
z:code:gamemanager [2008/05/25 14:55]
cabalistic
z:code:gamemanager [2015/08/23 13:59] (current)
Line 19: Line 19:
 ===== Main Loop, Game States und GUI-Verwaltung ===== ===== Main Loop, Game States und GUI-Verwaltung =====
 Nun kommen wir auch schon gleich zu einem ersten mehr oder minder großen Problem der aktuellen Codestruktur und damit also einem der ersten Zwischenschritte fürs Refactoring. Nachdem unser Spiel sich jetzt initialisiert hat und für den Spieler der Ladebildschirm gerade fertiggeworden ist, geht's in die Main Loop des Spiels (die Funktion run). Die hält zunächst die verstrichene Zeit in Sekunden im Auge und führt ein paar grundsätzlich nötige Updates pro Schleifendurchgang durch, im Moment ist das das Update von Input- und SoundManager, die regelmäßig ihren Zustand aktualisieren müssen. Zum Ende der Schleife wird ein neues Bild gerendert und auf Nachrichten vom Betriebssystem bzw. Window Manager geprüft. Das ist so weit, so gut, das Problem liegt in der Phase dazwischen. Nun kommen wir auch schon gleich zu einem ersten mehr oder minder großen Problem der aktuellen Codestruktur und damit also einem der ersten Zwischenschritte fürs Refactoring. Nachdem unser Spiel sich jetzt initialisiert hat und für den Spieler der Ladebildschirm gerade fertiggeworden ist, geht's in die Main Loop des Spiels (die Funktion run). Die hält zunächst die verstrichene Zeit in Sekunden im Auge und führt ein paar grundsätzlich nötige Updates pro Schleifendurchgang durch, im Moment ist das das Update von Input- und SoundManager, die regelmäßig ihren Zustand aktualisieren müssen. Zum Ende der Schleife wird ein neues Bild gerendert und auf Nachrichten vom Betriebssystem bzw. Window Manager geprüft. Das ist so weit, so gut, das Problem liegt in der Phase dazwischen.
 +Es gibt prinzipiell zwei Grundzustände, in denen sich das Spiel befinden kann; entweder es ist im Menu oder es läuft ein Szenario. Die Menu-Phase kann man dann noch weiter unterteilen in Hauptmenu, Konfigurationsdialog, Mapauswahl, Multiplayerplattform, ... Das Problem ist, dass der GameManager im Moment diese Zustände selbst verwaltet, man sieht in der Main Loop eine switch-Anweisung, die im Moment zwar nicht viel tut, aber eigentlich überhaupt nicht da sein dürfte. Das Problem ist, dass es auf diese Art extrem mühsam, umständlich und fehleranfällig wird, das Ganze zu erweitern. Das eigentlich korrekte Vorgehen wäre, diese Verwaltung in separate GameState-Klassen auszulagern. Es gäbe dann eine Basisklasse GameState, von der GameManager eine Instanz besäße. Dies hat dann eine Funktion process, die GameManager in der Main Loop aufruft, process liefert dabei einen Pointer auf GameState* zurück. Ist der Pointer 0, ändert sich nichts, ist er aber nicht 0, dann löscht GameManager seinen aktuellen GameState und benutzt den neu zurückgegebenen. Dadurch können die GameStates selbständig untereinander wechseln, ohne dass der GameManager etwas davon mitbekommen müsste. Man kann dann auch einfach neue GameStates schreiben, falls nötig. Die GameStates würden dann auch Funktionen von GameManager übernehmen wie z. B. aktuell startScenario, es gäbe also z. B. einen GameState ScenarioGameState, der entsprechende Initialisierungs- und Shutdownfunktionen hat und der ein Scenario verwaltet (was folglich dann der GameManager nicht mehr tun würde und effektiv also um diese Aufgabe entlastet wird).
 +Gleichermaßen würden diese GameStates Kontrolle über die GUI bekommen; wenn ihr etwas in den Source Files browst, wird euch auffallen, dass es eine Reihe von SMGUIxxx-Dateien gibt. Ich hatte hier ursprünglich versucht, die GUI-Verwaltung ein bisschen wie Desktop-GUIs zu machen, bei der man meistens für einen Dialog eine separate Klasse hat, aber das funktioniert hier so nicht. Wie man sieht, haben viele dieser Dateien nämlich fast keinen Code und sind im Großen und Ganzen überflüssig, weil sie nur ein oder zwei Callbacks für einen Button-Druck enthalten. Die Idee wäre nun, alle diese Dateien zu entfernen und ihre Aufgaben in die passenden GameStates einzubetten. 
 +
 +Wenn ihr eure Arbeit am Code beginnen wollt, würde ich sagen, dass das ein guter Startpunkt wäre. Diese Restrukturierung von GameManager ist dringend nötig und zwingt euch dazu, etwas im Code querzulesen, ohne aber alle anderen Komponenten im Detail verstehen zu müssen. Ich würde dabei dann auch begleitend mit drüberschauen, bin ja meistens im IRC erreichbar. Evtl. wäre es auch günstig, im "Practical Application"-Tutorial im Ogre-Wiki nachzulesen, da gibt es afair ein Kapitel über Game States.
 +
 +===== Accessor-Funktionen =====
 +Kommen wir nun zum zweiten Problem, den Accessor-Funktionen. Die Idee ist prinzipiell folgende, GameManager verwaltet einige Objekte, auf die anderer Code zugreifen können muss. Nehmen wir als Beispiel das Scenario (auch wenn das, wie oben erwähnt, künftig von einem GameState verwaltet werden sollte). Das Scenario ist das Kernstück eines laufenden Spiels, es verwaltet Einheiten, Terrain etc., kurz eigentlich den allgemeinen Spielablauf. Da andere Einheiten z. B. gelegentlich auf andere Einheiten zugreifen müssen, brauchen sie Zugriff auf das Scenario. Deswegen bietet GameManager momentan eine Accessor-Funktion für Scenario. Natürlich könnte man alternativ allen Objekten, die Zugriff auf das Scenario brauchen, einen Pointer zum Scenario bei der Konstruktion übergeben, aber das ist repetitiv und äußerst mühsam, also ist ein Accessor schon keine schlechte Wahl.
 +Das Problem ist aber, dass es einerseits Unter-Accessoren gibt. Scenario z. B. wiederum besitzt ein Terrain-Objekt, welches das Terrain des Szenarios beschreibt, und mancher Code benötigt Informationen über das Terrain. Nun ist ein Aufruf von GameManager::getSingleton().getScenario()->getTerrain() nicht gerade der Inbegriff von Eleganz, wie man sich vorstellen kann. Das größere Problem resultiert allerdings aus der Abhängigkeit. Um diesen Aufruf durchführen zu können, muss die Datei, in der der Aufruf steht, sowohl "SMGameManager.h" als auch "SMScenario.h" einbinden, obwohl sie von diesen eigentlich gar nichts braucht und nur vom Terrain abhängt. Dadurch wird die Datei aber von diesen Include-Dateien mitabhängig, und die Folge ist, dass wann immer man eine noch so kleine Änderung in "SMGameManager.h" vornimmt, wird fast das ganze Projekt neukompiliert.
 +Die Lösung ist zum Glück nicht so kompliziert und besteht einfach darin, Klassen wie Scenario auch als Ogre-Singletons zu implementieren. Dadurch werden sie nach wie vor von ihrem Besitzer erzeugt und gelöscht, aber sie erhalten automatisch einen Accessor, der in ihrer eigenen Include-Datei sitzt, z. B. also Terrain::getSingleton(). Damit wird der Aufruf etwas entschlackt und vor allem die Abhängigkeit vom GameManager entfernt. 
 +Manche Accessor-Funktionen kann man allerdings so nicht ersetzen. getSceneManager von GameManager muss man z. B. einfach hinnehmen, allerdings wird der SceneManager auch nicht so oft gebraucht, insofern ist das kein echtes Problem.
z/code/gamemanager.1211720136.txt.gz · Last modified: 2015/08/23 14:03 (external edit)