Terrain 101: Kamera ab und Action

Nachdem ich im vorherigen Teil erklärt habe, wie wir eine einfache Kamera kapseln und in eine eigene Klasse auslagern, geht es nur an die etwas komplizierteren Themen. In diesem Teil werden wir die Kamera bewegen was sowohl eine Rotation, als auch eine Translation umfassen wird und ich werde eine einfache Möglichkeit vorstellen eine Kollisionserkennung zu entwickeln, die verhindert, dass die Kamera durch die Landschaft fällt. Weiterhin wird das Thema Eingabebehandlung rudimentär behandelt, da wir unsere Kamera schliesslich mit einem bzw. zwei Eingabegeräten steuern möchten.

Langsam fällt mir kein Text mehr ein für die Einleitung zum Inhaltsverzeichnis, daher werde ich mir dies in diesem Teil einfach mal sparen.

Das Inhaltsverzeichnis

Terrain 101: Eine Einführung in dreidimensionale Landschaften in Echtzeit
Terrain 101: Die Entwicklungsumgebung und das XNA 4.0 Projekt
Terrain 101: Das erste Dreieck
Terrain 101: Transformationen
Terrain 101: Vertex- und Index-Buffer
Terrain 101: Land in Sicht
Terrain 101: Technische Rafinessen
Terrain 101: Neue Sichtweisen
Terrain 101: Kamera ab und Action

Die Planung

Am Anfang sollte immer die Planung stehen. In dieser Phase haben wir die Gelegenheit uns ein paar Gedanken darüber zu machen, was wir überhaupt benötigen und in welcher Art und Weise wir das Ziel erreichen wollen. Mögliche Probleme werden oft schon in dieser Phase sichtbar. Es macht zwar nicht unbedingt immer Sinn alles bis ins letzte Detail zu planen und oft ist dies auch garnicht möglich, aber trotzdem ist die Planung ein sehr wichtiger Punkt für den man sich ausreichend Zeit reservieren sollte.

Fangen wir zunächst damit an, was unsere Kamera können soll. Wir wollen uns umschauen können und dafür soll die Maus verwendet werden. Die Maus bestimmt dabei direkt den Blickwinkel. Natürlich möchten wir auch umherlaufen können und dafür verwenden wir die Tastatur. Dafür bietet sich die bekannte WASD-Steuerung an. Mit W laufen wir vorwärst und mit S laufen wir rückwärts, wobei wir uns dabei immer in Richtung des Kamera-Blickwinkels bewegen werden. Mit den Tasten A und D bewegen wir uns seitwärts in die jeweilige Richtung.

Die Höhe der Kamera wird durch die Höhe der Landschaft an der Kamera-Position bestimmt. Die Höhe der Kamera soll ca. 2m über der Landschaft sein, damit wir eine Perspektive erreichen, die einem Menschen entspricht. Damit erzeugen wir also eine sogenannte First Person Kamera.

Um später ein paar Dinge besser debuggen zu können und um eine perfekte Übersicht über die Welt zu haben, werden wir noch eine Art Flugmodus einbauen. Dabei soll die Kamera nicht zu Boden „fallen“, sondern die Höhe soll direkt kontrollierbar sein.

Um das Verhalten der Kamera besser beeinflussen zu können macht es Sinn, dass wir die Verhaltensweisen und Einschränkungen der Kamera austauschbar machen. Dazu werden wir sogenannte Controller und Constraints verwenden. Diese werden es uns ermöglichen, dass wir später z.B. einen vordefinierten Pfad über unserer Landschaft ablaufen werden um so z.B. die Geschwindigkeit zu messen.

Controller und Constraints

Controller kontrollieren das Verhalten der Kamera. Es sind kleine und einfache Module, die einer Kamera zugewiesen werden und die bestimmte Dinge mit der Kamera machen können. Ein Controller wäre beispielsweise ein „TranslationController“ der sich darum kümmert, dass die Eingabe W in eine Vorwärtsbewegung umgesetzt wird. Ist der TranslationController der Kamera zugewiesen und aktiviert, so reagiert die Kamera auf diese Eingabe, ansonsten nicht. Wenn wir später die Bewegung durch ein GamePad kontrollieren wollen, dann tauschen wir ganz einfach diesen Controller gegen einen passenden aus und schon kann die Kamera etwas anderes.

Die Constraints sind Einschränkungen. Ein Constraint kann z.B. verhindern, dass wir durch die Landschaft hindurch fallen, indem dieser Constraint einfach die Höhe der Kamera überwacht und so anpasst, dass wir uns immer oberhalb der Landschaft befinden. Ein anderer Constraint kann z.B. dafür zuständig sein, dass wir uns nicht über den Rand der Landschaft hinaus bewegen.

Wir werden in diesem Abschnitt nun die Kamera um Controller und Constraints erweitern und danach werden wir gemäß unserer Planung folgende Module erstellen:

  • TranslationController – für die Steuerung per WASD
  • RotationController – für die Steuerung der Blickrichtung per Maus
  • BoundsConstraint – damit wir nicht über den Rand der Landschaft hinauslaufen können
  • HeightConstraint – damit die Kamera immer 2m über der Landschaft bleibt oder im Flugmodus immer über der Landschaft

Im ersten Schritt erzeugen wir zwei Interfaces die unsere Constraint- und Controller-Module beschreiben und es uns ermöglichen mit diesen zu arbeiten, ohne das wir zur Entwicklungszeit und in der Kamera-Klasse bereits die genaue Implementation kennen müssen. Da dies kein Tutorial über Objektorientierte Programmierung sein soll, setze ich das Grundlagenwissen über Interfaces einfach voraus. Die beiden Interfaces sehen wie folgt aus und sollten jeweils in eine eigene Datei mit Namen ICameraController.cs und ICameraConstraint.cs gepackt werden.

    public interface ICameraController
    {
        void Update(Camera camera, GameTime gameTime);
    }

    public interface ICameraConstraint
    {
        void Update(Camera camera, GameTime gameTime);
    }

Im Grunde genommen wird damit nur festgelegt, dass sowohl ein Constraint, als auch ein Controller eine Update-Methode enthalten muss. Diese wird später von der Kamera aufgerufen. Dabei wird eine Referenz auf die aufrufende Kamera übergeben, damit das Modul auch auf deren Daten agieren kann. Dieses Konzept wird später aber noch klarer, wenn wir die einzelnen Module implementieren.

Selbstverständlich müssen wir auch die Kamera-Klasse noch ein wenig erweitern. Zum einen benötigen wir zwei neue Membervariablen.

        private List<ICameraConstraint> constraints = new List<ICameraConstraint>();
        private List<ICameraController> controllers = new List<ICameraController>();

Diese beiden Listen enthalten später alle Controller und Constraints die einer Kamera zugewiesen sind und die dann der Reihe nach abgearbeitet werden. Weiterhin benötigen wir auch noch zwei neue Methoden die es uns ermöglichen der Kamera neue Module zuzuweisen. Natürlich macht es Sinn noch mehr Methoden zu erstellen, die Module verwalten, z.B. Prüfmethoden, die ermitteln, ob ein bestimmtes Modul bereits zugeweisen wurde, Methoden zum entfernen der Methoden usw. Dies würde aber dieses Tutorial nur aufblähen und ist für das grundlegende Verständnis erstmal nicht notwendig. Vielleicht werde ich später mal diese Themen in einem eigenen Artikel aufgreifen. Nun aber zu den beiden angesprochenen Methoden.

        public void AddController(ICameraController controller)
        {
            this.controllers.Add(controller);
        }

        public void AddConstraint(ICameraConstraint constraint)
        {
            this.constraints.Add(constraint);
        }

Bisher alles kein Hexenwerk und vieles fehlt auch für den ersten Schritt nicht mehr. Der nächste Punkt ist eine Update-Methode für die Kamera-Klasse. Diese wird von unserem Spiel später in regelmäßigen Abständen aufgerufen. Die Methode selbst ist recht einfach gehalten, da sie einfach jeden Controller und jeden Constraint aufruft, den wir unserer Kamera zugewiesen haben.

        public void Update(GameTime gameTime)
        {
            foreach (ICameraController controller in this.controllers)
            {
                controller.Update(this, gameTime);
            }

            foreach (ICameraConstraint constraint in this.constraints)
            {
                constraint.Update(this, gameTime);
            }
        }

Der Code sollte soweit selbsterklärend sein und nun fehlt nur noch eine Kleinigkeit und zwar in der Game-Klasse. Dort müssen wir in der Update-Methode schlicht und einfach die Update-Methode der Kamera aufrufen.

camera.Update(gameTime);

Damit haben wir nun für klare Strukturen und Verhätnismäßigkeiten gesorgt. Wir können nun in der Game-Klasse einfach einer Kamera per Methodenaufruf Controller- und/oder Constraint-Module hinzufügen, die dann automatisch auf die Kamera agieren. Das ist einfach und bequem und vor allem ziemlich mächtig.

Eine wichtige Grundvoraussetzung fehlt uns jedoch noch und zwar die Eingabebehandlung, die ich nun im nächsten Schritt erläutern und einbauen möchte.

Eingabebehandlung

Über das sogenannte Input-Handling kann man sicherlich mehr als ein komplettes Tutorial schreiben, aber da wir Ergebnisse wollen und dieses Tutorial die Kamera behandelt, werde ich das Thema hier nur kurz anschneiden.

In der Spieleentwicklung und damit auch in XNA ist es üblich, dass man mit einer sogenannten ungepufferten Eingabebehandlung arbeitet. Dies bedeutet im Grunde genommen, dass man Eingabeereignisse live abfragt. Man kann also z.B. abfragen, ob eine Taste gerade gedrückt ist oder nicht. Man bekommt aber nicht die Information, welche Tasten seit der letzten Abfrage alle gedrückt wurden. Um diese Information muss man sich selbst kümmern, was wir aber auch garnicht benötigen. Wir benötigen lediglich folgende Informationen:

  • Ist eine bestimmte Taste gedrückt
  • Wurde eine bestimmte Taste gerade gedrückt
  • Wurde eine bestimmte Taste gerade losgelassen
  • Aktuelle Mausposition

Den ersten und letzten Punkt können wir ganz einfach ermitteln, indem wir uns den aktuellen Status des Eingabegerätes holen und dort die entsprechenden Daten abrufen. Die beiden anderen Punkte sind ein klein wenig komplexer, aber nicht wirklich schwer. Beide Ereignisse treten exakt dann auf, wenn sich der Status einer Taste seit der letzten Abfrage geändert hat. Der erste Fall tritt auf, wenn die Taste bei der vorherigen Abfrage nicht gedrückt war und nun gedrückt ist und der andere Fall tritt ein, wenn die Taste zuvor gedrückt war und nun nicht mehr. Und um diese Information zu erhalten benötigen wir nicht nur den aktuellen Status, sondern auch den Status der letzten Abfrage, den wir schlicht und einfach in einer Variable speichern. Wir benötigen also insgesamt vier Variablen: eine für den aktuellen Status, eine für den letzten und dies jeweils für Maus und Tastatur. Diese Variablen packen wir einfach mal in die Kamera-Klasse. Dies ist eigentlich nicht die beste Stelle dafür, macht aber dieses Tutorial etwas einfacher und daher gehen wir zunächst diesen Weg.

        private KeyboardState keyboardState;
        private KeyboardState lastKeyboardState = Keyboard.GetState();
        private MouseState mouseState;
        private MouseState lastMouseState = Mouse.GetState();

Dabei initialisieren wir die beiden last-Variablen mit dem aktuellen Status, was vielleicht ein wenig seltsam anmutet. Die Erklärung dazu ist schlicht: Bei der allerersten Prüfung erspart uns dies eine Sonderfallbehandlung, wenn wir den aktuellen mit dem letzten Status vergleichen. Würden wir die beiden Werte nicht initialisieren, so müssten wir diese zunächst auf null überprüfen, damit keine Exception auftritt.

Die Variablen werden einfach in der Update-Methode der Kamera-Klasse befüllt. Zunächst am Anfang der Methode der aktuelle Status:

            keyboardState = Keyboard.GetState();
            mouseState = Mouse.GetState();

Danach werden die Controller und Constraints aktualisiert. Den Code dazu haben wir ja bereits zuvor entwickelt. Danach sichern wir noch den aktuellen Status in den last-Variablen, damit wir den letzten mit dem aktuellen Status jederzeit vergleichen können.

            lastKeyboardState = keyboardState;
            lastMouseState = mouseState;

Damit wir aus unseren Modulen auch auf den Eingabe-Status zugreifen können, müssen wir noch ein paar Eigenschaften anlegen. Da wir beim Aufruf der Controller- und Constraint-Module ja eine Referenz auf die aufrufende Kamera übergeben, kann diese ganz einfach mittels dieser Eigenschaften den benötigten Eingabestatus ermitteln.

        public KeyboardState KeyboardState
        {
            get
            {
                return this.keyboardState;
            }
        }

        public KeyboardState LastKeyboardState
        {
            get
            {
                return this.lastKeyboardState;
            }
        }

        public MouseState MouseState
        {
            get
            {
                return this.mouseState;
            }
        }

        public MouseState LastMouseState
        {
            get
            {
                return this.lastMouseState;
            }
        }

Soviel zur Eingabebehandlung und weiter geht es mit der Implementation der eigentlichen Controller und Constraints.

Controller und Constraints

Wir haben nun alle Grundvoraussetzungen geschaffen um das Verhalten unserer Kamera durch Controller und Constraints beeinflussen zu können. Wenn wir das aktuelle Programm nun starten, so sehen wir nicht sehr viel. Alles ist exakt so, wie es auch bereits im letzten Teil war. Dies liegt daran, dass wir ja noch keine Controller haben und diese auch noch nicht der Kamera zugewiesen haben und das werden wir nun ändern.

RotationController

Wir beginnen mit dem RotationController, der es uns ermöglichen soll durch Bewegung mit der Maus die Blickrichtung zu ändern. Dazu erzeugen wir eine neue Klasse RotationController mit folgendem Code.

    public class RotationController : ICameraController
    {
        public void Update(Camera camera, GameTime gameTime)
        {

        }
    }

Dieser Controller macht zwar noch nichts, trotzdem werden wir ihn unmittelbar nach der Erzeugung unserer Kamera in der Methode Initialize der Game-Klasse zuweisen.

            this.camera.AddController(new RotationController());

Der RotationController wird durch diesen Aufruf in die Auflistung der Controller unserer Kamera aufgenommen und damit bei jedem Update-Aufruf aufgerufen. Und damit dieser Controller nun auch etwas macht, werden wir diesen jetzt implementieren. Erstmal der Code, den wir in die Update-Methode des Controllers packen:

            pitch += -(camera.MouseState.Y - camera.LastMouseState.Y) * rotationSpeed;
            yaw += (camera.MouseState.X - camera.LastMouseState.X) * rotationSpeed;

            Matrix rotationMatrix = Matrix.CreateRotationZ(roll) * Matrix.CreateRotationX(pitch) * Matrix.CreateRotationY(yaw);
 
            camera.Target = Vector3.Transform(Vector3.Forward, rotationMatrix) + camera.Position;

gefolgt von ein paar Member-Variablen, die wir im Kopf des Controllers deklarieren.

        const float rotationSpeed = 0.03f;

        float yaw = 0.0f;
        float pitch = 0.0f;
        float roll = 0.0f;

Glücklicherweise ist dies nicht soviel Code und daher auch schnell erklärt. Zunächst errechnen wir die Werte pitch und yaw aus der aktuellen und vorherigen Mausposition. Pitch ist dabei die Rotation der Kamera um die X-Achse (also die Neigung nach oben und unten) und wird aus der Bewegung der Maus auf der Y-Achse ermittelt. Um die Bewegung zu ermitteln subtrahieren wir von der aktuellen Position der Maus die vorherige Position und erhalten somit ein Delta. Dieses Multiplizieren wir mit der Konstante rotationSpeed, damit die Rotationsgeschwindigkeit nicht so hoch wird. Mit dem Wert Yaw verfahren wird analog. Dieser gibt die Rotation der Kamera um die Y-Achse an, also eine Rotation um die eigene Längsachse nach rechts oder links.

Mit diesen Werten und dem Wert Roll (den wir hier nicht benötigen) erzeugen wir eine Rotationsmatrix. Dazu verwenden wir die Methoden CreateRotationX, CreateRotationY und CreateRotationZ der Matrix-Klasse. Mit dieser Rotationsmatrix können wir nun den Forward-Vektor (der zeigt immer in Blickrichtung) transformieren, so dass er in die Richtung zeigt, die wir nach der Rotation erwarten. Da die Kamera-Klasse das Target relativ zur Kamera-Position erwartet addieren wir einfach noch die aktuelle Position der Kamera zum Richtungsvektor und weisen das Ergebnis als neues Kamera-Target hinzu.

Damit ist der RotationController fertig und Einsatzbereit. Starten wir nun unser Projekt, so können wir die Kamera mit der Maus schwenken.

LocationController

Der nächste Schritt ist die Bewegung der Kamera. Darum kümmern wir uns im LocationController, den wir nun als neue Klasse anlegen. Den Code dafür zeige ich diesmal nicht, da er bis auf den Namen exakt so aussieht wie im vorherigen Abschnitt für den Rotation-Controller gezeigt. Die eigentliche Implementierung weicht selbstverständlich ab.

Den neuen Controller registrieren wir wieder in der Initialize-Methode der Game-Klasse mittels:

this.camera.AddController(new TranslationController());

Zunächst wollen wir uns durch einen Druck auf die W-Taste vorwärst bewegen. Dazu verwenden wir folgenden Code:

            Vector3 forwardVector = Vector3.Normalize(camera.Target - camera.Position);

            if (camera.KeyboardState.IsKeyDown(Keys.W))
            {
                camera.Position += forwardVector * translationSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
                camera.Target += forwardVector * translationSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
            }

Der Code ist ebenfalls wieder sehr einfach: Zunächst ermitteln wir den forwardVector. Dies ist der Richtungsvektor der exakt nach vorne in Blickrichtung zeigt. Diesen können wir aus dem aktuellen Target und der Position der Kamera berechnen. Zur Sicherheit normalisieren wir diesen Vektor.

Wird nun die Taste W gedrückt, so wollen wir uns nach vorne bewegen. Dazu prüfen wir, ob die Taste W gedrückt wurde und bewegen sowohl die Position, als auch das Target der Kamera in die Richtung des forwardVector. Dies erfolgt durch eine einfache Addition. Damit wir die Geschwindigkeit beeinflussen können multiplizieren wir die Richtung noch mit einer Geschwindigkeit translationSpeed. Dies „verlängert“ den Vektor um den jeweils multiplizierten Wert. Je höher der Wert, desto schneller die Bewegung. Um unabhängig von Schwankungen in der Ausführungsgeschwindigkeit zu sein, multiplizieren wir diesen Wert noch mit der gameTime. Dies machen wir, da je nach Ausführungsgeschwindigkeit der Update-Intervall mal länger und mal kürzer sein kann. Ist er kürzer, so sollten wir uns etwas langsamer bewegen und ist er länger, so müssten wir uns etwas schneller bewegen um immer den gleichen Geschwindigkeitseindruck zu haben.

Starten wir nun unser Programm wieder, so können wir uns mit der Taste W in Blickrichtung der Kamera bewegen. Da die restlichen geplanten Bewegungen sehr einfach umzusetzen sind, lasse ich das an dieser Stelle mal als Aufgabe für euch unerklärt.

BoundsConstraint

Constraints sind ebenfalls nicht weiter schwer und werden im Grunde genau so erzeugt, wie auch Controller. Wir erzeugen eine Klasse BoundsConstraint, die das ICameraConstraint-Interface implementiert und registrieren diese wieder in der Initialize-Methode der Game-Klasse. Bis hierhin ist alles praktisch so, wie in den vorherigen beiden Abschnitten beschrieben. Selbstverständlich ist die Implementation der Update-Methode unterschiedlich.

Die Update-Methode enthält nur eine einzige Zeile:

            camera.Position = Vector3.Clamp(camera.Position, Vector3.Zero, new Vector3(1280f, float.MaxValue, 1280f));

Was wir mit dieser Zeile machen ist schlicht und einfach die Position der Kamera zwischen der minimalen Position unseres Terrain-Chunks und der maximlane Position unseres Terrain-Chunks zu halten. Wir verwenden dabei float.MaxValue für die Höhe, da wir diese in diesem Constraint nicht behandeln möchten. Das Ergebnis nach einem Programmstart ist nun, dass wir nicht mehr über den Rand unseres ersten Chunks hinauslaufen können.

HeightConstraint

Auch dieser letzte Constraint wird so erzeugt und auch registriert wie alle Controller und Constraints bisher und daher spare ich mir den entsprechenden Text, da er nun sitzen sollte. Mit dem HeightConstraint gibt es aber noch eine andere Herausforderung: Wir benötigen die Höhendaten die wir zur Erzeugung des Terrain-Chunks an der Kameraposition verwendet haben und genau da liegt der Knackpunkt. Unsere bisherige Struktur erlaubt es nicht auf diese Daten zuzugreifen, da unser Objektmodell das einfach nicht hergibt. Es wäre jetzt also Zeit für ein sogenanntes Refactoring, also eine Art Überarbeitung des Source-Codes. Dies ist nicht weiter kompliziert und sollte auch in maximal einer halben Stunde Arbeit erledigt sein, allerdings sind die Erklärungen dazu etwas umfangreicher. Daher habe ich mich spontan entschlossen den HeightConstraint erstmal noch nicht umzusetzen und das Refactoring in einem eigenen Artikel zu behandeln. Dies bietet sich besonders an, da wir dies sowieso für Multi-Chunk-Terrain benötigen werden und im Zuge dessen direkt zwei Fliegen mit einer Klappe schlagen können.

Zusammenfassung und Ausblick

In diesem Teil des Tutorials haben wir eine Menge Arbeiten durchgeführt und wichtige Grundlagen gelernt und erarbeitet. Wir haben unsere Kamera-Klasse mit Leben gefüllt und Constraints und Controllern geplant, die die Kamera beeinflussen können, ohne an der Basisimplementation Änderungen vornehmen zu müssen. Wir haben die Rotation und Translation einer Kamera erarbeitet und im Rahmen dessen die entsprechenden Controller implementiert. Anschliessend haben wir verhindert, dass wir vom Rand unserer kleinen Landschaft fallen können und einen Constraint entwickelt.

Im nächsten Teil geht es darum, dass wir unsere Landschaft vergößern werden. Dazu ist es – wie bereits angesprochen – notwendig, dass wir ein wenig Refactoring betreiben und die Struktur unseres Codes darauf vorbereiten, dass wir mehrere Chunks gleichzeitig haben können, die wir dann zu einem großen Terrain zusammensetzen. Dabei werden wir einige interessante Erkenntnisse erlangen, aber auch die notwendigen Grundlagen schaffen, um uns dann in den übernächsten Teilen des Terrain 101 mit dem Level of Detail beschäftigen zu können. Dieses wird durchaus zwei bis drei Artikel umfassen und danach werden wir uns dann endlich um die Texturierung des Terrains kümmern.

Sourcecode

Zum Abschluss werde ich hier jetzt nochmal den gesamten Sourcecode des aktuellen Projektes als Referenz veröffentlichen. Dieser ist aufgeteilt nach einzelnen Klassen. Ich empfehle auf jeden Fall, dass dieser Code nicht einfach kopiert wird, sondern in der Reihenfolge erarbeitet wird, wie ich ihn beschreibe. Das führt zu einem deutlich besseren Verständnis.

Game.cs

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

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

        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;

        Camera camera;

        Texture2D heightmap;

        RasterizerState rs;

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

            this.Window.Title = "http://www.MitOhneHaare.de - Terrain 101 - Kamera ab und Action (Teil 9)";
        }

        protected override void Initialize()
        {
            rs = new RasterizerState();
            rs.FillMode = FillMode.WireFrame;

            this.camera = new Camera(MathHelper.PiOver4, GraphicsDevice.Viewport.AspectRatio, 1.0f, 2000.0f, new Vector3(640.0f, 640.0f, 1280.0f), new Vector3(640.0f, 0.0f, 640.0f));
            this.camera.AddController(new RotationController());
            this.camera.AddController(new TranslationController());

            this.camera.AddConstraint(new BoundsConstraint());

            base.Initialize();
        }

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

            triangleEffect = Content.Load("Triangle");

            heightmap = Content.Load<Texture2D>("ChunkHeightmap");

            SetupFirstChunk();
        }

        protected override void UnloadContent()
        {
            if (indexBuffer != null)
            {
                indexBuffer.Dispose();
                indexBuffer = null;
            }

            if (vertexBuffer != null)
            {
                vertexBuffer.Dispose();
                vertexBuffer = null;
            }

            base.UnloadContent();
        }

        private void SetupFirstChunk()
        {
            Color[] heights = new Color[heightmap.Width * heightmap.Height];
            heightmap.GetData(heights);

            VertexPositionColor[] vertices = new VertexPositionColor[128 * 128];
            int index = 0;

            for (int z = 0; z &lt; 128; z++)
            {
                for (int x = 0; x &lt; 128; x++)
                {
                    vertices[z * 128 + x] = new VertexPositionColor(new Vector3(x * 10.0f, heights[z * 128 + x].R, z * 10.0f), Color.Green);
                }
            }

            this.vertexBuffer = new VertexBuffer(GraphicsDevice, typeof(VertexPositionColor), 128 * 128, BufferUsage.WriteOnly);
            this.vertexBuffer.SetData<VertexPositionColor>(vertices);

            int[] indices = new int[127 * 127 * 6];
            index = 0;

            for (int z = 0; z &lt; 127; z++)
            {
                for (int x = 0; x &lt; 127; x++)
                {
                    indices[index + 0] = z * 128 + x;
                    indices[index + 1] = indices[index + 0] + 128 + 1;
                    indices[index + 2] = indices[index + 0] + 128;
                    indices[index + 3] = indices[index + 0];
                    indices[index + 4] = indices[index + 0] + 1;
                    indices[index + 5] = indices[index + 0] + 128 + 1;

                    index += 6;
                }
            }

            this.indexBuffer = new IndexBuffer(GraphicsDevice, IndexElementSize.ThirtyTwoBits, 127 * 127 * 6, BufferUsage.WriteOnly);
            this.indexBuffer.SetData<int>(indices);
        }

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

            camera.Update(gameTime);

            base.Update(gameTime);
        }

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

            GraphicsDevice.RasterizerState = rs;

            triangleEffect.Parameters["World"].SetValue(Matrix.Identity);
            triangleEffect.Parameters["View"].SetValue(camera.ViewMatrix);
            triangleEffect.Parameters["Projection"].SetValue(camera.ProjectionMatrix);

            triangleEffect.CurrentTechnique.Passes[0].Apply();

            GraphicsDevice.SetVertexBuffer(this.vertexBuffer);
            GraphicsDevice.Indices = indexBuffer;

            GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, 127 * 127 * 6, 0, 127 * 127 * 2);
            
            base.Draw(gameTime);
        }
    }
}

Camera.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace Terrain_101
{
    public class Camera
    {
        private Matrix projectionMatrix;
        private Matrix viewMatrix;
        private bool viewMatrixDirty;
        private Vector3 position;
        private Vector3 target;
        private Vector3 upVector = Vector3.Up;

        private List<ICameraConstraint> constraints = new List<ICameraConstraint>();
        private List<ICameraController> controllers = new List<ICameraController>();

        private KeyboardState keyboardState;
        private KeyboardState lastKeyboardState = Keyboard.GetState();
        private MouseState mouseState;
        private MouseState lastMouseState = Mouse.GetState();

        public Camera(float fieldOfView, float aspectRatio, float nearPlane, float farPlane, Vector3 position, Vector3 target)
        {
            this.projectionMatrix = Matrix.CreatePerspectiveFieldOfView(fieldOfView, aspectRatio, nearPlane, farPlane);

            this.position = position;
            this.target = target;
            this.viewMatrixDirty = true;
        }

        public Vector3 Position
        {
            get
            {
                return this.position;
            }
            set
            {
                if (this.position != value)
                {
                    this.position = value;
                    this.viewMatrixDirty = true;
                }
            }
        }

        public Vector3 Target
        {
            get
            {
                return this.target;
            }
            set
            {
                if (this.target != value)
                {
                    this.target = value;
                    this.viewMatrixDirty = true;
                }
            }
        }

        public Vector3 UpVector
        {
            get
            {
                return this.upVector;
            }
            set
            {
                if (this.upVector != value)
                {
                    this.upVector = value;
                    this.viewMatrixDirty = true;
                }
            }
        }

        public Matrix ProjectionMatrix
        {
            get
            {
                return this.projectionMatrix;
            }
        }

        public Matrix ViewMatrix
        {
            get
            {
                if (viewMatrixDirty)
                {
                    UpdateViewMatrix();
                }

                return this.viewMatrix;
            }
        }

        public void Update(GameTime gameTime)
        {
            keyboardState = Keyboard.GetState();
            mouseState = Mouse.GetState();

            foreach (ICameraController controller in this.controllers)
            {
                controller.Update(this, gameTime);
            }

            foreach (ICameraConstraint constraint in this.constraints)
            {
                constraint.Update(this, gameTime);
            }

            lastKeyboardState = keyboardState;
            lastMouseState = mouseState;
        }

        public void AddController(ICameraController controller)
        {
            this.controllers.Add(controller);
        }

        public void AddConstraint(ICameraConstraint constraint)
        {
            this.constraints.Add(constraint);
        }

        public KeyboardState KeyboardState
        {
            get
            {
                return this.keyboardState;
            }
        }

        public KeyboardState LastKeyboardState
        {
            get
            {
                return this.lastKeyboardState;
            }
        }

        public MouseState MouseState
        {
            get
            {
                return this.mouseState;
            }
        }

        public MouseState LastMouseState
        {
            get
            {
                return this.lastMouseState;
            }
        }

        private void UpdateViewMatrix()
        {
            // View Matrix erzeugen
            this.viewMatrix = Matrix.CreateLookAt(this.position, this.target, upVector);

            this.viewMatrixDirty = false;
        }
    }
}

ICameraConstraint.cs

using System;
using Microsoft.Xna.Framework;

namespace Terrain_101
{
    public interface ICameraConstraint
    {
        void Update(Camera camera, GameTime gameTime);
    }
}

ICameraController.cs

using System;
using Microsoft.Xna.Framework;

namespace Terrain_101
{
    public interface ICameraController
    {
        void Update(Camera camera, GameTime gameTime);
    }
}

RotationController.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;

namespace Terrain_101
{
    public class RotationController : ICameraController
    {
        const float rotationSpeed = 0.03f;

        float yaw = 0.0f;
        float pitch = 0.0f;
        float roll = 0.0f;

        public void Update(Camera camera, GameTime gameTime)
        {
            // roll -&gt; Kamera nach rechts/links kippen
            // pitch -&gt; hoch/runter schauen
            // yaw -&gt; rechts/links drehen

            pitch += -(camera.MouseState.Y - camera.LastMouseState.Y) * rotationSpeed;
            yaw += (camera.MouseState.X - camera.LastMouseState.X) * rotationSpeed;

            Matrix RotationMatrix = Matrix.CreateRotationZ(roll) * Matrix.CreateRotationX(pitch) * Matrix.CreateRotationY(yaw);
 
            camera.Target = Vector3.Transform(Vector3.Forward, RotationMatrix) + camera.Position;
        }
    }
}

TranslationController.cs

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

namespace Terrain_101
{
    public class TranslationController : ICameraController
    {
        private const float translationSpeed = 100.0f;

        public void Update(Camera camera, GameTime gameTime)
        {
            Vector3 forwardVector = Vector3.Normalize(camera.Target - camera.Position);

            if (camera.KeyboardState.IsKeyDown(Keys.W))
            {
                camera.Position += forwardVector * translationSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
                camera.Target += forwardVector * translationSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
            }

            if (camera.KeyboardState.IsKeyDown(Keys.S))
            {
                camera.Position -= forwardVector * translationSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
                camera.Target -= forwardVector * translationSpeed * (float)gameTime.ElapsedGameTime.TotalSeconds;
            }
        }
    }
}

BoundsConstraint.cs

using System;
using Microsoft.Xna.Framework;

namespace Terrain_101
{
    public class BoundsConstraint : ICameraConstraint
    {
        public void Update(Camera camera, GameTime gameTime)
        {
            camera.Position = Vector3.Clamp(camera.Position, Vector3.Zero, new Vector3(1280f, float.MaxValue, 1280f));
        }
    }
}
Advertisements

Veröffentlicht am 09.01.2012 in 3D Terrain, Tutorial, XNA, XNA 4.0 und mit , , , , , getaggt. Setze ein Lesezeichen auf den Permalink. 42 Kommentare.

  1. Markus Schack

    Schön, dass es weitergeht!

    Zwei kleine Fehler:

    Im Sourcecode für game1.cs sind die „kleine als“ Zeichen als HTML-Tags angegeben statt als Klartextzeichen.
    for (int z = 0; z < 128; z++)

    In der Camera.cs sind die Listen für die constraints und controllers so angegeben:

    private List constraints = new List();
    private List controllers = new List();

    Das führt zur Fehlermeldung:
    Fehler 1 Die Verwendung von Typ „System.Collections.Generic.List“ (generisch) macht das 1-Typargument erforderlich.

    • Danke für den Hinweis, ich werde das gleich korrigieren…

    • Hallo,
      toller Blog. weiter so! 🙂

      zu dem Problem in der Camera.cs hätte ich folgende Lösung:

      private List constraints = new List();
      private List controllers = new List();

      ersetzen mit:

      private List constraints = new List();
      private List controllers = new List();

      Allerdings sind bei mir seitliche Mausbewegungen vertauscht.

      • ich verstehe jetzt das Problem mit größer/kleiner 😀

        wärs nicht sinnvoll für Beiträge html zu deaktivieren?

      • Freddy_krueger

        für alle die wissen wollen wie sie die mausbewegung richtig bekommen !
        RotationController.cs

        yaw += (camera.MouseState.X – camera.LastMouseState.X) * rotationSpeed;

        ersetzten mit

        yaw -= (camera.MouseState.X – camera.LastMouseState.X) * rotationSpeed;

      • Welches konkrete Problem hast du denn damit gelöst?

  2. Wieder einmal ein sehr anschaulicher Artikel
    Nicht zu komplex, gut erklärt und die wesentlichen Dinge rausgestellt..
    Diese Methode einer MouseCamera ist wesentlich komfortabler als meine Version, welche ich vorher benutzt habe… beeindruckend.
    Bin schon gespannt was als nächste kommt =) (Warte immernoch sehnlichst auf die erste Konfrontation mit LoD :D)

    mfG Blain

    • Freut mich, dass ich dir „helfen“ konnte 🙂

      LoD wird bald kommen. Der nächste Artikel wird darauf vorbereiten und dann geht’s rund 😉

  3. Ich finde es ein sehr gutes Tutorial, aber wann wolltest du den Fehler verbessern? Du wurdest am 9ten Januar darauf angesprochen 😀

  4. Ich habe außerdem noch Fehler weil es z und x nicht gibt:

    vertices[z * 128 + x] = new VertexPositionColor(new Vector3(x * 10.0f, heights[z * 128 + x].R, z * 10.0f), Color.Green);

    • Das liegt vermutlich daran, dass im Schleifenkopf die Variablen nicht anständig deklariert werden (aufgrund der Tatsache, dass das Syntax Highlighting Plugin Kleiner/Größer-Zeichen ausfiltert).

  5. Was wird das Terrain denn für Schatten bekommen? Ich suche schon länger nach einem guten Tutorial für VSM, hab aber bisher noch nicht so viel gefunden. Vielleicht lässt sich das ja hier einbauen 😀

  6. Hallo,
    very nice Work soweit, auch wenn die Abstände Deiner Tut’s immer länger werden 😉
    In den ganzen Tut’s haben sich Fragen zum Verständniss aufgestaut
    die ich gerne mal loswerden möchte.

    Ich lass 5000 Objekte (einfache Box) anzeigen und schon geht die FPS Anzeige in den Keller.
    (Instanzing außer acht lassen bitte, das verzögert das Problem nur)
    1.) Wieso muss man jedes Objekt mit .Draw zeichnen ? Ich hätte eher erwartet daß
    man die Objekte 1x in den GFX-Speicher bringt und nur noch die POS oder ROT ändern muß.
    Und zwar nur dann wenn sie sich wirklich ändert, stattdessen pumpt man in der Schleife
    ständig die Matrizen und den Draw rein. Kann das ein Shader tun ?
    2.) Aufgefallen ist mir dann aber daß die Update Methode, selbst bei diesen Lags, immer
    noch 60x/s aufgerufen wird. Während die Draw Methode so bei 3 FPS rumkrebst.
    Trotzdem lagt die Abfrage auf die ESC-Taste gehörig. Was stimmt da nicht ?
    3.) Wahrscheinlich habe ich den falschen Ansatz aber durch den Typ „float“, der ja überall in XNA genutzt wird,
    ist es mir nicht möglilch eine Galaxy zu bevölkern welche 10 Milliarden Km groß ist da float diese Zahl nicht von 100 Millionen unterscheiden kann.
    Muss man hier schon LOD nutzen oder gibts ein anderes Schlagwort um das zu lösen ?
    4.) Zu Problem 1, kann man threading auch für die GFX-Geschichten nutzen oder ist XNA dabei nicht „Threadsafe“?
    5.) Bei den Objekten sind einige vor den anderen zu sehen obwohl sie dahinter sein müssten. Wahrscheinlich
    ist das abhängig von der Reihenfolge wie man sie „Drawed“. Aber das kanns nicht sein. Wie kann man abhängig
    von der Kameraposition verhindern dass Objekte durchscheinen ? Oder muss man das bei XNA auch alles selber machen ?
    6.) Was zum Henker denkt sich Microsoft, Version 4 rauszubringen und immer noch nichts für eine GUI getan
    zu haben, ok XNA ist keine Game-Engine, das habe ich mittlerweile entsetzt feststellen müssen.
    Aber das Framework wächst irgendwie nicht wirklich Programmiererfreundlich. Alles muss man selber machen.
    Sei es Physik oder eine GUI. Wenigstens die Integration von FORM im XNA Fenster hätte ich erwartet.
    Ist das ein Grund wieso du NXA entwickelst ? ist das nicht „das Rad 2x erfinden“ ? Wären da nicht
    importierbare Klassen besser die man mit XNA nutzen könnte ?
    7.) Jedes Mesh scheint mehrere Effekte beinhalten zu können. Welche ja alle durch eine Schleife iteriert werden.
    Standardmässig läuft aber immer nur ein Effect durch. Was können da denn noch für welche drin sein ?

    • Zu deinen Fragen:

      1) Nein, es muss nicht jedes einzelne Objekt mit .Draw gezeichnet werden, man sollte das sogar soweit möglich vermeiden. Besser ist es größere Batches zu machen. Beim Würfelbeispiel sollten einige Hundert bis Tausend Würfel auf einmal gerendert werden um eine anständige Geschwindigkeit zu erhalten. Man kann aber nicht mitten im Batch den Shader wechseln und solche Dinge, daher muss man das ausbalancieren.

      2) Die Update-Methode sollte in diesem Fall nicht 60x/s aufgerufen werden. Wie ermittelst du das?

      3) Das ist ein nicht triviales Problem. Eine mögliche Lösung ist z.B. die Aufteilung in Sonnensysteme die so „klein“ sind, dass der Float-Wertebereich ausreichend ist für die Genauigkeit. Man kann dies durch weiteres Unterteilen noch verbessern. Am Float-Wertebereich kommt man aber nicht vorbei und auch Double würde (wenn das von den Grafikkarten ausreichend unterstützt würde, was es meist nicht wird) das Problem nur verlagern, aber nicht lösen.

      4) Der Zugriff auf die Grafikkarte ist erst seit DirectX 11 per Multi-Threading möglich. Da XNA auf DirectX 9 basiert (mit einem DirectX 10 artigen Interface) ist es nicht Thread-Safe. Nur der Main-Thread darf auf das Grafikdevice zugreifen. Das sollte aber für die meisten Fälle ausreichend sein.

      5) Du musst den DepthBuffer aktivieren (graphics.PreferredBackBufferFormat und einen entsprechenden DepthStencilState setzen)

      6) Sowohl GUI, als auch Physik, als auch Scene-Management sind ausserhalb des Scopes von XNA. XNA ist keine Game-Engine, genausowenig wie DirectX oder OpenGL eine Game-Engine wäre… Es gibt aber genügend Libraries die solche Funktionalitäten bieten. Und ANX ist ein XNA-Replacement und wird einen ähnlichen Funktionsumfang haben, näheres dazu im ANX-Artikel hier im Blog.

      7) Jeder Mesh-Part kann einen eigenen Effekt haben, der zum Rendern verwendet wird. Es kann dafür jeder Effekt verwendet werden, der IEffectMatrices implementiert.

      • Huhu nochmal,
        zu 1) kannst du kurz erläutern wie man 2 Models mit einem Draw zeichnen kann ?
        zu 2) wie man auch FPS in Draw ermittelt, ich zähle die Update-Methodenaufrufe die in der gleichen Totalsecond geschehen und geb sie aus.
        zu 5) ich habe nun lange mit graphics.PreferredBackBufferFormat und graphics.PreferredDepthStencilFormat rumgespielt, habe aber kein Unterschied
        bei der Verdeckung erkennen können. Kannst Du Deinen Tip präzisieren ?
        zu 6) Mein Einwand war nur „der Scope ist zu klein um XNA erfolgreicher werden zu lassen“.
        .NET ist auch nicht explizit eine Window-Engine bietet aber Forms an, ohne dies wäre .NET wahrscheinlich gescheitert, so wie XNA am scheitern ist durch seinen geringen Funktionsumfang. Microsoft und Wikipedia nennen XNA eine Technologie zur Spieleentwicklung was sich etwas nach GameEngine anhören könnte. Mich stört auch nur daß MS nichts für die Spieleentwickler mehr in XNA integriert. Auch Libraries die man nutzen könnte sind mir nicht oder nicht lauffähig untergekommen.
        Hast Du eine Quelle dafür ?
        Nochmal zu Deinem ANX. Ich versteh immer noch nicht ganz wieso Du das ganze Rad neu erfinden möchtest anstatt das „Auto“ mit ein paar Funktionen aufzuwerten.

        Lieben Gruß
        gespannt erwarte ich das LOD-Tut.

      • 1) Wenn du die XNA-Model-Klasse meinst: garnicht 🙂 Wenn du einfach ein Model hast (also ein 3D-Objekt), dann geht das relativ einfach wenn du mehrere Models (die einen Effekt und die gleichen Texturen verwenden) in einen Buffer packst.

        2) Das könnte deutlich zu ungenau sein, versuch mal folgendes: http://blogs.msdn.com/b/shawnhar/archive/2007/06/08/displaying-the-framerate.aspx von Shawn Hargreaves (Ex-XNA-Entwickler)

        5) Du musst halt den Depth-Buffer aktivieren. Das passiert normalerweise mit dem Depth24Stencil-DepthStencilFormat und dem DepthStencilState.Default. Du musst halt das DepthStencilState setzen und zwar unmittelbar vor den Draw-Aufrufen.

        6) Der Sinn und Zweck von XNA ist nicht und war auch niemals, dass es eine komplette Game-Engine mit allem drum und dran werden sollte. Es ist eine Low-Level-Bibliothek zur Spieleentwicklung, der es ermöglicht auf unterschiedlichen Plattformen (Phone, PC und XBox) Spiele zu entwickeln und zwar in einer Managed-Sprache. Dieses Ziel wurde voll erreicht und es gibt knapp 100.000 aktive Entwickler und mittlerweile mittlerweile fast 20.000 XNA Spiele die in den unterschiedlichen Markplätzen veröffentlicht wurden. Dazu unzählige Beispiele und Games auf dem PC (dort gibt es keinen offiziellen Marktplatz, es gibt aber ein paar ziemlich erfolgreiche XNA-Games auf Steam). Wenn das nicht erfolgreich ist, dann weis ich es nicht. Wenn du mehr willst, dann schau dir z.B. die Sunburn-Engine an, die ist sehr professionell. Ansonsten gibt es viele Libraries wie Mercury (Partikel), FlatRedBall (alles mögliche), Farseer und starLiGHT.Physik (2D Physik), BePU und andere (3D-Physik) und vieles, vieles mehr. Dazu noch hunderte Samples und Starter-Packs im App Hub direkt von Microsoft aus denen man – wenn man sich damit beschäftigt – alles extrahieren kann um Spiele zu entwickeln (Game-State-Management, Input-Handling, GPU-Partikel, Multi-Player lokal und Netzwerk, Highscore-Listen, Split-Screen, Post-Processing-Effekte). Es ist viel Arbeit, aber das ist Spieleentwicklung immer… Und wenn dir das immer noch nicht reicht, dann musst du tatsächlich zu solchen Paketen wie dem RPG-Maker oder Unity3D greifen. Dort bist du deutlich inflexibler, dafür hast du aber mehr aus einer Hand.

        Ich will nicht das „Rad neu erfinden“, sondern ich liefere eine Source-Code-Kompatible Variante von XNA. Dazu ist es leider notwendig erstmal das XNA-Framework nachzubilden in der gleichen Form. ANX bietet dann aber ein paar Vorteile: Wir haben unterschiedliche Render- (DirectX10, 11, 11.1 und OpenGL), Sound- (DirectSound, XAudio, OpenAL) und Input-Systeme (XInput, DirectInput, Kinect, nativer Input). Das ist schonmal ein großer Vorteil gegenüber XNA und ermöglicht es, dass mit ANX (und damit auch mit XNA entwickelte) viele Spiele ohne Änderungen (Shader sind da eine Ausnahme) auf einer ganzen Reihe von Plattformen lauffähig wird, wie z.B. Linux, MacOS, iOS, Android, Windows 8 (als Metro-Style-App). Weiterhin haben wir damit die Möglichkeit, dass wir erweiterte Funktionen zu bieten, wie z.B. Kinect als Input-System, aber auch Hull- und Domain-Shader, Shader-Modell 5.0, eine optimierte Content-Pipeline, optimierte Basis (z.B. SSE aus Mono, aber auch die Höhere Geschwindigkeit von SharpDX im Render-System). Das alles und noch viel mehr ist mit XNA niemals für jemanden ausserhalb von Microsoft möglich, da XNA nicht Open-Source ist und damit nur erweitert werden kann, nicht aber grundsätzliche Strukturen zu ändern wie das verwendete Render-System.

      • Huhu,
        erstmal Hut ab dass Du antwortest und dass nicht zu knapp.
        zu 1) ein Model aus Blender ist eine XNA-Model-Klasse ?

        zu 2) zu verwirrst mich…Wieso soll das deutlich zu ungenau sein ?
        ich zähle jeden Einsprung der in der gleichen Sekunde passiert.
        Genau das will ich wissen -> UpdateMethodenEinsprünge / sek
        ob das nun 2 mehr oder weniger sind ist mir wurscht.
        Es ging darum dass ich in der Draw Methode durch das ganze
        mesh.draw() der vielen Objekte schon lagge wie ein Elch, nämlich 3fps
        und die Update Methode noch bei 60 ist. Die Steuerung dagegen sich
        aber wie 3 verhält. Wahrscheinlich wird die Update Methode zwar
        mit 60 angesprungen aber die Tastaturabfrage wird nicht in dem Rythmus refreshed.
        Sehr mysteriös.

        zu 3)
        Als Tut.Author lässt Du mich aber trotzdem ganz schön im Regen stehen 🙂
        Du hast etwas viel Basiswissen vorrausgesetzt.
        aber mit :
        depth = new DepthStencilState();
        depth.DepthBufferEnable = true;
        in der game1()
        und :
        GraphicsDevice.DepthStencilState = depth;
        in der Draw() unter dem „GraphicsDevice.Clear(Color.Black);“
        hats dann endlich gefunzt.
        Unmittelbar vor den Draw-Aufrufen wäre er deplaziert gewesen,
        weil er dann für jedes Objekt in der Schleife
        gesetzt worden wäre, das war aber anscheinend nicht nötig.
        Aber selbst hier wunder ich mich wieso ich das Graphicsdevice bei
        jedem Draw-Methoden-Einsprung wieder an die Eigenschaft DepthStencilState
        erinnern muss.

        Nundenn, Deine letzten Sätze haben es begreiflich gemacht. Klar,
        wenn man ein anderes Rendersystem und andere structuren/typen benötigt
        kommt man nicht drumrum XNA zu tauschen.
        Aber Du bist auch echt goldig. Wetterst gegen meine neg. XNA-Aussagen
        wie ein Microsoft Mitarbeiter und unterstreichst sie doch dadurch daß Du XNA
        neu schreibst, weil Du eben genau wie ich nicht recht zufrieden bist mit XNA 😉
        Appropos, an XNA haben doch bestimmt zig MS-Mitarbeiter monatelang gearbeitet.
        Wie willst Du das dieses Leben noch schaffen. Gibts da n Fahrplan ?
        Meine Hochachtung wenn Dir das gelingen sollte, ich hoffe Du implementierst dann
        Vektor3 structuren als double 😉
        Mein Tenor sollte nur sein „Ich mag c# und xna aber ich fühl mich von MS im Stich gelassen“

        Lieben Gruß

      • 1) Wenn du es per ContentPipeline aus einem .FBX oder .X File lädst, dann ja. Wenn du selbst die Vertex- und Index-Buffer generierst, dann nicht. Es geht halt um die Model-Klasse aus XNA, die aber ja nicht die einzige Möglichkeit ist 3D-Objekte zu rendern.

        2) Ich kenne deinen Code ja nicht genau, aber die verlinkte Methode ist eigentlich die Beste und in der Standardeinstellung von XNA kann es normalerweise nicht passieren, dass Update 60x/s aufgerufen wird und Draw nur 3x. Daher ist vermutlich etwas mit deiner Messmethode nicht richtig.

        3) Naja, erstens sind wir noch nicht beim Depth-Buffer und zweitens habe ich dir doch die zwei entscheidenden Tipps im ersten Post gegeben, die aufzeigen was notwendig ist, um den Depth-Buffer zu aktivieren. So komplexes Hintergrundwissen kann ich leider nicht in den paar Zeilen in den Kommentaren vermitteln 😉 Du musst die RenderStates setzen und dann bleiben diese solange bestehen bis sie geändert werden. Geändert werden diese z.B. vom SpriteBatch, von Model.Draw, von Game-Components und vielen anderen Dingen mehr. Daher sollte man diese immer unmittelbar vor dem eigentlichen Draw-Aufruf setzen, damit nichts „dazwischenhaut“. Innerhalb der Schleife macht dies natürlich normalerweise keinen Sinn 😉

        Du hast mich nicht richtig verstanden. Ich finde sowohl C# gut, als auch XNA. Ich bin mit XNA so zufrieden wie es ist, deshalb entwickele ich ANX auch auf Basis von XNA. Ich könnte mir das Leben deutlich leichter machen, wenn ich meine eigene Struktur verwenden würde und nicht XNA „nachbauen“ müsste. Allerdings ist XNA gut durchdesigned, es gibt extrem gute Dokumentation und tonnenweise Beispiele und daher macht es Sinn.

        Trotzdem fehlt mir noch ein bisschen was an XNA. Wenn ich für Windows 8 (Metro-Style), Android, Linux, iOS oder MacOS entwickeln möchte, dann muss ich den Source-Code meiner XNA-Programme in unterschiedliche Richtungen portieren und das ist für einen Indie-Entwickler zeitlich schwer möglich. Daher muss etwas anderes her. Weiterhin steht in den Sternen ob XNA zukünftig noch erste Wahl bei Microsoft sein wird, aktuell scheint es so, als würde die Spieleentwicklung wieder eher verstärkt auf DirectX/C++ setzen. ANX könnte da eine Alternative sein. Die von dir angesprochenen Punkte gehören aber meiner Meinung nicht in Low-Level-Libraries wie XNA, ANX, DirectX, OpenGL, OpenTK, OpenGL.ES, SDL, Allegro oder wie sie alle heißen mögen.

      • Huhu,
        das ist bestimmt wieder Megakompliziert für ein Blendermodel
        die Buffer selber zu generieren 🙂

        Die verlinkte Methode mag die Beste sein, aber Du kennst das mit
        den Kanonen und den Spatzen 🙂 Aber das möchte ich doch nun
        genau wissen. Hier mal ein Beispielcode. Da wirst Du sehen
        daß FPS durch die schleife in den Keller geht während UPS
        oben bleibt. Die ESC-Taste aber sehr träge ist. Nun bin ich gespannt
        wo mein Fehler liegt 😉

        protected override void Update(GameTime gameTime)
        {
        KeyboardState kb = Keyboard.GetState();
        if (kb.IsKeyDown(Keys.Escape)) this.Exit();
        sekunde2 = gameTime.TotalGameTime.Seconds;
        if (sekunde2 != oldsek2)
        {
        oldsek2 = sekunde2;
        ups = updates;
        updates = 0;
        }
        else
        {
        updates += 1;
        }
        base.Update(gameTime);
        }
        protected override void Draw(GameTime gameTime)
        {
        GraphicsDevice.Clear(Color.CornflowerBlue);

        sekunde1 = gameTime.TotalGameTime.Seconds;
        if (sekunde1 != oldsek1)
        {
        oldsek1 = sekunde1;
        fps = frames;
        frames = 0;
        }
        else
        {
        frames += 1;
        }

        for (int i = 0; i < 100000000; i++)
        {

        }
        spriteBatch.Begin();
        spriteBatch.DrawString(font, "FPS:" + fps.ToString() + " UPS:" + ups.ToString(), new Vector2(20, 20), Color.White);
        spriteBatch.End();
        base.Draw(gameTime);
        }

  7. Wirst du diese Tutorial Reihe noch weiterführen?

    • Ja, definitiv, aber nicht hier, sondern unter http://www.indiedev.de.

      Aktuell sind zwei Ableger (für SharpDX und natives DirectX 11) in Arbeit, sowie der nächste Teil, in dem es um Refactoring geht.

      • wie lange dauert es noch bis der nächste teil bei indiedev kommt? Der Teil über das Refactoring besteht nur aus Einleitung?

      • Ja, wohl wahr… Ich denke mal, daß ich die Serie in anderer Form unabhängig von XNA fortsetzen werde, kann aber noch nicht genau sagen wann und wie…

  8. Freddy_krueger

    Was ist die lösung zu diesem fehler ?
    Die Verwendung von Typ “System.Collections.Generic.List” (generisch) macht das 1-Typargument erforderlich.

    Camera.cs

    private List constraints = new List();
    private List controllers = new List();

    • Freddy_krueger

      Gut habe es geschaft ^^

      private List constraints = new List();
      private List controllers = new List();

      geht auf jeden fall ^^

      • Freddy_krueger

        “ private List constraints = new List();
        private List controllers = new List();

    • Oh, da hat der HTML-Filter scheinbar was gelöscht, wird sofort korrigiert. Es sollte eigentlich die generische Version sein…

  9. Hi, finde deine Artikel einfach großartig, einige wenige der deutschsprachigen Texte die wirklich verständlich sind. Hab eine kleine frage, gibt es den nächsten teil schon ? Finde ihn auf mitohneharre.de nicht und auch auf deinem forum indiedev.de find ich nur eine „wird noch bearbeitet“ seite.

    Find ich den einfach nicht oder gibt es den folge artikel noch nicht? Und falls es ihn noch nicht gibt, weiß du schon wann du ihn ca. fertig haben wirst ??

    lg und weiter mit der tollen Arbeit 😉

  10. Bei mir wird das Terrain so wie es im Tut steht nciht gezeichnet. Ersetze ich in Draw

    triangleEffect.Parameters[„View“].SetValue(camera.ViewMatrix);
    triangleEffect.Parameters[„Projection“].SetValue(camera.ProjectionMatrix);

    durch

    triangleEffect.Parameters[„View“].SetValue(Matrix.CreateLookAt(new Vector3(1000, 1000, 1000), Vector3.Zero, Vector3.Up));
    triangleEffect.Parameters[„Projection“].SetValue(Matrix.CreatePerspectiveFieldOfView(MathHelper.ToRadians(45.0f), GraphicsDevice.Viewport.AspectRatio, 0.1f, 10000.0f));

    wird es wieder angezeigt. Hoffe das hilft auch ein paar anderen!

  11. GrafSeismo

    Sehr schade das die Serie grade dann zu ende geht wenn es spannend wird…

  12. gibt es einen Link, wo es mit dem Projekt hier weiter geht?
    Seit zwei Tagen habe ich mir hier reingearbeitet und dazu alles in VB.NET umgeschrieben – und auf einmal ist hier Schluss?!?!

  13. Schade! so ein geiles Tutorial

  1. Pingback: Terrain 101: Neue Sichtweisen « "Mit ohne Haare"

  2. Pingback: Terrain 101: Technische Rafinessen « "Mit ohne Haare"

  3. Pingback: Terrain 101: Vertex- und Index-Buffer « "Mit ohne Haare"

  4. Pingback: Terrain 101: Land in Sicht « "Mit ohne Haare"

  5. Pingback: Terrain 101: Das erste Dreieck « "Mit ohne Haare"

  6. Pingback: Terrain 101: Transformationen « "Mit ohne Haare"

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: