ContentPipeline Assets in Resource-Dateien einbinden

Hin und wieder besteht die Anforderung, daß man Content (also Assets, die von der Content-Pipeline verarbeitet wurden) in ein Spiel oder eine Game-Library einbindet und zwar so, daß man nicht mehrere Dateien ausliefern muss. In diesem kurzen Tutorial möchte ich genau dies erklären. Wie fast schon üblich werden wir dabei vom XNA-Framework sehr gut unterstützt.

Beginnen wir einfach damit, daß wir Visual Studio 2010 starten und per Datei/Neu ein neues „Windows Game (4.0)“ erzeugen. Wir nehmen den Standard-Namen „WindowsGame1“, denn dieser ist für dieses Beispiel vollkommen in Ordnung.

In einem Zeichenprogramm erstellen wir uns eine einfache Textur. Nichts besonderes. Einfach etwas, daß wir später anzeigen können. Ich habe ein Rendering eines meiner 3D-Modelle dafür genommen, daß wie folgt aussieht.

Das Bild bindet ihr ganz einfach in das Content-Projekt ein (rechte Maustaste und Hinzufügen, Vorhandenes Element). Bei mir ist der Name des Bildes „texture.png“. Die Einstellungen nehmt ihr ganz normal im Content-Projekt vor, so wie immer.

Im Game-Projekt stellen wir diese Textur nun dar. Ganze einfach per Sprite-Batch. Dazu erstellen wir zunächst eine Member-Variable am Anfang der Klasse.

Texture2D texture;

In LoadContent laden wir die Textur.

this.texture = Content.Load<Texture2D>("texture");

Und schliesslich rendern wir diese nun in Draw.

spriteBatch.Begin();
spriteBatch.Draw(this.texture, Vector2.Zero, Color.White);
spriteBatch.End();

Soweit sollte alles bekannt sein, da es sich um die absoluten Basics handelt.

Wir wollen nun die Textur in die Game-Klasse einbinden und zwar so, daß wir die von der Content-Pipeline erzeugte XNB-Datei nicht mehr mit dem Spiel ausliefern möchten. Dazu müssen wir zunächst wissen, wo sich diese XNB-Dateien befinden.

Dazu gehen wir in unseren Projektmappenordner. Dieser seht ihr in den Eigenschaften des Projektes. Dort sollten wir zwei Ordner finden und zwar windowsGame1 und WindowsGame1Content. Wir wählen Ersteren und dort finden wir den Ordner bin. In diesem Ordner landet unser fertig kompiliertes Spiel. In unserem Fall, da wir nur ein einzelnes Projekt haben und keine Kopien für XBox und/oder Windows Phone, befindet sich darin nur der Ordner x86 und dieser enthält die Ordner Debug und Release. Es müssen nicht zwangsweise beide Ordner vorhanden sein, denn es hängt jeweils davon ab, ob wir in diesem Modus bereits kompiliert haben oder nicht. Bei mir ist momentan nur der Debug-Ordner vorhanden. Dieser enthält den Ordner Content. Dies ist der Default-Ordner der Content-Pipeline. Alles was sich im Content-Projekt befindet (also Ordner und Assets) landen exakt hier. Wenn wir diesen Ordner öffnen, dann sollten wir dort unsere Textur finden, allerdings mit der Endung XNB. Dies bedeutet, daß die Datei von der Content-Pipeline verarbeitet wurde und in ein Format überführt wurde, daß vom ContentManager geladen werden kann. Wir merken uns nun diese Stelle.

Zurück in Visual Studio klicken wir unser WindowsGame1-Projekt mit der rechten Maustaste an und fügen eine neue Resourcendatei hinzu. Dieser geben wir den Namen ContentAssets. Der Name ist dabei aber vollkommen egal. Ihr könnt hier einfach wählen, was ihr für richtig haltet. Die Datei wird auch sofort im Resource-Editor geöffnet. Dort wählt ihr in der Toolbar Resource hinzufügen/Vorhandene Datei hinzufügen und wählt die XNB-Datei aus, die wir eben lokalisiert haben. Nun müsst ihr die Resource-Datei noch speichern.

Wir öffnen nun die Game-Klasse und erzeugen eine neue Member-Variable.

ResourceContentManager resourceContentManager;

Diesen neuen Content-Manager müssen wir natürlich auch initialisieren, was wir in der Initialize-Methode machen werden. Dazu benötigen wir zwei Parameter. Als erstes eine Referenz auf den Service-Provider. Dieser ist in der Standard-Game-Klasse immer über die Property Services zu erreichen. Als nächsten Parameter benötigen wir eine Referenz auf einen ResourceManager. Dieser ist in unserer Resource-Datei vorhanden bzw. besser gesagt wird von Visual-Studio über die CodeBehind-Datei automatisch bereitgestellt. Die Initialisierung ist damit ein Kinderspiel:

resourceContentManager = new ResourceContentManager(this.Services, WindowsGame1.ContentAssets.ResourceManager);

Kommen wir nun zum spannenden Teil, dem Laden unserer Textur. Dazu kommentieren wir in der LoadContent-Methode die bisherige Zeile zum laden der Textur aus und schreiben eine neue Zeile in der wir den ResourceContentManager verwenden. Der Aufruf sieht exakt so aus wie bisher auch, da der ResourceContentManager eine Subklasse vom ContentManager ist. Die Funktionalität ist also die Gleiche mit dem Unterschied, daß aus Resource-Dateien geladen wird und nicht aus Dateien im Filesystem.

//this.texture = Content.Load<Texture2D>("texture");
this.texture = resourceContentManager.Load<Texture2D>("texture");

Sehr einfach also und wenn wir unser Beispielprogramm nun starten, sehen wir keinen Unterschied zu vorher.

An dieser Stelle sind wir eigentlich schon mit diesem kurzen Tutorial durch. Ich möchte hier aber noch einen Hinweis geben: Diese Methode hat einen Nachteil. Resource-Dateien werden fest in das Programm eingebunden. Das bedeutet, daß bei Programmstart die Assets geladen werden, weshalb dieser länger dauert. Auch belegen die Assets in den Resource-Dateien Arbeitsspeicher. Wenn wir die Assets laden, dann wird eine Kopie davon erzeugt und es wird nochmals Arbeitsspeicher belegt. Daher eignet sich diese Methode eher für kleinere Assets, wie z.B. Effektfiles oder ähnliches und weniger für riesige Texturen.

Zum Abschluss hier nochmal der gesamte Sourcecode der Game-Klasse, falls ich euch zwischendrin irgendwo abgehangen habe.

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

namespace WindowsGame1
{
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        ResourceContentManager resourceContentManager;

        Texture2D texture;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";
        }

        protected override void Initialize()
        {
            resourceContentManager = new ResourceContentManager(this.Services, WindowsGame1.ContentAssets.ResourceManager);

            base.Initialize();
        }

        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            //this.texture = Content.Load<Texture2D>("texture");
            this.texture = resourceContentManager.Load<Texture2D>("texture");
        }

        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
        }

        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed ||
                Keyboard.GetState().IsKeyDown(Keys.Escape))
                this.Exit();

            // TODO: Add your update logic here

            base.Update(gameTime);
        }

        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue);

            spriteBatch.Begin();
            spriteBatch.Draw(this.texture, Vector2.Zero, Color.White);
            spriteBatch.End();

            base.Draw(gameTime);
        }
    }
}
Advertisements

Veröffentlicht am 11.09.2011 in Content-Pipeline, XNA, XNA 4.0 und mit , , , , getaggt. Setze ein Lesezeichen auf den Permalink. 11 Kommentare.

  1. internetfreak413

    Danke für deinen Beitrag, Glatzmann
    Vor ein paar Tagen beschäftigte mich genau dieses Problem, da ich gerne in einer Klassenbibliothek von mir 1-2 Dateien mitliefern will, die der User dann nur bei Bedarf ändern muss (es geht konkret um ne SpriteFont und evtl um ne kleine Textur [1×1 px])

    Hab den Link zum Beitrag übrigens geschnappt und in nem Artikel bei mir auf dem Blog reingepackt, du solltest also nen Trackback von internetfreak.net finden 😀

    • Schön, daß dir der Artikel geholfen hat. Das mit dem Trackback hat aber leider nicht funktioniert. Könnte daran liegen, daß der Link auf deiner Seite anscheinend nicht als Link erkannt wurde (also nicht klickbar ist).

      • internetfreak413

        So, hab den Link nun als Link drin, hatte es einfach per C&P eingefügt.
        Somit sollte nun der Trackback vorhanden sein, falls der nachträglich noch kommt.
        Wenn nicht, dann eben irgendwann ein mal, wenn du wieder nen hilfreichen Artikel postest

      • Jetzt hat’s geklappt, wunderbar… Danke…

  2. Sehr schöner Beitrag. Was mich mal interessiert, wäre ein Tutorial über eine frei bewegliche Kamera. Am besten als GameComponent. Vielleicht hast Du sowas schon demnächst angedacht in Deiner Serie über Terrains.

    • Danke sehr, freut mich natürlich, daß dir der Beitrag gefallen hat.

      Tatsächlich werde ich – vermutlich bereits im übernächsten Teil – eine Kamera vorstellen. Ob ich diese direkt in aller Ausführlichkeit behandeln werde oder über mehrere Teile ausbauen werde, kann ich jedoch noch nicht sagen. Aber definitiv wird die Kamera benötigt, sobald es ans Paging von einzelnen Terrain-Tiles geht und die Landschaft damit größer wird.

      • internetfreak413

        Erstmal noch wegen dem trackback, sry, dass mein Blog so klein ist, ja? (Bin derzeit etwas schreibfaul, was Tutorials angeht und Projektmäßig hab ich momentan nicht viel zu schreiben, da ich noch bissl im Verdeckten programmiere)

        Wenn du eine 3D-Kamera machst, könntest du auch ein kleines Tutorial zu einer 2D-Kamera machen (da ich hauptsächlich 2D-Games programmiere aufgrund meiner Unfähigkeit, 3D-Models zu machen und so [vorgefertigtes will ich nich für ernsthafte Spiele nutzen, Lizenz usw])
        Ich kenn zwar schon ne kleine Klasse, wo die Kamera mittels Matrix.CreateTranslation funktioniert, aber so ganz dahintergestiegen bin ich nicht. (Der Kameraartikel würde dir auch wieder nen Trackback einbringen, denk ich mal [Wenn was gut ist, werd ichs auch verteilen, so ists nicht^^])

      • Kein Problem, jeder Link zählt, oder? Danke sehr jedenfalls.

        Zur Kamera: Ich habe mir das mal auf meinem Schmierzettel notiert. Ich kann dir nichts versprechen, aber wenn ich gerade mal Zeit und Lust habe, werde ich sicherlich was zur 2D-Kamera schreiben.

  3. Ein Großartiger Blog und wieder ein mal ein guter Beitrag. Eine kleine Bitte jedoch: neue Rechtschreibung… (daß = dass)

    • Vielen Dank…

      Ich versuche mich zu bessern. Ich hatte schon versucht, mir das dass anzueignen, aber jahrzehntelange andere Übung macht das echt schwer 😉

  1. Pingback: XNA Content-Pipeline Dateien mit der Assembly ausliefern und laden | internetfreak's Corner

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit Deinem WordPress.com-Konto. Abmelden / Ändern )

Twitter-Bild

Du kommentierst mit Deinem Twitter-Konto. Abmelden / Ändern )

Facebook-Foto

Du kommentierst mit Deinem Facebook-Konto. Abmelden / Ändern )

Google+ Foto

Du kommentierst mit Deinem Google+-Konto. Abmelden / Ändern )

Verbinde mit %s

%d Bloggern gefällt das: