XNA - Statische Klassen aus Spielbibliotheken, die nach Erweiterungen der Inhaltspipeline ausgeführt werden. Wie kann das Problem vermieden werden?

8

Okay, so formuliert, ich bin sicher, es klingt dunkel, aber ich werde mein Bestes tun, um mein Problem zu beschreiben.

Lassen Sie mich zunächst erklären, was mit meinem echten Quellcode nicht stimmt.

Ich mache einen Plattformer, und hier sind die aktuellen Komponenten meines Spiels: ein Level , das ein eigenes Kachelraster enthält, sowie die Kachelblätter und eine Kachel , die nur den Namen des Kachelblatts und des Index innerhalb des Feldes enthält Fliesenblatt. All dies befindet sich in einem separaten XNA-Spielprojekt namens GameLib.

So werden meine Ebenen in einer Textdatei formatiert:

x x . . . . .

. . . b g r .

. . . . . . .

X X X X X X X

Wo .eine leere Kachel darstellt, beine blaue Plattform darstellt, Xeinen Block mit einer zufälligen Farbe darstellt usw.

Anstatt die gesamte Arbeit des Ladens eines Levels innerhalb der Level- Klasse zu erledigen, habe ich beschlossen, die Content-Pipeline-Erweiterung zu nutzen und eine LevelContentPipelineExtension zu erstellen.

Als ich anfing, wurde mir schnell klar, dass ich nicht mehr als 20 Zeilen fest codieren wollte:

if (symbol == 'b')
    return new Tile(/*...*/);

In der Hoffnung, zumindest die gesamte Inhaltserstellung in einer Klasse zu zentralisieren, habe ich eine statische Klasse namens LevelContentCreation erstellt , die Informationen darüber enthält, welches Symbol was und all diese Dinge erstellt. Es enthält auch eine Liste aller Asset-Pfade der Kachelblätter, um sie später zu laden. Diese Klasse befindet sich in meinem GameLib-Spielbibliotheksprojekt.

Das Problem ist, dass ich diese statische LevelContentCreation- Klasse sowohl in meiner Level-Klasse (um die eigentlichen Kachelblätter zu laden) als auch in meiner Inhaltspipeline-Erweiterung (um zu wissen, welches Symbol welche Kachel darstellt) verwende ...

Und so sieht meine LevelContentCreation- Klasse aus:

public static class LevelContentCreation
{
    private static Dictionary<char, TileCreationInfo> AvailableTiles =
        CreateAvailableTiles();

    private static List<string> AvailableTileSheets { get; set; }

    /* ... rest of the class */
}

Jetzt, da die LevelContentCreation Klasse Teil des Spiel Bibliothek ist, die Content - Pipeline - Erweiterung versucht , es zu benutzen , aber der Anruf zu CreateAvailableTiles () nie passiert , und ich bin nicht in der Lage eine Stufe Objekt aus der Content - Pipeline zu laden.

Ich weiß, dass dies daran liegt, dass die Inhaltspipeline vor der eigentlichen Kompilierung oder ähnlichem ausgeführt wird. Aber wie kann ich das Problem beheben?

Ich kann die LevelContentCreation- Klasse nicht wirklich in die Pipeline-Erweiterung verschieben, da die Level- Klasse sie dann nicht verwenden kann. Ich bin ziemlich verloren und weiß nicht, was ich tun soll. Ich kenne diese sind ziemlich schlechte Designentscheidungen, und ich würde es gerne sehen, wenn mich jemand in die richtige Richtung weisen könnte.

Vielen Dank für Ihre wertvolle Zeit.

Wenn etwas nicht klar genug ist oder ich mehr Informationen / Code bereitstellen sollte, hinterlassen Sie bitte unten einen Kommentar.


Ein bisschen mehr Informationen zu meinen Projekten:

  • Spiel - XNA Windows-Spielprojekt. Enthält Verweise auf: Engine, GameLib und GameContent
  • GameLib - XNA- Spielbibliotheksprojekt . Enthält Verweise auf: Motor
  • GameContent - XNA-Inhaltsprojekt. Enthält Verweise auf: LevelContentPipelineExtension
  • Engine - XNA-Spielbibliotheksprojekt. Keine Referenzen.
  • LevelContentPipelineExtension - Erweiterung der XNA-Inhaltspipeline. Enthält Verweise auf: Engine und GameLib

Ich weiß so gut wie nichts über XNA 4.0 und bin ein bisschen verloren darüber, wie Inhalte jetzt funktionieren. Wenn Sie weitere Informationen benötigen, sagen Sie es mir.

Jesse Emond
quelle
Ich kann nicht genau sagen, was - aber ich denke, Ihnen fehlen einige Informationen. Können Sie genau erklären, welche Projekte Sie haben (Spiel, Spielbibliothek, Erweiterung der Inhaltspipeline, Inhaltsprojekt) und welche Referenzen auf was?
Andrew Russell
Klar, ich wusste, dass es nicht klar genug sein würde. Bearbeiten Sie jetzt.
Jesse Emond
Übrigens scheint alles in Ordnung zu sein, außer dass zur Kompilierungszeit innerhalb der Pipeline-Erweiterung eine Ausnahme ausgelöst wird. Die Ausnahme sagt mir tatsächlich, dass der Inhalt der Level-Datei nicht konvertiert werden kann, da die verfügbaren Symbole noch nicht in meine LevelContentCreation-Datei geladen sind. Ich habe sogar getestet, indem ich eine Ausnahme sowohl in die Pipeline-Erweiterung als auch in die statische Klasse eingefügt habe. Die Ausnahme der Pipeline-Erweiterung wurde zuerst ausgelöst, sodass das Problem möglicherweise tatsächlich von dort kommt und ich meinen Code neu strukturieren muss.
Jesse Emond
Zum späteren Nachschlagen: Hier ist eine Methode zum Debuggen von Inhaltspipeline-Erweiterungen .
Andrew Russell

Antworten:

11

In Fällen, in denen Sie Singletons, statische Klassen oder globale Variablen verwenden, sollten Sie in 99% der Fälle einen Spieldienst verwenden . Spieledienste werden verwendet, wenn eine Instanz einer Klasse allen anderen Klassen zur Verfügung stehen soll, aber die enge Kopplung der anderen Optionen gibt Ihnen einen lustigen Geschmack. Services haben auch nicht die Instanziierungsprobleme, die Sie gesehen haben, sie sind wiederverwendbarer und können verspottet werden (wenn Sie sich für automatisierte Unit-Tests interessieren) .

Um einen Dienst zu registrieren, müssen Sie Ihrer Klasse eine eigene Schnittstelle geben. Dann ruf einfach an Game.Services.AddService(). Rufen Sie an, um diesen Dienst später in einer anderen Klasse zu verwenden Game.Services.GetService().

In Ihrem Fall würde Ihre Klasse ungefähr so ​​aussehen:

public interface ILevelContentCreation
{
    void SomeMethod();
    int SomeProperty { get; }
}

public class LevelContentCreation : ILevelContentCreation
{
    private Dictionary<char, TileCreationInfo> availableTiles =
        CreateAvailableTiles();

    private List<string> AvailableTileSheets { get; set; }

    public void SomeMethod() { /*...*/ }
    public int SomeProperty { get; private set; }

    /* ... rest of the class ... */
}

Irgendwann zu Beginn des Programms müssten Sie Ihren Dienst bei registrieren Game.Services. Ich bevorzuge dies zu Beginn LoadContent(), aber einige Leute bevorzugen es, jeden Dienst im Konstruktor dieser Klasse zu registrieren.

/* Main game */
protected override void LoadContent()
{
    RegisterServices();

    /* ... */
}

private void RegisterServices()
{
    Game.Services.AddService(typeof(ILevelContentCreation), new LevelContentCreation());
}

Wenn Sie jetzt LevelContentCreationin einer anderen Klasse auf Ihre Klasse zugreifen möchten, tun Sie dies einfach:

ILevelContentCreation levelContentCreation = (ILevelContentCreation) Game.Services.GetService(typeof(ILevelContentCreation));
levelContentCreation.SomeMethod(); //Tada!

Nebenbei bemerkt, dieses Paradigma ist als Inversion of Control (IoC) bekannt und wird auch außerhalb der XNA-Entwicklung häufig verwendet . Das beliebteste Framework für IoC in .Net ist Ninject , das eine bessere Syntax als die Game.Services-Methoden aufweist:

Bind<ILevelContentCreation>().To<LevelContentCreation>(); //Ninject equivalent of Services.AddService()
ILevelContentCreation levelContentCreation = kernel.Get<ILevelContentCreation>(); //Ninject equivalent of Services.GetService()

Es unterstützt auch die Abhängigkeitsinjektion , eine Form von IoC, die etwas komplexer ist, aber viel besser zu bearbeiten ist.

[Bearbeiten] Natürlich können Sie diese nette Syntax auch mit XNA erhalten:

static class ServiceExtensionMethods
{
    public static void AddService<T>(this GameServiceContainer services, T service)
    {
        services.AddService(typeof(T), service);
    }

    public static T GetService<T>(this GameServiceContainer services)
    {
        return (T)services.GetService(typeof(T));
    }
}
BlueRaja - Danny Pflughoeft
quelle
1
Wow, obwohl ich mein Problem behoben habe, wird mein Code dadurch so viel sauberer. Vielen Dank für die Hilfe und die Annahme Ihrer Antwort.
Jesse Emond
1

Ich habe tatsächlich einen Weg gefunden, das Problem zu beheben: Ich habe die statische Klasse entfernt und sie zu einer normalen Klasse gemacht. Auf diese Weise bin ich sicher, dass die richtigen Anweisungen zur richtigen Zeit ausgeführt werden.

Ich wünschte, ich hätte deine Zeit nicht so verschwendet, aber ich kam tatsächlich auf die Idee, dies zu tun, indem ich die Kommentare / Antworten las.

Trotzdem danke.

Jesse Emond
quelle
Ein weiterer Hinweis, der nichts mit meiner Antwort oben / unten zu tun hat: Wenn Sie if/ switchAnweisungen finden, die einfach verschiedene Typen zurückgeben, möchten Sie wahrscheinlich, dass diese Werte stattdessen Teil der Klasse sind. In Ihrem Fall würde ich eine TileTypeKlasse erstellen , die Informationen (Textur, Dateibrief usw.) zu jedem Kacheltyp enthält. Um einen TileTypeBrief zu erhalten, durchsuchen Sie einfach Ihre Liste TileTypesoder speichern Sie eine statische Aufladung Dictionary<char, TileType>in der TileTypeKlasse , wenn Sie Bedenken hinsichtlich der Leistung haben (Sie können sie automatisch im TileTypeKonstruktor ausfüllen ).
BlueRaja - Danny Pflughoeft
Dann würde jede Tile(Klasse, die eine einzelne Kachel darstellt, die auf dem Bildschirm angezeigt wird) auf eine einzelne verweisen TileType. Wenn genügend Kacheln ein eindeutiges Verhalten aufweisen, für das spezieller Code erforderlich ist, sollten Sie stattdessen eine Klassenhierarchie verwenden.
BlueRaja - Danny Pflughoeft
0

Schauen Sie sich das an: http://msdn.microsoft.com/en-us/library/bb447745.aspx

Für das Level-Layout habe ich einfach eine Funktion in meine Level-Klasse eingefügt, die "LoadFromFile (String-Name)" heißt. Wenn Sie später Unterstützung für benutzerdefinierte Karten in Ihrem Spiel hinzufügen möchten, müssen Sie die Ebenen ohnehin aus Ihrem eigenen Dateiformat speichern und laden.

Sie können auch weiterhin einen Inhaltsimporter erstellen, um ihn aus den enthaltenen Assets und aus vom Benutzer bereitgestellten Dateien zu laden. Auf dieser Seite finden Sie weitere Informationen zum Erstellen eines Importers

http://msdn.microsoft.com/en-us/library/bb447743.aspx

Riki
quelle
Danke für deine Antwort. Mein Problem ergibt sich nicht aus der Erstellung der Inhaltspipeline-Erweiterung, sondern aus der Tatsache, dass meine Level-Erstellungsklasse sowohl von meiner Level-Klasse als auch von meiner Content-Pipeline-Erweiterung verwendet wird, sodass der Pipeline-Code vor den statischen Klassendeklarationen und ausgeführt wird dort wird nichts initialisiert.
Jesse Emond