使用 XNA 创建游戏/3D 开发/地形建模
我们如何将基于 XNA 框架的地形实现并建模到我们的游戏中?本维基条目将专门解决这个问题。例如,将展示如何使用高度图创建地形。此外,我们将创建纹理,将其拖放到我们的地形上,并编写大量源代码。最后,将提供一些关于地形建模相关主题的技巧。
高度图(维基百科:高度图)只不过是灰度图。也就是说,一个 2D 纹理,它指出我们地形的高度和深度。灰度图的每个像素都在 0 到 255 之间,表示我们的海拔高度。要创建这样的地图,请使用 Terragen 之类的程序。
Terragen 是一个用于快速创建逼真地形图像的程序。但是,它也是创建高度图的完美工具。Terragen 有两个版本(日期:2011 年 06 月 05 日),一个需要付费的版本 - Terragen 2 和一个免费版本 Terragen Classic。对于我们的需求,免费版本已经足够了。
介绍就到这里 - 让我们开始吧。下载并安装 Terragen Classic 后,我们可以看到以下菜单
在左侧,我们可以看到 Terragen 提供的按钮。第一步是单击“地形”,一个新窗口将打开。在这里,我们单击“大小”来调整高度图的大小 - 257x257 或 513x513。提示:如果您已经实现了天空盒,请使用天空盒图像的大小。接下来,我们单击“视图/雕刻”来建模我们的高度图。您将看到一张带有白色箭头的黑色图片 - 这是您的相机视角。您可以通过将箭头移动到所需位置来根据需要调整视角。要开始绘制地形,您需要单击位于窗口左上角的“基本雕刻工具”(1)。现在您可以开始绘制地形了。结果应该类似于此
如果您对结果不满意,您始终可以单击地形窗口中的“修改”并调整某些设置,例如山脉的最大高度。另一个有用的功能是“清除/平坦化”,它可以重置您的高度图,这样您就可以重新开始。完成绘制高度图后,单击“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 代码中。但要实际看到一些东西,我们需要从编程一个相机开始。
我们在 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),您将找到对方法使用的所有参数以及它们的返回值的详细描述。
回到我们的相机类。下一步是创建一个 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 类应该如下所示。
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 类的概述
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);
}
}
}
在我们开始之前,我们将我们的高度图以及我们的纹理图像导入 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);
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。
您可以在此处找到有关此主题和一些代码的更多信息
- http://en.wikipedia.org/
- http://www.riemers.net/eng/Tutorials/XNA/Csharp/Series1/Terrain_from_file.php
- Microsoft XNA Game Studio 3.0,Chad Carter
- Microsoft XNA Game Studio 创建者指南第二版,S. Cawood 和 P. McGee
- 使用 XNA 框架进行游戏编程,Hans-Georg Schumann
RayIncarnation