Ich möchte mein Make-Everything-Static-and-Global-Design-Pattern loswerden, aber wie?

11

Ich mache einen kleinen Dungeon-Crawler im Weltraum und würde gerne einen Rat hören, wie man das Backend der Engine schöner macht. Grundsätzlich basiert derzeit alles auf einer Menge Manager:

  • BackgroundManager: Verfügt über eine AddBackground(image, parallax)Methode zum Erstellen cooler Hintergrundeffekte.
  • ConfigManager: Liest / erstellt die Konfigurationsdatei und enthält auch die aus dieser Konfigurationsdatei gelesenen Daten.
  • DrawManager: hat Draw(sprite)Methode Zeug auf den Bildschirm zu zeichnen, und solche Sachen SetView(view), GetView()usw.
  • EntityManager: Enthält alle aktiven Entitäten und verfügt über Methoden zum Hinzufügen, Entfernen und Suchen von Entitäten.
  • DungeonManager (eigentlich ein GridManager genannt, aber das ist der Einfachheit halber): hat Methoden wie GenerateDungeon(), PlaceEnemies(), PlaceFinish()usw.

Jetzt bin ich noch nicht einmal auf halber Strecke und liste alle meine Manager auf. Ich denke, das muss sich ändern. Mein Spiel basiert auch auf Bildschirmen, was es ärgerlicher macht, weil ich nicht die Hälfte der Dinge benötige, die meine Manager verwalten, zum Beispiel das Hauptmenü (ich benötige keine Physik / Entities / Dungeons in meinem Hauptmenü) !)

Jetzt dachte ich darüber nach, die Manager nicht statisch zu machen und meinem ScreenMainGame eine Instanz aller spielspezifischen Manager zu geben. Aber das macht das Anrufen / Erhalten von Informationen zu den Managern zu einem großen Chaos ... Wenn ich zum Beispiel meinen Kerker von einem Ort ziehen wollte, an dem er nicht ist ScreenMainGame.Draw(), müsste ich das tun ...

((ScreenMainGame)ScreenManager.CurrentScreen).GridManager.Draw()

Das ist wirklich hässlich.

Also, liebe Spieleentwickler, hat jemand von euch eine Idee, wie man dieses Durcheinander löst? Ich wäre dankbar!

Dlaor
quelle
Ich weiß nicht genau, wie ich Ihnen helfen soll, aber ich würde empfehlen, ein gutes Buch über Designmuster zu lesen. Ich habe Head First Design Patterns gelesen und es ist sehr einfach zu verstehen. Die Beispiele sind in Java, aber ich denke, das wäre nah genug an C #, um Ihnen zu helfen. Entschuldigung, wenn mein Vorschlag zu unerfahren ist.
Amplify91
Womit verbinden Sie sich mit der Grafikkarte? XNA? SlimDX?
BlueRaja - Danny Pflughoeft
@ BlueRaja: Ich benutze SFML.NET.
Dlaor
In diesem Fall sollten Sie einfach die normalen Methoden zum Behandeln dieser Probleme in C # verwenden: Siehe meinen letzten Link unten sowie Was ist Abhängigkeitsinjektion?
BlueRaja - Danny Pflughoeft

Antworten:

10

Was ist mit einem komponentenbasierten Motor ?

Sie hätten eine Hauptklasse mit dem Namen Engine, die eine Liste von enthält GameScreens, die selbst eine Liste von enthält Components.

Der Motor verfügt über ein Updateund ein DrawVerfahren und rufen beide die GameScreen‚s Updateund DrawMethoden, die sich jede Komponente und Anruf durchlaufen Updateund Draw.

So präsentiert, stimme ich zu, dass es sich nach einem schlechten und sich wiederholenden Design anhört. Aber glauben Sie mir, mein Code wurde durch die Verwendung eines komponentenbasierten Ansatzes viel sauberer als bei all meinen alten Manager- Klassen.

Es ist viel einfacher, solchen Code zu pflegen, da Sie nur eine große Klassenhierarchie durchlaufen und nicht nach BackgroundManagerallen spezifischen Hintergründen suchen müssen . Sie haben nur ein ScrollingBackground, ParallaxBackground, StaticBackgroundetc. , die alle aus einer derive BackgroundKlasse.

Sie werden schließlich eine ziemlich solide Engine aufbauen, die Sie in all Ihren Projekten mit einer Vielzahl häufig verwendeter Komponenten und Hilfsmethoden wiederverwenden können (z. B. FrameRateDisplayerals Debugging-Dienstprogramm, eine SpriteKlasse als grundlegendes Sprite mit Textur- und Erweiterungsmethoden für Vektoren) und Zufallszahlengenerierung).

Sie würden keine BackgroundManagerKlasse mehr haben , sondern eine BackgroundKlasse, die sich selbst verwalten würde.

Wenn dein Spiel beginnt, musst du nur Folgendes tun:

// when declaring variables:
Engine engine;

// when initializing:
engine = new Engine();
engine.Initialize();
engine.LoadContent();
engine.AddGameScreen(new MainMenuScreen());

// when updating:
engine.Update();

// when drawing:
engine.Draw();

Und das wars für deinen Spielstartcode.

Dann für den Hauptmenübildschirm:

class MainMenuScreen : MenuScreen // where MenuScreen derives from the GameScreen class
{
    const int ENEMY_COUNT = 10;

    StaticBackground background;
    Player player;
    List<Enemy> enemies;

    public override void Initialize()
    {
        background = new StaticBackground();
        player = new Player();
        enemies = new List<Enemy>();

        base.AddComponent(background); // defined within the GameScreen class
        base.AddComponent(player);
        for (int i = 0; i < ENEMY_COUNT; ++i)
        {
            Enemy newEnemy = new Enemy();
            enemies.Add(newEnemy);
            base.AddComponent(newEnemy);
        }
    }
}

Sie bekommen die allgemeine Vorstellung.

Sie würden auch die Referenz der Enginein all Ihren GameScreenKlassen behalten , um auch innerhalb einer GameScreenKlasse neue Bildschirme hinzufügen zu können (z. B. wenn der Benutzer auf die Schaltfläche StartGame klickt , während Sie sich in Ihrer befinden MainMenuScreen, können Sie zum wechselnGameplayScreen ).

Dasselbe gilt für die ComponentKlasse: Sie sollte die Referenz ihres übergeordneten Elements enthalten GameScreen, um sowohl Zugriff auf die EngineKlasse als auch auf das übergeordnete Element GameScreenzu haben, um neue Komponenten hinzuzufügen (z. B. können Sie eine HUD-bezogene Klasse erstellen DrawableButton, die eine DrawableTextKomponente und eine StaticBackgroundKomponente enthält).

Anschließend können Sie sogar andere Entwurfsmuster anwenden, z. B. das "Dienstentwurfsmuster" (bei dem der genaue Name nicht bekannt ist), in dem Sie verschiedene nützliche Dienste in Ihrer EngineKlasse aufbewahren können (Sie führen einfach eine Liste mit IServices und lassen andere Klassen Dienste selbst hinzufügen ). ZB würde ich eine Camera2DKomponente über mein gesamtes Projekt als Dienst behalten , um ihre Transformation beim Zeichnen anderer Komponenten anzuwenden. Dies vermeidet, dass es überall als Parameter übergeben werden muss.

Zusammenfassend kann gesagt werden, dass es sicherlich andere bessere Designs für einen Motor gibt, aber ich fand den von diesem Link vorgeschlagenen Motor sehr elegant, äußerst wartungsfreundlich und wiederverwendbar. Ich persönlich würde es zumindest empfehlen, es zu versuchen.

Jesse Emond
quelle
1
XNA unterstützt alle diese sofort einsatzbereiten Funktionen GameComponentund Dienste. Keine Notwendigkeit für eine separate EngineKlasse.
BlueRaja - Danny Pflughoeft
+1 für diese Antwort. Wäre es auch sinnvoll, die Zeichnung in einem anderen Thread als dem Spielaktualisierungsthread zu platzieren?
f20k
1
@BlueRaja - DannyPflughoeft: In der Tat, aber ich nahm an, dass XNA nicht verwendet wurde, und erklärte ein bisschen mehr darüber, wie es geht.
Jesse Emond
@f20k: Ja, ziemlich genau so, wie es XNA macht. Ich weiß jedoch nicht, wie es implementiert wird. = / Es muss irgendwo im Internet einen Artikel darüber geben, wie es geht. :)
Jesse Emond
Ja, ich verwende kein XNA, aber dies scheint eine gute Alternative zu dem zu sein, was ich tue. Ich lese gerade das Buch "Head First Design Patterns", um zu prüfen, ob es noch weitere Alternativen gibt. Prost!
Dlaor
1

Sie möchten Ihren Code in Komponenten und Services unterteilen. XNA hat bereits eine integrierte Unterstützung für diese.

Siehe diesen Artikel auf GameComponentsund Dienstleistungen. Lesen Sie dann diese Antwort zur Verwendung von Diensten in XNA und diese allgemeinere Antwort zu Diensten in einer beliebigen Sprache.

BlueRaja - Danny Pflughoeft
quelle
-3

Nur weil sie statisch oder global sind, müssen sie nicht immer geladen / initialisiert werden. Verwenden Sie ein Singleton-Muster, und ermöglichen Sie den Managern, bei Bedarf freigegeben und erneut geladen zu werden.

Bo Buchanan
quelle
4
Ein Singleton ist nur eine Fassade für das zugrunde liegende Problem, das das OP hier zu lösen versucht.