跳轉到內容

使用 XNA 建立遊戲/3D 開發/地形建模

來自 Wikibooks,開放世界中的開放書籍

地形建模

[編輯 | 編輯原始碼]
高度圖(來源:維基百科)

我們如何將基於 XNA 框架的地形實現並建模到我們的遊戲中?本維基條目將專門解決這個問題。例如,將展示如何使用高度圖建立地形。此外,我們將建立紋理,將其拖放到我們的地形上,並編寫大量原始碼。最後,將提供一些關於地形建模相關主題的技巧。

高度圖(維基百科:高度圖)只不過是灰度圖。也就是說,一個 2D 紋理,它指出我們地形的高度和深度。灰度圖的每個畫素都在 0 到 255 之間,表示我們的海拔高度。要建立這樣的地圖,請使用 Terragen 之類的程式。

Terragen 是一個用於快速建立逼真地形影像的程式。但是,它也是建立高度圖的完美工具。Terragen 有兩個版本(日期:2011 年 06 月 05 日),一個需要付費的版本 - Terragen 2 和一個免費版本 Terragen Classic。對於我們的需求,免費版本已經足夠了。






建立高度圖

[編輯 | 編輯原始碼]

介紹就到這裡 - 讓我們開始吧。下載並安裝 Terragen Classic 後,我們可以看到以下選單

Terragen 選單。


在左側,我們可以看到 Terragen 提供的按鈕。第一步是單擊“地形”,一個新視窗將開啟。在這裡,我們單擊“大小”來調整高度圖的大小 - 257x257 或 513x513。提示:如果您已經實現了天空盒,請使用天空盒影像的大小。接下來,我們單擊“檢視/雕刻”來建模我們的高度圖。您將看到一張帶有白色箭頭的黑色圖片 - 這是您的相機視角。您可以透過將箭頭移動到所需位置來根據需要調整視角。要開始繪製地形,您需要單擊位於視窗左上角的“基本雕刻工具”(1)。現在您可以開始繪製地形了。結果應該類似於此

地形檢視/雕刻視窗。


如果您對結果不滿意,您始終可以單擊地形視窗中的“修改”並調整某些設定,例如山脈的最大高度。另一個有用的功能是“清除/平坦化”,它可以重置您的高度圖,這樣您就可以重新開始。完成繪製高度圖後,單擊“3D 預覽”按鈕。它應該看起來像這樣(取決於您所繪製的內容)

高度圖的 3D 預覽視窗。


要儲存您的高度圖,請單擊地形選單中的“匯出”,然後選擇“原始 8 位”作為匯出方法(1)。單擊“選擇檔案並儲存……”,命名您的高度圖並將其儲存到您的硬碟驅動器。

地形匯出視窗。


我們幾乎完成了高度圖,它現在以 .raw 格式。最後,我們需要使用 Photoshop 之類的程式或“XnView”(www.xnview.de) 等免費工具將這種格式轉換為其他格式。將您的 .raw 格式更改為 .jpg、.bmp 或 .png,因為 XNA 的“預設內容管道”可以將這些格式處理為“Texture2D”。


建立紋理

[編輯 | 編輯原始碼]

沒有紋理,我們的地形會是什麼樣的?因此,讓我們使用 Terragen 來建立一個紋理。為此,請在您的 Terragen 選單中開啟“渲染控制”。

首先要做的是使用“影像大小”(1)調整大小,具體取決於您建立高度圖的大小(512x512 或 256x256)。在渲染控制視窗的右下角,定位您的相機,以便您實際看到地板(2)。要直接面對地板,請將俯仰(3)的值設定為 -90。這會讓您直接看著地板。此外,將“細節”滑塊(4)設定為最大值,以便在渲染時獲得最高質量。單擊“渲染預覽”(5)以預覽您的紋理。或者,您可以再次開啟“3D 預覽”,但您的紋理不會顯示渲染。

渲染控制視窗。


紋理上的任何黑點可能是投射到地形上的陰影。單擊 Terragen 選單中的“照明條件”按鈕,然後取消選中“地形投射陰影”和“雲投射陰影”(1)以使它們消失。

照明條件視窗。


現在您已經完成了,可以單擊“渲染影像”(6)在您的“渲染控制”中。Terragen 現在將渲染您的紋理,它應該看起來像這樣

渲染的紋理


您也可以更改紋理的顏色。為此,請單擊 Terragen 選單中的“地形”按鈕。選擇“表面地圖”(1),然後單擊“編輯”(2)。“表面層”視窗將開啟。現在單擊“顏色...”(3)選擇您的顏色。當您對紋理滿意後,將其儲存到您的硬碟驅動器。

更改紋理顏色。


嘗試不同的設定,渲染它並檢查更改。如果您選擇白色作為顏色,您的紋理應該看起來像這樣

具有不同顏色的紋理。


現在我們完成了基礎部分,最終實現了我們的第一個目標 - 我們自己的高度圖和紋理


在 XNA 中的實現

[編輯 | 編輯原始碼]

從現在開始,我們將開始將高度圖和紋理實現到 XNA 程式碼中。但要實際看到一些東西,我們需要從程式設計一個相機開始。


建立相機類

[編輯 | 編輯原始碼]

我們在 Visual Studio 2008 中建立一個新專案,並新增一個名為“Camera”的新類。

我們首先分配一些類變數。一個用於相機檢視的矩陣 viewMatrix 和一個用於投影的 projectionMatrix。projectionMatrix 將 3D 相機檢視轉換為 2D 影像。為了以後定位地形,我們將需要另一個矩陣 terrainMatrix。此外,如果我們可以在地形上移動或旋轉相機,那就太好了。因此,我們為相機的位置、對齊、移動和旋轉宣告 Vector3 變數。

        // 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);
        }


現在,如果您想知道 CreateLookAt()、CreatePerspective()、CreateTranslation() 究竟做了什麼,請檢視 XNA 框架的類庫 -> XNA 框架類庫參考。所有方法都在其中清楚地描述。請記住 XNA 框架類庫,以檢查您不清楚的所有方法,因為原始碼中使用的並非所有方法都將詳細解釋。

為了至少練習一次,我們使用 CreateTranslation() 方法。轉到 Matrix.CreatePerspective 方法 (Single, Single, Single, Single),您將找到對方法使用的所有引數以及它們的返回值的詳細描述。

CreatePerspective() 方法的引數和返回值


回到我們的相機類。下一步是建立一個 Update() 方法,它將獲取一個數字作為引數。在此方法中,我們定義了相機的移動和旋轉,並在最後計算了新的相機位置。我們這樣做是因為,當我們稍後在 Game1.cs 中建立一個相機時,我們可以使用鍵盤輸入來移動相機。每個鍵盤輸入都會發送一個數字,該數字將由相機的 Update() 方法處理。

 
        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;
        }


最後,我們的相機獲得一個 Draw() 方法。在此方法中,我們傳遞地形,以確保它稍後顯示。

 
        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();
        }


在我們開始編寫 Terrain.cs 類之前,我們需要實現 Draw() 方法使用的 SetEffects() 方法。BasicEffect 是 XNA 框架中的一個類,它提供渲染效果來顯示物件。

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


現在,我們的 Camera.cs 類已經準備好了,為了實際看到一些東西,我們現在開始編寫 Terrain.cs 類。


Camera.cs 類概述

[編輯 | 編輯原始碼]

完整的 Camera.cs 類應該如下所示。

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();
        }
    }
}

建立地形類

[編輯 | 編輯原始碼]

建立一個新類並將其重新命名為 Terrain.cs。我們首先定義所需的類變數。我們需要 Texture2D 變數來儲存我們的高度圖和紋理影像,以及用於處理紋理的變數,特別是陣列。

        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;


在我們的 Terrain.cs 的建構函式中,我們呼叫 GraphicsDevice 單位以便能夠在我們的類中訪問它。

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


現在我們建立一個方法,該方法將獲取我們的紋理(這將來自 Game1.cs 類,稍後解釋)並呼叫其他方法,以便我們更接近我們的地形。所以讓我們編寫缺少的方法。

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


我們首先實現 SetHeight() 方法,該方法將從紋理的每個畫素獲取灰度值,表示其實際高度,並將它們寫入 heightMapData[] 陣列中。完整方法

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;
                }
            }
        }


要獲取每個灰度的強度,只需獲取單個顏色的值,可以是紅色、綠色或藍色 - 您選擇哪一個都可以。為了避免高度相差太大,您可以將顏色值除以一個值。因此,這行程式碼

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


它也可以反過來。當您乘以一個值時,您將獲得更高的海拔差異。

接下來的兩個方法處理索引和頂點的建立。SetVertice() 使用三角形建立我們的地形區域。一個區域由兩個三角形組成。三角形可以透過 3 個數字來描述,這些數字稱為索引。這些三角形的索引被分配給頂點。如果您需要對此進行復習,請檢視 Riemer 的 XNA 教程 -> 使用索引回收頂點.

在我們的方法中,使用了一些奇怪的數學運算來計算正確的索引。稍微試著調整一下,看看改變某些值會發生什麼。

 
        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;
                }
        }


SetVertices() 方法計算紋理應應用於每個頂點的 2D 位置。高度和深度將使用來自 heightMapData[] 陣列的資料進行分配。

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);
            }
        }


現在我們在其中使用一個新的 BasicEffet 型別(維基百科:著色器)的著色器物件來實現 SetEffects() 方法。它的紋理屬性被分配到我們的地形紋理,並且它的顯示被啟用。

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


為了實際繪製地形,我們的 terrain.cs 類獲得了一個自己的 Draw() 方法。從這裡,我們呼叫 DrawUserIndexedPrimitives() 方法(來自 XNA 的 GraphicsDevice 類),該方法非常強大,包含一個很長的引數列表。首先是將要繪製的物件型別。使用 TriangleList 時,表示三角形集合。後面是我們的包含頂點的陣列。接下來的引數接受我們的頂點的起點和數量。接下來是包含我們索引的陣列,最後是第一個三角形的數量和三角形的數量。

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


最後但並非最不重要的是,我們需要調整我們的 Game1.cs,在其中我們現在呼叫我們的相機和地形來實現我們的目標,即檢視我們的地形。


概述 Terrain.cs 類

[編輯 | 編輯原始碼]

在此之前,完整 Terrain.cs 類的概述

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);
        }
        
    }
}


調整 Game1.cs 類

[編輯 | 編輯原始碼]

在我們開始之前,我們將我們的高度圖以及我們的紋理影像匯入 VisualStudio2008。右鍵單擊專案資源管理器中的內容。在彈出的選單中選擇“新增” -> “現有元素...”。選擇您的影像並匯入它們。現在您應該在“內容”下看到您的高度圖和紋理影像。現在建立您的相機和地形作為類變數。

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

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


為了讓 VisualStudio2008 知道在哪裡找到您的影像,在建構函式中新增以下行

Content.RootDirectory = "Content";


接下來使用 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);


如果您後來什麼也看不到,您可能需要調整傳遞到相機類的 Vector3 向量。

LoadContent() 方法中的以下行用於將高度圖和紋理影像載入到您的地形類中

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


因為我們將相機類程式設計為向前看,並且想要將相機移動到我們的地形上,所以我們只需要在 Update() 方法中定義移動鍵即可。

// 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);
            }


最後但並非最不重要的是,我們需要告訴相機的 Draw() 方法繪製我們的地形。

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


概述 Game1.cs 類

[編輯 | 編輯原始碼]
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);
        }
    }
}


恭喜- 我們完成了。

作為您的工作的結果,您現在應該看到您的地形,其高度圖和紋理由偵錯程式生成。此外,您可以將相機移動到您的地形上,以確認您確實擁有高度和深度。


如果您想知道您的地形作為三角形網格的樣子,請轉到 Terrain.cs 類的 SetEffects() 方法,並像這樣修改它

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



現在您可以透過簡單地使用不同的高度圖輕鬆替換整個地形。紋理也是如此。只需在 Terrain.cs 類中的 SetHeightMapData() 方法中使用新影像的新名稱作為引數。

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


[編輯 | 編輯原始碼]

不幸的是,XNA 的基本著色器 (BasicEffect) 只能處理一種紋理。為了改進您的地形,您現在可以編寫自己的 EffectShader 檔案,該檔案可以處理多種紋理。如果您對著色器感興趣,請檢視 使用 XNA 建立遊戲/3D 開發/著色器和效果。您可以使用多紋理使您的地形更有趣。

也可以使用 3D 建模軟體建立地形,並將其作為 .x 或 .fbx 檔案匯入。這樣做將需要更多的 CPU 能力和 3D 建模軟體的知識。檢視 使用 XNA 建立遊戲/3D 開發/匯入模型.

另一個非常複雜的主題是檢測在您地形表面移動的物件的碰撞。檢視 使用 XNA 建立遊戲/數學物理/碰撞檢測。使用下圖進行簡要介紹。

插值


藍色圓圈是物件(可能是您的遊戲角色)。該物件必須始終請求您地形在其移動方向上的 y 位置(綠色線)。為了在您的地形海拔髮生變化時獲得平滑的移動,您需要對您物件向量的當前位置的 y 值與您地形向量的新的 y 值(目的地)進行插值(維基百科:插值)。影像中地形的 y 值從 15 變化到 23。

您可以在此處找到有關此主題和一些程式碼的更多資訊

碰撞系列 4:與高度圖碰撞

碰撞系列 5:帶法線的高度圖碰撞



  • Microsoft XNA Game Studio 3.0,Chad Carter
  • Microsoft XNA Game Studio 建立者指南第二版,S. Cawood 和 P. McGee
  • 使用 XNA 框架進行遊戲程式設計,Hans-Georg Schumann


RayIncarnation

華夏公益教科書