Game Creation with XNA/3D Development/Landscape Modelling

Landscape Modelling

edit

Introduction

edit
 
HeightMap (Source: Wikipedia)

How do we implement and model a landscape which is based on XNA Framework into our game? This WIKI-entry will deal exactly with this problem. By example it will be shown how to create a landscape using a HeightMap. Furthermore we will create a texture, drag it onto our landscape and write loads of source code. Finally there will be some tips regarding topics related to Landscape Modeling.

A HeightMap (Wikipedia: HeightMap) is nothing else than a greymap. So to say a 2D texture which points out the heights and depths of our landscape. Every pixel of the greymap is between 0 and 255 indicating our elevation. To create such a map use a program like Terragen.

Terragen is a program used to create photorealistic landscape-images pretty quick. However it also is a perfect tool to create a HeightMap. Terragen is available in 2 versions (date: 05.06.2011) one version which has to be paid for - Terragen 2 and a free version Terragen Classic. For our needs the free version is perfectly ok.






Creating HeightMap

edit

Enough of the introduction – let’s get started. After downloading and installing Terragen Classic we can see the following menu:

 
Terragen menu.


On the left hand side we can see the buttons provided by Terragen. First step is to click on „Landscape“ and a new window will open up. Here we click on “Size” to adjust the size of our HeightMap – 257x257 or 513x513. Tip: If you already have a skybox implemented, use the size of your skybox image. Next we click on “View/Sculpt” to model our HeightMap. You will see a black picture with a white arrow in it – that’s your camera perspective. You can adjust the perspective as you like by moving the arrow to the desired position. To start painting your terrain you need to click on “Basic Sculpting Tool” (1) located at the top left corner of your window. Now you can start to draw your landscape. Something like this should be the result:

 
Landscape View/Sculp window.


If you are not satisfied with your result you can always click on “Modify” within your landscape window and adjust certain settings like maximum height of your mountains. Another useful function is “Clear/Flatten” which resets your HeightMap so you can start all over again. When you are done painting your HeightMap, click on the button “3D Preview”. This is what it should look like (depending on what you have drawn):

 
3D Preview window of the HeightMap.


To save your HeightMap click on „Export“ in the landscape menu and choose „Raw 8 bits“ as Export Method (1). Click on “Select File and Save…” name your HeightMap and save it to your Hard Drive.

 
Terrain Export window.


We are nearly done with our HeightMap, which is now in .raw format. Finally we need to convert this format into something else by using a program like Photoshop or the free tool “XnView” (www.xnview.de). Change your .raw format to .jpg, .bmp or .png because the “default Content Pipeline” from XNA can handle these formats as “Texture2D”.


Creating Texture

edit

What would our landscape be without texture? Therefore, let’s use Terragen to create one. To do so open the “Rendering Controls” within your Terragen menu.

First thing to do is adjust the size using „Image Size“ (1) depending on whatever size you made your HeightMap (512x512 or 256x256). In the Rendering Control Window, at the bottom right corner, position your camera so you can actually see you floor (2). To directly face the floor use the value -90 for pitch (3). This makes you directly look at your floor. Furthermore set the “Detail” – slider (4) to maximum in order to get the highest quality when rendering. Click on “Render Preview” (5) to get a preview of your texture. Alternatively you can open your “3D Preview” again, but your texture will not be shown rendered.

 
Rendering Control window.


Any black spots on your texture will probably be shadows cast on your terrain. Click on the button „Lightning Conditions“ in the Terragen Menu and uncheck „Terrain Casts Shadows“ and „Clouds Casts Shadows“(1) to make them disappear.

 
Lightning Conditions window.


Now you are done and can click on „Render Image“ (6) in your “Rendering Control”. Terragen now renders your texture which should look something like this:

 
rendered texture


You can also change the colour of your texture. To do so click on the “Landscape” button in your Terragen menu. Choose “Surface Map” (1) and click on “Edit” (2). The “Surface Layer” window will open up. Now click „Colour…“(3) to choose your colour. When you are satisfied with your texture save it to your Hard Drive.

 
Change the texture colour.


Play around with the settings, render it and check the changes. If you choose the colour to white, this is what your texture should look like:

 
Texture with a different colour.


Now we are done with the basics and finally reached our first goal – our own HeightMap and texture:


Implementation in XNA

edit

From now on we start working on implementing the HeightMap and the texture into XNA code. But to actually see something we need to start by programming a camera.


Creating Camera Class

edit

We create a new Project in Visual Studio 2008 and add a new class named „Camera“.

We start of by assigning some class variables. A matrix viewMatrix for the camera view and a projectionMatrix for the projection. The projectionMatrix converts the 3D camera view into a 2D image. To position our landscape later on, we will need another matrix terrainMatrix. Furthermore it would be nice if we could move or rotate our camera over our landscape. Therefore we declare Vector3 variables for position, alignment, movement and rotation of our camera.

        // matrix for camera view and projection
        Matrix viewMatrix;
        Matrix projectionMatrix;

        // world matrix for our landscape
        public Matrix terrainMatrix;

        // actual camera position, direction, movement, rotation
        Vector3 position;
        Vector3 direction;
        Vector3 movement;
        Vector3 rotation;


The camera constructor gets parameters to initialize all these variables.

 
        public Camera(Vector3 position, Vector3 direction, Vector3 movement, Vector3 landscapePosition)
        {
            this.position = position;
            this.direction = direction;
            this.movement = movement;
            rotation = movement*0.02f;
            //camera position, view of camera, see what is over camera
            viewMatrix = Matrix.CreateLookAt(position, direction, Vector3.Up);
            //width and height of camera near plane, range of camera far plane (1-1000)
            projectionMatrix = Matrix.CreatePerspective(1.2f, 0.9f, 1.0f, 1000.0f);
            // positioning our landscape in camera start position
            terrainMatrix = Matrix.CreateTranslation(landscapePosition);
        }


Now if you ask yourself what exactly the methods CreateLookAt(), CreatePerspective(), CreateTranslation() are doing, check the class library of XNA Framework -> XNA Framework Class Library Reference. All methods are clearly described there. Keep the XNA Framework class library in mind to check all the methods unclear to you, because not all methods used in the source code will be explained in detail.

To exercise this at least once we use the method CreateTranslation(). Go to Matrix.CreatePerspective Method (Single, Single, Single, Single) and you will find a detailed description of all the parameters used by the method as well as their return values.

 
Parameters and return value CreatePerspective() method


Back to our camera class. Next step is to create an Update() method which will get a number as parameter. In this method we define the movement and rotation of our camera and calculate our new camera position at the end. We do that because when we create a camera in our Game1.cs later on, we can move our camera by using keyboard inputs. Every keyboard input sends a number which will be processed by the camera’s Update() method.

 
        public void Update(int number)
        {
            Vector3 tempMovement = Vector3.Zero;
            Vector3 tempRotation = Vector3.Zero;
            //left
            if (number == 1)
            {
                tempMovement.X = +movement.X;
            }
            //right
            if (number == 2)
            {
                tempMovement.X = -movement.X;
            }
            //up
            if (number == 3)
            {
                tempMovement.Y = -movement.Y;
            }
            //down
            if (number == 4)
            {
                tempMovement.Y = +movement.Y;
            }
            //backward (zoomOut)
            if (number == 5)
            {
                tempMovement.Z = -movement.Z;
            }
            //forward (zoomIn)
            if (number == 6)
            {
                tempMovement.Z = +movement.Z;
            }
            //left rotation
            if (number == 7)
            {
                tempRotation.Y = -rotation.Y;
            }
            //right rotation
            if (number == 8)
            {
                tempRotation.Y = +rotation.Y;
            }
            //forward rotation
            if (number == 9)
            {
                tempRotation.X = -rotation.X;
            }
            //backward rotation
            if (number == 10)
            {
                tempRotation.X = +rotation.X;
            }

            //move camera to new position
            viewMatrix = viewMatrix * Matrix.CreateRotationX(tempRotation.X) * Matrix.CreateRotationY(tempRotation.Y) * Matrix.CreateTranslation(tempMovement);
            //update position
            position += tempMovement;
            direction += tempRotation;
        }


Finally our camera gets a Draw() method. In this method we pass our landscape to ensure it gets displayed later on.

 
        public void Draw(Terrain terrain)
        {
            terrain.basicEffect.Begin();
            SetEffects(terrain.basicEffect);
            foreach (EffectPass pass in terrain.basicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                terrain.Draw();
                pass.End();
            }
            terrain.basicEffect.End();
        }


Before we can start to write our Terrain.cs class we need to implement the method SetEffects() which is used by the Draw() method. BasicEffect is a class in XNA Framework which provides rendering effects to display objects.

        public void SetEffects(BasicEffect basicEffect)
        {
            basicEffect.View = viewMatrix;
            basicEffect.Projection = projectionMatrix;
            basicEffect.World = terrainMatrix;
        }


Now our Camera.cs class is ready and to actually see something we now start to write our Terrain.cs class.


Overview Camera.cs class

edit

This is how the complete Camera.cs class should look like.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace WindowsGame1
{
    class Camera
    {
        // matrix for camera view and projection
        Matrix viewMatrix;
        Matrix projectionMatrix;

        // world matrix for our landscape
        public Matrix terrainMatrix;

        // actual camera position, direction, movement, rotation
        Vector3 position;
        Vector3 direction;
        Vector3 movement;
        Vector3 rotation;
        
        public Camera(Vector3 position, Vector3 direction, Vector3 movement, Vector3 landscapePosition)
        {
            this.position = position;
            this.direction = direction;
            this.movement = movement;
            rotation = movement*0.02f;
            //camera position, view of camera, see what is over camera
            viewMatrix = Matrix.CreateLookAt(position, direction, Vector3.Up);
            //width and height of camera near plane, range of camera far plane (1-1000)
            projectionMatrix = Matrix.CreatePerspective(1.2f, 0.9f, 1.0f, 1000.0f);
            // positioning our landscape in camera start position
            terrainMatrix = Matrix.CreateTranslation(landscapePosition);
        }

        public void Update(int number)
        {
            Vector3 tempMovement = Vector3.Zero;
            Vector3 tempRotation = Vector3.Zero;
            //left
            if (number == 1)
            {
                tempMovement.X = +movement.X;
            }
            //right
            if (number == 2)
            {
                tempMovement.X = -movement.X;
            }
            //up
            if (number == 3)
            {
                tempMovement.Y = -movement.Y;
            }
            //down
            if (number == 4)
            {
                tempMovement.Y = +movement.Y;
            }
            //backward (zoomOut)
            if (number == 5)
            {
                tempMovement.Z = -movement.Z;
            }
            //forward (zoomIn)
            if (number == 6)
            {
                tempMovement.Z = +movement.Z;
            }
            //left rotation
            if (number == 7)
            {
                tempRotation.Y = -rotation.Y;
            }
            //right rotation
            if (number == 8)
            {
                tempRotation.Y = +rotation.Y;
            }
            //forward rotation
            if (number == 9)
            {
                tempRotation.X = -rotation.X;
            }
            //backward rotation
            if (number == 10)
            {
                tempRotation.X = +rotation.X;
            }

            //move camera to new position
            viewMatrix = viewMatrix * Matrix.CreateRotationX(tempRotation.X) * Matrix.CreateRotationY(tempRotation.Y) * Matrix.CreateTranslation(tempMovement);
            //update position
            position += tempMovement;
            direction += tempRotation;
        }

        public void SetEffects(BasicEffect basicEffect)
        {
            basicEffect.View = viewMatrix;
            basicEffect.Projection = projectionMatrix;
            basicEffect.World = terrainMatrix;
        }
                
        public void Draw(Terrain terrain)
        {
            terrain.basicEffect.Begin();
            SetEffects(terrain.basicEffect);
            foreach (EffectPass pass in terrain.basicEffect.CurrentTechnique.Passes)
            {
                pass.Begin();
                terrain.Draw();
                pass.End();
            }
            terrain.basicEffect.End();
        }
    }
}

Creating Landscape Class

edit

Create a new class and rename it Terrain.cs. Again we start by defining class variables we will need. We will need Texture2D variables for our HeightMap and our texture image as well as variables to work with the textures, especially arrays.

        GraphicsDevice graphicsDevice;

        // heightMap
        Texture2D heightMap;
        Texture2D heightMapTexture;
        VertexPositionTexture[] vertices;
        int width; 
        int height;

        public BasicEffect basicEffect;
        int[] indices;

        // array to read heightMap data
        float[,] heightMapData;


In the constructor of our Terrain.cs we call the GraphicsDevice unit in order to be able to access it in our class.

        public Terrain(GraphicsDevice graphicsDevice)
        {
            this.graphicsDevice = graphicsDevice;
        }


Now we create a method which will get our textures (this will happen from the Game1.cs class and will be explained later) and calls other methods so we get closer to our landscape. So let’s write the missing methods.

        public void SetHeightMapData(Texture2D heightMap, Texture2D heightMapTexture)
        {
            this.heightMap = heightMap;
            this.heightMapTexture = heightMapTexture;
            width = heightMap.Width;
            height = heightMap.Height;
            SetHeights();
            SetVertices();
            SetIndices();
            SetEffects();
        }


We start by implementing the SetHeight() method which will get the greyscale from each pixel of the texture, indicating its actual height, and writes them into the heightMapData[] array. The complete method:

public void SetHeights()
        {
            Color[] greyValues = new Color[width * height];
            heightMap.GetData(greyValues);
            heightMapData = new float[width, height];
            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    heightMapData[x, y] = greyValues[x + y * width].G / 3.1f;
                }
            }
        }


To get the intensity of each greyscale it is suffice to get the value of a single colour, either red, green or blue – which one you choose is up to you. To not get to much difference in altitude you can divide your colourvalue by a value. Hence this line:

heightMapData[x, y] = greyValues[x + y * width].G / 3.1f;


It also works the other way around. When you multipliy with a value you will get a higher difference in altitude.

The next two methods deal with the creation of indices and vertices. SetVertice() creates the area of our landscape using triangles. An area consist of two triangles. A triangle can be described by 3 numbers which are called indices. These indices of a triangle are assigned to vertices. If you need a refreshment in that matter go check Riemer’s XNA Tutorials -> Recycling vertices using inidices.

In our method some strange mathematical stuff is used to calculate correct indices. Play around a bit and check out what happens when you change certain values.

 
        public void SetIndices()
        {
            // amount of triangles
            index = new int[6 * (width - 1) * (height - 1)];
            int number = 0;
            // collect data for corners
            for (int y = 0; y < height - 1; y++)
                for (int x = 0; x < width - 1; x++)
                {
                    // create double triangles
                    index[number] = x + (y + 1) * width;      // up left
                    index[number + 1] = x + y * width + 1;        // down right
                    index[number + 2] = x + y * width;            // down left
                    index[number + 3] = x + (y + 1) * width;      // up left
                    index[number + 4] = x + (y + 1) * width + 1;  // up right
                    index[number + 5] = x + y * width + 1;        // down right
                    number += 6;
                }
        }


The SetVertices() method calcualtes the 2D-position for each vertex the texture should be applied. The heights and depths will be assigned using the data from the heightMapData[] array.

public void SetVertices()
        {
            vertices = new VertexPositionTexture[width * height];
            Vector2 texturePosition;
            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    texturePosition = new Vector2((float)x / 25.5f, (float)y / 25.5f);
                    vertices[x + y * width] = new VertexPositionTexture(new Vector3(x, heightMapData[x, y], -y), texturePosition);
                }
                graphicsDevice.VertexDeclaration = new VertexDeclaration(graphicsDevice, VertexPositionTexture.VertexElements);
            }
        }


Now we implement a SetEffects() method in which we use a new shader object of type BasicEffet (Wikipedia: Shader). Its texture properties get assigned to our terrain texture and its display gets activated.

 
        public void SetEffects()
        {
            basicEffect = new BasicEffect(graphicsDevice, null);
            basicEffect.Texture = heightMapTexture;
            basicEffect.TextureEnabled = true;
        }


To actually draw the landscape our terrain.cs class gets an own Draw() method. From here we call the method DrawUserIndexedPrimitives()(from GraphicsDevice class from XNA) which is extremely powerful and contains a pretty long list of parameters. First the type of object that is to be drawn. A collection of triangles is meant when using TriangleList. Followed by our array containing the vertices. The next parameters take the starting point and the ammount of our vertices. Next is the array with our indices and at the end the number of the first triangle and the ammount of triangles.

        public void Draw()
        {
           graphicsDevice.DrawUserIndexedPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, indices.Length / 3);
        }


Last but not least we need to adjust our Game1.cs in which we now call our camera and our terrain to reach or goal to see our landscape.


Overview Terrain.cs class

edit

Prior to that an overview of the complete Terrain.cs class:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;


namespace WindowsGame1
{
    public class Terrain
    {
        GraphicsDevice graphicsDevice;

        // heightMap
        Texture2D heightMap;
        Texture2D heightMapTexture;
        VertexPositionTexture[] vertices;
        int width; 
        int height;

        public BasicEffect basicEffect;
        int[] indices;

        // array to read heightMap data
        float[,] heightMapData;
        


        public Terrain(GraphicsDevice graphicsDevice)
        {
            this.graphicsDevice = graphicsDevice;
        }

        public void SetHeightMapData(Texture2D heightMap, Texture2D heightMapTexture)
        {
            this.heightMap = heightMap;
            this.heightMapTexture = heightMapTexture;
            width = heightMap.Width;
            height = heightMap.Height;
            SetHeights();
            SetVertices();
            SetIndices();
            SetEffects();
        }

        public void SetHeights()
        {
            Color[] greyValues = new Color[width * height];
            heightMap.GetData(greyValues);
            heightMapData = new float[width, height];
            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    heightMapData[x, y] = greyValues[x + y * width].G / 3.1f;
                }
            }
        }

        public void SetIndices()
        {
            // amount of triangles
            indices = new int[6 * (width - 1) * (height - 1)];
            int number = 0;
            // collect data for corners
            for (int y = 0; y < height - 1; y++)
                for (int x = 0; x < width - 1; x++)
                {
                    // create double triangles
                    indices[number] = x + (y + 1) * width;      // up left
                    indices[number + 1] = x + y * width + 1;        // down right
                    indices[number + 2] = x + y * width;            // down left
                    indices[number + 3] = x + (y + 1) * width;      // up left
                    indices[number + 4] = x + (y + 1) * width + 1;  // up right
                    indices[number + 5] = x + y * width + 1;        // down right
                    number += 6;
                }
        }

        public void SetVertices()
        {
            vertices = new VertexPositionTexture[width * height];
            Vector2 texturePosition;
            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    texturePosition = new Vector2((float)x / 25.5f, (float)y / 25.5f);
                    vertices[x + y * width] = new VertexPositionTexture(new Vector3(x, heightMapData[x, y], -y), texturePosition);
                }
                graphicsDevice.VertexDeclaration = new VertexDeclaration(graphicsDevice, VertexPositionTexture.VertexElements);
            }
        }
        
       

        public void SetEffects()
        {
            basicEffect = new BasicEffect(graphicsDevice, null);
            basicEffect.Texture = heightMapTexture;
            basicEffect.TextureEnabled = true;
        }

        public void Draw()
        {
           graphicsDevice.DrawUserIndexedPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, indices.Length / 3);
        }
        
    }
}


Adjusting Game1.cs class

edit

Before we start we import our HeightMap as well as our texture image into VisualStudio2008. Right click on content in your project-explorer. Choose "Add" –> "existing Element…" in the menu popping up. Choose your images and import them. You now should see your HeightMap and your texture image listed under Content. Now create your camera and your terrain as class variables.

        //-------------CAMERA------------------
        Camera camera;

        //-------------TERRAIN-----------------
        Terrain landscape;


To let VisualStudio2008 know where to find your images, add the following line to the constructor:

Content.RootDirectory = "Content";


Next initialize your camera and your terrain using the Initialize() method.

            // initialize camera start position
            camera = new Camera(new Vector3(-100, 0, 0), Vector3.Zero, new Vector3(2, 2, 2), new Vector3(0, -100, 256));
                   
            // initialize terrain
            landscape = new Terrain(GraphicsDevice);


If you later dont see anything you might need to adjust your Vector3 vectors which are passed into the camera class.

The following line from the LoadContent() method is used to load the HeightMap and texture image into your terrain class:

            //load heightMap and heightMapTexture to create landscape
           landscape.SetHeightMapData(Content.Load<Texture2D>("heightMap"), Content.Load<Texture2D>("heightMapTexture"));


Because we programmed our camera class forward-looking and want to move our camera over our terrain, we simply need to define the keys for movement in our Update() method.

// move camera position with keyboard
            KeyboardState key = Keyboard.GetState();
            if (key.IsKeyDown(Keys.A))
            {
                camera.Update(1);
            }
            if (key.IsKeyDown(Keys.D))
            {
                camera.Update(2);
            }
            if (key.IsKeyDown(Keys.W)) 
            { 
                camera.Update(3); 
            }
            if (key.IsKeyDown(Keys.S))
            {
                camera.Update(4);
            }
            if (key.IsKeyDown(Keys.F))
            {
                camera.Update(5);
            }
            if (key.IsKeyDown(Keys.R))
            {
                camera.Update(6);
            }
            if (key.IsKeyDown(Keys.Q))
            {
                camera.Update(7);
            }
            if (key.IsKeyDown(Keys.E))
            {
                camera.Update(8);
            }
            if (key.IsKeyDown(Keys.G))
            {
                camera.Update(9);
            }
            if (key.IsKeyDown(Keys.T))
            {
                camera.Update(10);
            }


Last but not least we need to tell the camera’s Draw() method to draw our landscape.

            // to get landscape viewable
            camera.Draw(landscape);


Overview Game1.cs class

edit
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace WindowsGame1
{
    /// <summary>
    /// This is the main type for your game
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;

        //-------------CAMERA------------------
        Camera camera;

        //-------------TERRAIN-----------------
        Terrain landscape;

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

 
        protected override void Initialize()
        {
            // initialize camera start position
            camera = new Camera(new Vector3(-100, 0, 0), Vector3.Zero, new Vector3(2, 2, 2), new Vector3(0, -100, 256));
            
            // initialize terrain
            landscape = new Terrain(GraphicsDevice);

            base.Initialize();
        }

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

            //load heightMap and heightMapTexture to create landscape
           landscape.SetHeightMapData(Content.Load<Texture2D>("heightMap"), Content.Load<Texture2D>("heightMapTexture"));
        }
   
        protected override void Update(GameTime gameTime)
        {
            // move camera position with keyboard
            KeyboardState key = Keyboard.GetState();
            if (key.IsKeyDown(Keys.A))
            {
                camera.Update(1);
            }
            if (key.IsKeyDown(Keys.D))
            {
                camera.Update(2);
            }
            if (key.IsKeyDown(Keys.W)) 
            { 
                camera.Update(3); 
            }
            if (key.IsKeyDown(Keys.S))
            {
                camera.Update(4);
            }
            if (key.IsKeyDown(Keys.F))
            {
                camera.Update(5);
            }
            if (key.IsKeyDown(Keys.R))
            {
                camera.Update(6);
            }
            if (key.IsKeyDown(Keys.Q))
            {
                camera.Update(7);
            }
            if (key.IsKeyDown(Keys.E))
            {
                camera.Update(8);
            }
            if (key.IsKeyDown(Keys.G))
            {
                camera.Update(9);
            }
            if (key.IsKeyDown(Keys.T))
            {
                camera.Update(10);
            }
            base.Update(gameTime);
        }

        
        protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.Clear(Color.CornflowerBlue); 
            // to get landscape viewable
            camera.Draw(landscape);
    
            base.Draw(gameTime);
        }
    }
}


Congratualtions – we are done.

As result of your work you should now see your landscape with your HeightMap and your texture generated by the Debugger. Furthermore you can move your camera over your terrain to confirm to really have heights and depths.


If you are interested in how your landscape looks like as a grid of triangles, go to the SetEffects() method of your Terrain.cs class and modify it like this :

 
        public void SetEffects()
        {
            basicEffect = new BasicEffect(graphicsDevice, null);
            basicEffect.Texture = heightMapTexture;
            basicEffect.TextureEnabled = false;
            graphicsDevice.RenderState.FillMode = FillMode.WireFrame;
        }



Now you can easily replace your whole landscape by simply using a different HeightMap. Same applies for the texture. Just use the new names of your new images as parameters in the SetHeightMapData() method in your Terrain.cs class.

 
landscape.SetHeightMapData(Content.Load<Texture2D>("heightMap"), Content.Load<Texture2D>("heightMapTexture"));


edit

Unfortunately the Basic-Shader from XNA (BasicEffect) can handle only one texture. To improve your landscape you could now write your own EffectShader file which would handle more than one texture. If you are interested in shaders check Game Creation with XNA/3D Development/Shaders and Effects. You could make your landscape more interesting using multitexturing.

It is also possible to create a landscape using a 3D Modeling Software and import it as .x or .fbx file. Doing so will require more CPU-Power and knowledge of 3D Modeling Software though. Check Game Creation with XNA/3D Development/Importing Models.

Another really complex topic would be collision detection for an object moving on the surface of your landscape. Check Game Creation with XNA/Mathematics Physics/Collision Detection. A short introduction using the image below.

 
Interpolation


The blue circle is the object (maybe your game character). This object always has to request the y-position of you landscape for the direction it is moving in (green line). To get a smooth movement when the altitude of your landscape changes you need to interpolate (Wikipedia: Interpolate) the y-value of your object’s vector at its current position with the new y-value of your landscape’s vector (the destination). The y-value of the landscape in the image changes from 15 to 23.

You can find more on this topic and some code here:

Collision Series 4: Collision with a Heightmap

Collision Series 5: Heightmap Collision with Normals


Links

edit


Literature

edit
  • Microsoft XNA Game Studio 3.0, Chad Carter
  • Microsoft XNA Game Studio Creator’s Guide Second Edition, S. Cawood and P. McGee
  • Spieleprogrammierung mit dem XNA Framework, Hans-Georg Schumann


Authors

edit

RayIncarnation