XNA - Grundlagen

  • Was wird benötigt?
    - Visual C# 2008
    - Installiertes XNA Game Studio
    - Grundkenntnisse bzw. Vorwissen in der Programmierung
    - Resourcen-Paket mit verwendeten Grafiken


    Inhalt:

    • Einleitung
    • Die Basisklassen
    • Das Anlegen des Projekts
    • Los gehts!

      • Aufräumen des generierten Codes
      • Der Content-Compiler
      • Unser erster Sprite
      • Text zeichnen


    • Shader

      • Was sind Shader?
      • Einstieg in Shader


    • Maus und Tastatur

      • Abfangen von Eingabegeräten
      • Unser Sprite lernt laufen



    1. Einleitung
    In diesem Tutorial werde ich euch, zumindest was Grundlagen anbetrifft, mit XNA vertraut machen und in die Abläufe bzw. Programmierung von Spielen einweisen. Für komplexere Spiele, wie z.B. MMORPG's oder auch komplette Rollenspiele wird hier eindeutig zu wenig in die Materie eingedrungen, ich veröffentliche eventuell aber noch weitere Tutorials zu Xna, in denen ihr in fortgeschrittenen Praktiken eingewiesen werdet.


    2. Die Basisklassen
    Das Rectangle
    Ein Rectangle, zu deutsch "Rechteck", definiert die Position und Größe eines zu zeichnenden Sprites bzw. einer Grafik. Die X- bzw. Y-Position (angegeben in Pixel) wird vom linken oberen Bildschirm- bzw. Fensterrand ausgehend gemessen und bezeichnet den Abstand dazu. Es besitzt folgende Eigenschaften:


    X Die X-Position des Sprites (in Pixel)
    Y DiY-Position des Sprites (in Pixel)
    Width Die Breite des Sprites (in Pixel)
    Height Die Höhe des Sprites (in Pixel)


    Zusätzlich findet ihr einige Methoden, wie z.B. Contains, die wir aber vorerst nicht verwenden werden.


    Vector2
    Ein Vector2 ist im Prinzip nichts anderes als ein Rectangle, nur das eben keine Größenangabe beinhaltet, sondern lediglich die Eigenschaften X und Y besitzt. Es bestimmt die Position von z.B. gerendertem Text, wieder gemessen vom oberen linken Fensterrand und angegeben in Pixel.


    Texture2D
    Die Texture2D Klasse beinhaltet eine Grafik und ist letztendlich nichts weiter als eine Bitmap. Sie beinhaltet ein Array aus Farb-Daten und einige Funktionen, unter anderem zum Speichern der Grafik und zum Laden der Grafik aus z.B. Png-Bildern. In Xna können lediglich Texture2D-Objekte gezeichnet werden, weshalb uns diese Klasse durch das gesamte Tutorial begleiten wird.


    Das SpriteBatch
    Das SpriteBatch ist die wichtigste Klasse in XNA überhaupt, zumindest was die 2D-Programmierung angeht. Mithilfe eines SpriteBatches können Grafiken und Texte auf die Form gerendert werden und das auf eine, wie ich finde, sehr komfortable Art und Weise. Die Methoden zum zeichnen sehen folgendermaßen aus:


    Begin() "Startet" das SpriteBatch
    Draw() Rendert eine Grafik auf die GameForm
    End() "Beendet" das SpriteBatch, bzw. rendert alle in einer "Liste" angesammelten Draw-Aufrufe auf die GameForm


    Das GraphicsDevice
    Diese Klasse ist genaugenommen noch wichtiger als das SpriteBatch, denn ohne sie würde XNA nichteinmal laufen. Die einzige Funktion die wir davon jedoch verwenden, ist die Clear()-Funktion, da uns aber das Game Studio die Instanzierung und Verwendung dieser Klasse abnimmt.


    3. Anlegen des Projekts
    Solltet ihr Visual C# 2008 und das XNA Game Studio erfolgreich installiert haben, können wir uns dem Erstellen des Projekts widmen. Über den Menü-Eintrag Datei > Neu > Projekt erscheint ein Dialog auf eurem Bildschirm. Wählt dort folgendes aus:


    [Blockierte Grafik: http://img838.imageshack.us/img838/4079/img01.png]


    Nach einer kurzen Zeit solltet ihr die Klasse Gam1.cs sehen, die (noch) ziemlich durcheinandergrwürfelten und mit Kommentaren zugekleisterten Code beinhaltet.


    4. Los gehts!
    Aufräumen des generierten Codes
    Um erstmal ein wenig Struktur in die ganze Geschichte zu bringen, entfernt ihr am besten alle Kommentare (in VS grün gekennzeichnete Passagen) und die damit zusammenhängenden Zeilenumbrüche. Euer Code sollte dann ungefähr so aussehen:



    Der Code sollte eigentlich selbsterklärend sein.


    Evtl. Unklare Stellen:


    Der Content-Compiler
    Eine Besonderheit an XNA, z.b. gegenüber DirectX, ist der Content-Compiler bzw. Content-Loader. Anders als in anderen Game-Engines kann man in Xna seine Grafiken, Soundeffekte, Xml-Dokumente und jeglichen anderen Kram, der unterstützt wird, durch den Content-Compiler jagen, welcher wiederum eine *.xnb-Datei daraus erstellt. Diese Xnb-Dateien können direkt in Xna mit einer einzigen Zeile Code geladen werden, ohne das Format weiter zu behandeln. Und genau dieses Feature wollen wir uns zu Nutzen machen.


    (Solltet ihr die resources.zip noch nicht entpackt haben, wäre es jetzt ein geeigneter Zeitpunkt dies zu tun :wink:)


    Um eine Grafik dem Content-Compiler hinzuzufügen, wählen wir im Projektmappen-Explorer "Content" aus und klicken mit der rechten Maustaste darauf. Über "Hinzufügen" > "Vorhandenes Element" lassen sich Grafiken auswählen.


    [Blockierte Grafik: http://img688.imageshack.us/img688/7287/img02.png]


    Nun wählen wir in dem sich öffnenden FileDialog die 3 Sprites der Pokemon aus dem resources.zip Archiv und fügen diese per Klick auf "Hinzufügen" zu unserer Projektmappe hinzu.


    [Blockierte Grafik: http://img828.imageshack.us/img828/2051/img03.png]


    Wenn ihr diese Schritt hinter euch gebracht habt, steht der Programmierung nichts mehr im Weg. Das Laden der Grafiken funktioniert über einen Content-Manager, der bereits vorinstanziert ist, wenn ihr ein neues Xna-Projekt erstellt habt.


    Zu allererst erstellen wir 3 Variablen, die die einzelnen Sprites von Turtok, Glurak und Bisaflor beinhalten:


    Code
    1. Texture2D blastoise; //Turtok Sprite
    2. Texture2D charizard; //Glurak Sprite
    3. Texture2D venusaur; //Bisaflor Sprite


    Nun erweitern wir unseren LoadContent() Befehl um folgende 3 Zeilen:


    Code
    1. protected override void LoadContent()
    2. {
    3. spriteBatch = new SpriteBatch(GraphicsDevice);
    4. blastoise = Content.Load<Texture2D>("blastoise");
    5. charizard = Content.Load<Texture2D>("charizard");
    6. venusaur = Content.Load<Texture2D>("venusaur");
    7. }


    Der Content-Manager lädt nun unsere 3 hinzugefügten Sprites aus dem Content-Ordner und weist den Texture2D-Variablen deren Wert zu. Um dem Content-Manager mitzuteilen, welches Objekt er laden soll, wird in "<" und ">" der jeweilige Typ angegeben.


    Unser erster Sprite
    Da das Laden der Sprites mittlerweile einwandfrei funktioniert, können wir uns dem Zeichnen widmen. Um Sprites auf die Form zu zeichnen, benötigen wir ein SpriteBatch. Wie auch der Content-Manager, wurde das SpriteBatch bereits mit dem Erstellen des Projekts instanziert; wir brauchen uns also nicht mehr darum zu kümmern, sondern können direkt darauf zugreifen. Mithilfe von SpriteBatch.Draw() können wir Grafiken auf die Form rendern und müssen lediglich 3 Parameter für den Zeichen-Aufruf angeben:


    texture [size=xx-small]Die zu zeichnende Grafik[/size]
    destinationRectangle [size=xx-small]Der Abstand zum oberen linken Fensterrand und die Größe, in der die Grafik gezeichnet werden soll[/size]
    color [size=xx-small]Die Farbtönung, mit der der Sprite gezeichnet werden soll (Standard ist Color.White)[/size]


    Um also als erstes unser Turtok zeichnen zu können, verwenden wir folgenden Aufruf der Funktion:


    Code
    1. protected override void Draw(GameTime gameTime)
    2. {
    3. GraphicsDevice.Clear(Color.CornflowerBlue);
    4. spriteBatch.Draw(blastoise, new Rectangle(0, 0, blastoise.Width, blastoise.Height), Color.White);
    5. base.Draw(gameTime);
    6. }


    Und schon wird unser Turtok auf die Form gerendert. Drückt F5 um euch selbst davon zu überzeugen und das Projekt zu debuggen. Hm? Wie bitte? Ein Laufzeitfehler? Was haben wir denn vergessen? Richtig. Den Begin()-Aufruf und den End()-Aufruf des SpriteBatches. Sobald ihr diesen noch hinzufügt, sollte alles reibungslos funktionieren.



    Drückt wieder F5 um euch vom Ergebnis zu überzeugen und siehe da, unser Turtok wird in seiner ganzen Pracht auf die Form geklatscht:


    [Blockierte Grafik: http://img137.imageshack.us/img137/3330/img04.png]


    Text zeichnen
    Unser Turtok sieht zwar schon prächtig aus, eine Aussage wie "Ich bin Turtok!" trifft es aber noch nicht. Damit wir unser Turtok sprechen lassen können, brauchen wir eine neue Methode, klar könnten wir eine Grafik erstellen, die Text beinhaltet, aber das geht unter XNA auch eleganter. Die DrawText()-Methode kommt ins Spiel. Doch bevor wir diese benutzen können, braucht man ein gewisses Hintergrundwissen. Fonts werden in Xna nicht wie in anderen Game-Engines behandelt und es ist auch nicht möglich diese aus dem Windows-Font Ordner auszulesen und dann zu verwenden. Xna setzt in diesem Punkt auf ein eigenes Format bzw. auf eine eigene Klasse, die sich SpriteFont nennt. Ein SpriteFont hat den Vorteil, dass der verwendete Font nicht auf dem Computer des Nutzers installiert sein muss, der das Spiel spielt. Der Nachteil ist, dass wieder zusätzlich Dateien entstehen, die geladen werden müssen. In unserem Beispiel reicht der Windows-Standard Font "Arial". Wir fügen also über den Projektmappenexplorer per "Content" > "Hinzufügen" > "Neues Element" und solltet zu einem neuen Dialog kommen, der euch 3 Möglichkeiten bietet: Xml-Datei, Effect File und Sprite Font. Was uns erstmal interessiert, ist wie bereits gesagt, der Sprite Font. Gebt ihm am besten den Namen "Arial", dann können wir ihn später besser identifizieren.


    [Blockierte Grafik: http://img844.imageshack.us/img844/8144/img05.png]


    Sollte "Arial.spritefont" hinzugefügt worden sein, müssen wir uns vorher um den Font an sich kümmern. Die Struktur, wir vllt. zu erkennen, ist mit XML eine einfach zu veränderne Anordnung an Daten, vorerst sollten wir jedoch wieder alle Kommentare entfernen, damit das Ganze etwas übersichtlicher wird:



    Momentan definiert dieser Spritefont einen Kootenay-Font der Größe 14, was wir wollen ist jedoch das standardmäßge Arial. Also ändern wir einfach diese Zeile:


    Code
    1. <FontName>Kootenay</FontName>


    und ersetzen sie mit dieser:


    Code
    1. <FontName>Arial</FontName>


    Nun, da unser Spritefont einsatzbereit ist, können wir uns wieder der Verwendung im Code wenden. Wie bestimmt zu erwarten war, wird der Spritefont wieder über den Content-Manager geladen, diesmal jedoch mit einem kleinen Unterschied. Zuerst erstellen wir wieder eine Variable, die später unseren Sprite Font beinhalten soll:


    Code
    1. SpriteFont arial; //Arial Font


    Um den entsprechend von uns erstellten Spritefont benutzen zu können, müssen wir ihn natürlich erstmal laden. Da unser Objekt-Typ diesmal SpriteFont ist, ändern wir das zwischen "<" und ">" stehende Texture2D einfach in "SpriteFont" und benutzen den Namen unserer Datei "Arial":


    Code
    1. arial = Content.Load<SpriteFont>("Arial");


    Der Spritefont sollte nun ohne Probleme geladen werden und ist dementsprechend verwendbar. An dieser Stelle kommt die vorhin erwähnte Methode DrawString() zum einsatz.


    Code
    1. spriteBatch.DrawString(arial, "Ich bin ein Turtok!", new Vector2(blastoise.Width, blastoise.Height / 2), Color.White);


    Wir verwenden nun den geladenen SpriteFont arial, den Text "Ich bin ein Turtok!" und die Farbe weiß. Für den einen oder anderen mag jetzt die Position etwas unklar sein, hier eine kurze Erklärung:


    [Blockierte Grafik: http://img828.imageshack.us/img828/1069/img06.png]


    Wir wollen den Text rechts neben Turtok zeichnen, d.h. der Abstand zum linken Fensterrand muss so groß sein, wie Turtok breit ist. Um den Text dann vertikal zentriert zu zeichnen, nehmen wir als oberen Fensterrand-Abstand die Hälfte seiner Höhe. Wenn ihr alles richtig gemacht habt, sollte das Ergebnis so ausschauen:


    [Blockierte Grafik: http://img135.imageshack.us/img135/6285/img07.png]


    5. Shader
    Was sind Shader?
    Shader könnte man als Programme bezeichnen, die von der Grafikkarte ausgeführt werden und mit denen man die Ausgabe von Grafiken oder Vertices (3D-Objekten) verändern kann. Bekannte Beispiele dafür sind z.B. Bump und Blur, oder auch der Bloom-Effekt. Shader werden in Xna über die Content-Pipeline bzw. über den Content-Manager geladen und in einer "eigenen" Programmiersprache definiert. Es gibt 2 verschiedene Arten von Shadern, einmal Pixel Shader, welche den Effekte auf eine 2D-Grafik anwenden und auf der anderen Seite Vertex-Shader, die wir hier nicht weiter behandelt werden.


    Einstieg in Shader
    Wir erstellen zuerst einen Shader, der noch keinen Effekt aufweist:



    Der Teil, in dem technique MeinErsterShader steht, hat uns erstmal nicht großartig zu interessieren. Er wendet die Funktion PS_COLOR() an und gibt deren Wert zurück. Viel mehr interessiert uns, was die Funktion PS_COLOR() denn nun anrichtet, sie gibt ja lediglich einen Wert von 1.0 mit dem Typ Float4 zurück. Die Float4-Variable ist in diesem Fall mit einem Array aus Float Werten zu vergleichen, dass 4 Elemente fasst und für eine RGBA (Rot-Grün-Blau-Alpha) Farbe steht. Da wir ja wissen, dass Shader die Pixel einer Grafik mithilfe der Grafikkarte verändern, müssten wir logischerweise daraus schließen, dass der Shader für alle Pixel, die in der Grafik vorkommen, den Wert 1.0f für Rot, Grün, Blau und auch den Transparenz-Wert zurückgibt. Das Ergebnis ist also eine nicht-transparente weiße Farbe und somit ein weißes Bild.


    Ein vielleicht einfacheres Beispiel ist die direkte Rückgabe von RGBA-Farben. Das geht folgendermaßen:



    In diesem Fall würde das Bild einer roten Fläche gleichen, da wir allen Pixeln der Grafik den Rot-Wert 0.9, den Grün-Wert 0.4 und den Blau-Wert 0.2 zuordnen. Der Alpha(also Transparenz)-Wert ist mit 1.0 angegeben, d.h. das bild ist nicht-transparent.


    Das alles klingt sicherlich furchtbar kompliziert, solltest du das nicht verstanden haben, begib dich möglichst zum nächsten Teil und versuche dich mit diesem Teil des Tutorials auseinanderzusetzen, wenn du mehr Erfahrung im Bereich Programmierung bzw. XNA gesammelt hast.


    Kommen wir zu Texturen und Samplern. Wie ihr vielleicht bemerkt habt, haben wir bis jetzt noch keinen Zugriff auf die eigentliche Textur, um sie gegebenenfalls abzudunkeln oder in einem rötlichen Farbton darzustellen. Was wir bisher erreicht haben, war die Rückgabe einer Farbe für ein Bild. Um dieses Problem zu lösen, arbeiten wir mit Samplern und TextureCoordinates. Ein Sampler nimmt die vom SpriteBatch gerenderte Texut auf, sodass wir nun endlich darauf zugreifen und sie bearbeiten können. Zu allererst deklarieren wir den Sampler, der später die Textur aufnimmt und fügen den Parameter texCoord (TextureCoordinate) zur Funktion PS_Color hinzu:


    Code
    1. sampler firstSampler;
    2. float4 PS_COLOR(float2 texCoord: TEXCOORD0) : COLOR
    3. {
    4. }


    Jetzt brauchen wir natürlich Zugriff auf den Farbwert eines Pixels in der Textur, was auch als "samplen" bezeichnet wird. Die folgende Funktion gibt die Textur unverändert zurück:


    Code
    1. sampler firstSampler;
    2. float4 PS_COLOR(float2 texCoord: TEXCOORD0) : COLOR
    3. {
    4. float4 color = tex2D(firstSampler, texCoord);
    5. return color;
    6. }


    So, genug der Theorie, um es auch wirklich anwenden zu können, braucht jeder ein bisschen Praxis. Wir erstellen also ein neues Effect File in unserem Projekt, dass wir "blastoise_fx.fx" nennen.


    [Blockierte Grafik: http://img257.imageshack.us/img257/9396/img08.png]


    Dort fügen wir folgenden Code ein:


    Der Code bewirkt, wie eben schon erwähnt, vorerst absolut gar nichts. Unser Ziel ist es erstmal, unser Turtok komplett ohne blaue Farbwerte zu zeichnen. Das erreichen wir mit folgendem Aufruf:



    Der Shader ändert nun jeden Pixel in der Textur und entfernt den Blau-Wert, sodass nur noch Grün- bzw. Rot-Werte verbleiben. Die große Frage ist nun, wie wendet man Shader an? Zuerst erstellen wir wieder eine Variable und laden unseren Shader mit dem Content-Manager:


    Code
    1. Effect blastoise_fx;


    Code
    1. blastoise_fx = Content.Load<Effect>("blastoise_fx");


    Damit wäre unser Shader einsatzbereit. Um ihn nun anzuwenden, benutzen wir folgenden Code:



    Der Code initalisiert wieder das Spritebatch, doch diesemal mit speziellen Optionen, die nötig sind, damit der Shader richtig funktioniert. Anschließend wird das Spritebatch, der Effekt und die Technik (technique Blastoise) initialisiert, der Sprite wird gezeichnet und das Spritebatch, bzw. die anderen beiden Komponenten mit End() angeschlossen. Das Ergebnis ist ein Turtok, komplett ohne Blau-Werte:


    [Blockierte Grafik: http://img716.imageshack.us/img716/8744/img09.png]


    Falls euch dieser Part des Tutorials zu komplex war, lest ihn am besten noch einmal oder ignoriert ihn und verwendet ihn später, wenn ihr etwas länger mit XNA gearbeitet habt. Das wars fürs erste mit den Shadern, natürlich kann man auch nach wesentlich komplexere Sachen damit praktizieren, aber simple Farbshader fand ihr für dieses Einstiegstutorial allemal komplex genug. Und wenn ich jetzt nochmal das Wort komplex verwende, denkt ihr bestimmt, kennt der keine anderen Wörter außer komplex? Deshalb belassen wir es mal lieber dabei und widmen uns anderen spannenden Teil des XNA Frameworks :wink:


    6. Maus und Tastatur
    Abfangen von Eingabegeräten
    Sicherlich ist ein Spiel kein Spiel, wenn man ein paar Sprites auf die Form rendert und diese sich nicht einmal bewegen. Für Bewegungen wird eigentlich in fast allen Spielen auf die Maus und die Tastatur zurückgegriffen, teilweise gibt es aber auch Joystick-Steuerung, mit der wir uns vorerst nicht beschäftigen. Um Tastatur- und Mauseingaben abzufangen, gibt es eine bzw. mehrere spezielle Klassen:


    Keyboard, KeyBoardState, Mouse und MouseState


    In diesem Kapitel werden wir uns der Update()-Methode widmen, in der diese Aktionen hauptsächlich berechnet werden. Unser Ziel ist es, dass sich unser Turtok mit der Maus steuern lässt und somit wie ein Mauszeiger fungiert. Dafür erstellen wir erstmal eine Vector2-Variable, in der wir die aktuelle Position speichern:


    Code
    1. Vector2 mousePosition;


    Um nun die aktuelle Position der Maus in dieser Variable zu speichern zu können, muss etwas Code in der Update()-Methode ergänzt werden, dieser sieht wie folgt aus:


    Code
    1. MouseState mouseState = Mouse.GetState();
    2. mousePosition = new Vector2(mouseState.X, mouseState.Y);


    Der Code bewirkt folgendes: Der Variable mouseState wird der aktuelel Status der Maus, sprich Position, gedrückte Tasten etc. zugeordnet. Die Variable mousePosition, die wir zur Positionierung unseres Turtoks verwenden möchten, bekommt die X- bzw. Y-Position des MouseStates zugewiesen. Damit wäre unsere Maussteuerung auch soweit fertig, fehlt nur noch das Ergänzen in der Draw()-Methode. Anstatt dem X- und Y-Wert 0 setzen wir nun einfach den X- bzw. Y-Wert der Variable ein:


    Code
    1. spriteBatch.Draw(blastoise, new Rectangle((int)mousePosition.X, (int)mousePosition.Y, blastoise.Width, blastoise.Height), Color.White);


    Wenn ihr nun nch F5 drückt, könnt ihr die Änderung begutachten: Das Turtok folgt der Maus.


    Unser Sprite lernt laufen
    Unser letztes, aber doch immernoch simples Ziel für dieses Tutorial ist es, ein sich per Pfeiltasten steuern lassendes Glurak zu programmieren. Wir ändern kurz den Namen unserer Vector2-Variable, da die Bezeichnung nicht mehr ganz passend ist:


    Code
    1. Vector2 currentPosition; //Anstatt mousePosition


    Wir fügen abermals eine Erweiterung in unsere Update()-Methode ein:



    Der Code fragt den Tastaturstand ab und überprüft jeweils die Tasten Hoch, Runter, Links, Rechts, auf den aktuellen Staus.


    Natürlich würde derzeit Turtok gezeichnet werden, war unser Ziel nicht ein Glurak? Kein Problem, ändern wir eben die Variable in der Draw-Methode() von Turtok zu der von Glurak:


    Code
    1. spriteBatch.Draw(charizard, new Rectangle((int)currentPosition.X, (int)currentPosition.Y, charizard.Width, charizard.Height), Color.White);


    Und drücken abermals auf F5. Das Ergebnis sollte, sofern alles richtig gemact wurde, ein per Pfeiltasten steuerbares Glurak sein.


    MfG ;)

  • Hi


    ich weiß nicht wos hinsoll also mach ichs hier.
    Mein Code:


    http://www.xnamag.de/article.php?aid=28


    So wollte ich das machen. Aber der Hero wird nicht angezeigt. Aber als ich Teil 1 gelesen hab wurde er angezeigt. WTF? Mich regt das gerade so auf!


    MfG
    raupy


    Edit:


    Im Tutorial wird nicht erwähnt dass man in der Game.Draw Methode noch ein hero.draw machen muss. So klappts dann auch...


    Krissel ftw.!

    Einmal editiert, zuletzt von raupy ()


  • Im Tutorial wird nicht erwähnt dass man in der Game.Draw Methode noch ein hero.draw machen muss. So klappts dann auch...


    Und wo genau im Tutorial wird dein geposteter Code überhaupt erklärt? hero.Draw ist eine Methode deiner Klasse und hat absolut nichts mit dem Tutorial zu tun, aus welchem Grund sollte also im Tutorial erwähnt werden, dass man genau diese Methode in der Draw Routine aufrufen sollte? Macht für mich keinen Sinn ;)


    Das Tutorial befasst sich außerdem nicht mit dem Rendern von Tilemaps, sondern bietet einen Einstieg in XNA, sodass man Sprites rendern, Effekte anwenden und Tastatur bzw. Maus abfragen kann.


    MfG

  • Naja, der Hero ist ja an sich ein Sprite und Hero ist ein Map Object, diese Klasse wird im Tutorial erstellt. Ist ja jetzt auch egal.


    MfG
    raupy


    Krissel ftw.!