Создание Ландшафта. Фокусирование на создании Ландщафта (Перевод) Часть 2

Как Вы можете видеть, мы просто преобразовываем вершину и передаем входной цвет пиксельному шейдеру. Он выводит цвет вершины. Таким образом, цвета вершины интерполированы. Так как мы начинаем с желтого цвета, Вы будете видеть единственный цвет.

Вот скриншот приложения:


Теперь у нас есть все, чтобы играть с созданием heightmap и рендерингом ландшафта.

Управление:

thumbstick/AWSD, чтобы двигать курсор, правые клавиши thumbstick/arrow, чтобы вращаться, PAGE DOWN/PAGE UP для движения вверх и вниз.

Случайный heightmap

В этой части мы изучим простое случайное создание heightmap. Что мы должны сделать?

Во-первых, мы добавляем минимальную и максимальную высоту в класс heightmap, чтобы иметь контроль над heightmap. Кроме того, мы изменяем конструктор, класс ландшафта и gamefot класс, чтобы показывать изменения.

/// <summary>

/// Создание случайного heightmap.

/// </summary>

/// <param name=»width»></param>

/// <param name=»depth»></param>

/// <param name=»min»></param>

/// <param name=»max»></param>

public void GenerateRandomHeightmap(int width, int depth, float min, float max)

{

Random random = new Random();

_width = width;

_depth = depth;

_minimumHeight = min;

_maximumHeight = max;

_heightValues = new float[_width * _depth];

for (int x = 0; x < _width; ++x)

{

for (int z = 0; z < _depth; ++z)

{

_heightValues[x + z * _width] = (float)random.NextDouble() *

(_maximumHeight — _minimumHeight) +

_minimumHeight;

}

}

}

Мы также добавляем в класс Terrain небольшую вспомогательную функцию по имени GetHeightPercentage, чтобы менять цвета и размер вершины.

/// <summary>

/// Возвращаем процент от высоты, основанной на минимальных и максимальных значениях в данных координатах.

/// </summary>

/// <param name=»x»></param>

/// <param name=»z»></param>

/// <returns></returns>

public float GetHeightPercentage(int x, int z)

{

return (_heightValues[x + z * _width] / _maximumHeight);

}

В классе ландшафта мы добавляем флаг _isWireframe, чтобы позволить пользователям переключаться между каркасом и сплошной закраской. Мы меняем метод рисования с помощью простого условного оператора для того, чтобы отразить изменения:

if (_isWireframe)

{

GameFOT.Instance.GraphicsDevice.RenderState.FillMode = FillMode.WireFrame;

}

else

{

GameFOT.Instance.GraphicsDevice.RenderState.FillMode = FillMode.Solid;

}

Построение вершинного буфера обновлено, теперь выбираем цвет вершины в функции значения процента высоты ландшафта, устанавливаем значения красного и синего компонентов и устанавливаем зеленый компонент в 1.0f. Таким образом, самая низкая часть ландшафта будет зеленого цвета, а самая высокая часть ландшафта будет белого.

Так как мы теперь должны обновить ландшафт на ходу, мы перемещаем создание сети ландшафта в новый метод по имени BuildTerrainMesh, и обновляем метод LoadGraphicsContent, чтобы его использовать. Вот метод BuildTerrainMesh:

/// <summary>

/// Перестройка сети ландшафта.

/// </summary>

private void BuildTerrainMesh()

{

BuildVertexBuffer();

_primitiveType = PrimitiveType.TriangleStrip;

_vertexDeclaration = new VertexDeclaration(GameFOT.Instance.GraphicsDevice,

VertexPositionColor.VertexElements);

_vertexBuffer = new VertexBuffer(GameFOT.Instance.GraphicsDevice,

VertexPositionColor.SizeInBytes * _geometry.Length,

ResourceUsage.WriteOnly,

ResourceManagementMode.Automatic);

_vertexBuffer.SetData<VertexPositionColor>(_geometry);

BuildIndexBuffer();

_indexBuffer = new IndexBuffer(GameFOT.Instance.GraphicsDevice,

sizeof(short) * _indices.Length,

ResourceUsage.WriteOnly,

IndexElementSize.SixteenBits);

_indexBuffer.SetData<short>(_indices);

Мы добавляем новый метод GenerateRandomTerrain к классу Terrain:

/// <summary>

/// Generate a random terrain.

/// </summary>

/// <param name=»width»></param>

/// <param name=»depth»></param>

/// <param name=»min»></param>

/// <param name=»max»></param>

public void GenerateRandomTerrain(int width, int depth, float min, float max)

{

_heightmap.GenerateRandomHeightmap(width, depth, min, max);

BuildTerrainMesh();

}

Теперь в файле GameFOT.cs, для того чтобы переключить каркасный режим и создавать новый случайный ландшафт, мы добавляем следующий код в функцию обновления игры:

if ((_inputManager.CurrentGamePadState.Buttons.A == ButtonState.Pressed &&

_inputManager.PreviousGamePadState.Buttons.A == ButtonState.Released) ||

(_inputManager.CurrentKeyboardState.IsKeyDown(Keys.Space) &&

_inputManager.PreviousKeyboardState.IsKeyUp(Keys.Space)))

{

_terrain.IsWireframe = !_terrain.IsWireframe;

}

if ((_inputManager.CurrentGamePadState.Buttons.B == ButtonState.Pressed &&

_inputManager.PreviousGamePadState.Buttons.B == ButtonState.Released) ||

(_inputManager.CurrentKeyboardState.IsKeyDown(Keys.Enter) &&

_inputManager.PreviousKeyboardState.IsKeyUp(Keys.Enter)))

{

_terrain.GenerateRandomTerrain(32, 32, 0.0f, 3.0f);

}

Вот результат:


Вы можете найти исходный текст в папке P103RandomHeightmap.

Управление:

Левый thumbstick/AWSD, чтобы двигаться, правые клавиши thumbstick/arrow, чтобы вращаться, левый и правый triggers/E и Q, чтобы двигаться вверх и вниз, клавиша/пробел, чтобы переключить каркасный режим, B/enter, чтобы генерировать новый ландшафт.

Heightmap из файла

Теперь мы рассмотрим, как сохранить и загрузить heightmap. Мы будем делать это по-простому, используя формат RAW, и используя стандартный ввод–вывод .NET.

В классе Heightmap мы добавляем два новых метода: SaveToFile и LoadFromFile. Кроме того, мы добавляем System.IO сверху файла. Мы не будем проверять ошибки, а будем просто использовать блокировку try/catch. Не стесняйтесь добавлять Ваши собственные особенности, если хотите.

Вот код для метода SaveToFile:

/// <summary>

/// Save a heightmap to a file.

/// </summary>

/// <param name=»filename»></param>

public void SaveToFile(String filename)

{

try

{

FileStream stream = File.Open(filename, FileMode.OpenOrCreate);

if (stream != null)

{

BinaryWriter writer = new BinaryWriter(stream);

writer.Write(_width);

writer.Write(_depth);

writer.Write(_minimumHeight);

writer.Write(_maximumHeight);

for (int i = 0; i < _heightValues.Length; ++i)

{

writer.Write(_heightValues[i]);

}

writer.Flush();

stream.Close();

}

}

catch (Exception)

{

}

}

Сначала мы создаем новый filestream, используя указанное имя файла. Файл открывается, или создается, если он не существует. Мы создаем двойную программу записи, и сначала записываем ширину heightmap. Затем мы записываем глубину, минимальную и максимальную высоту и, наконец, значения высоты. Мы скидываем на диск программу записи, и закрываем поток. Метод LoadFromFile работает аналогично, это важно для чтения/записи в том же порядке.

Вот код для функции LoadFromFile:

/// <summary>

/// Load a heightmap from a file.

/// </summary>

/// <param name=»filename»></param>

public void LoadFromFile(String filename)

{

try

{

FileStream stream = File.Open(filename, FileMode.Open);

BinaryReader reader = new BinaryReader(stream);

_width = reader.ReadInt32();

_depth = reader.ReadInt32();

_minimumHeight = reader.ReadSingle();

_maximumHeight = reader.ReadSingle();

_heightValues = new float[_width * _depth];

for (int i = 0; i < _heightValues.Length; ++i)

{

_heightValues[i] = reader.ReadSingle();

}

reader.Close();

stream.Close();

}

catch (Exception)

{

}

}

Мы также добавляем методы SaveToFile и LoadFromFile к классу Terrain. SaveToFile просто вызывает метод heightmap SaveToFile. Метод LoadFromFile вызывает метод heightmap LoadFromFile и перестраивает сеть ландшафта.

Теперь, мы должны позволить пользователю использовать эти функции в игре. В методе обновления игры мы добавляем следующий код:

if ((_inputManager.CurrentGamePadState.Buttons.X == ButtonState.Pressed &&

_inputManager.PreviousGamePadState.Buttons.X == ButtonState.Released) ||

(_inputManager.CurrentKeyboardState.IsKeyDown(Keys.PageDown) &&

_inputManager.PreviousKeyboardState.IsKeyUp(Keys.PageDown)))

{

_terrain.SaveToFile(«terrain.raw»);

}

if ((_inputManager.CurrentGamePadState.Buttons.Y == ButtonState.Pressed &&

_inputManager.PreviousGamePadState.Buttons.Y == ButtonState.Released) ||

(_inputManager.CurrentKeyboardState.IsKeyDown(Keys.PageUp) &&

_inputManager.PreviousKeyboardState.IsKeyUp(Keys.PageUp)))

{

_terrain.LoadFromFile(«terrain.raw»);

}

Просто, не так ли? 🙂

Исходный код для этой части может быть найден в папке P104HeightmapFromFile.

Heightmask

Вы наверное задумывались, что такое heightmask’и, и для чего они используются? heightmask — в основном маска, которая используется когда Вы генерируете heightmap, чтобы изменить сгенерированные высоты. Например, она может использоваться, чтобы генерировать процедурные карты
в сигнале RTS, используя шаблон для плоских стартовых областей, а так же генерировать пути от одной области к другой. Эта тема редко затрагивается, и в этой части я опишу основы heightmask. Далее, в обучающей программе я покажу Вам, как использовать продвинутые методики heightmask.

Вот два скриншота того, что мы собираемся сделать:



Вы поняли идею? Хорошо, давайте переходить к коду.

Во-первых, мы создаем heightmask класс. Этот класс очень похож на наш первый heightmap класс. Вместо того чтобы хранить значения высоты, мы будем использовать булевские переменные. True означает, что мы используем mask для высоты, False оставляет ее нетронутой.

Вот класс:

/// <summary>

/// This is the heightmask class.

/// </summary>

public class Heightmask

{

/// <summary>

/// Width of the heightmask.

/// </summary>

int _width;

/// <summary>

/// Depth of the heightmask.

/// </summary>

int _depth;

/// <summary>

/// Mask values of the heightmask.

/// </summary>

bool[] _maskValues;

/// <summary>

/// Get the width of the heightmask.

/// </summary>

public int Width

{

get

{

return _width;

}

}

/// <summary>

/// Get the depth of the heightmask.

/// </summary>

public int Depth

{

get

{

return _depth;

}

}

/// <summary>

/// Mask values of the heightmask.

/// </summary>

public bool[] MaskValues

{

get

{

return _maskValues;

}

}

/// <summary>

/// Default constructor.

/// </summary>

/// <param name=»width»></param>

/// <param name=»depth»></param>

public Heightmask(int width, int depth)

{

_width = width;

_depth = depth;

_maskValues = new bool[_width * _depth];

}

/// <summary>

/// This constructor builds a heightmask using the boolean array.

/// </summary>

/// <param name=»width»></param>

/// <param name=»depth»></param>

/// <param name=»data»></param>

public Heightmask(int width, int depth, bool[] data)

{

_width = width;

_depth = depth;

_maskValues = (bool[])data.Clone();

}

/// <summary>

/// Return the mask value at the given x,z coordinate.

/// </summary>

/// <param name=»x»></param>

/// <param name=»z»></param>

/// <returns></returns>

public bool GetMaskValue(int x, int z)

{

return _maskValues[x + z * _width];

}

/// <summary>

/// Set the mask value at the given x,z coordinate.

/// </summary>

/// <param name=»x»></param>

/// <param name=»z»></param>

/// <param name=»value»></param>

public void SetMaskValue(int x, int z, bool value)

{

_maskValues[x + z * _width] = value;

}

/// <summary>

/// Save a heightmask to a file.

/// </summary>

/// <param name=»filename»></param>

public void SaveToFile(String filename)

{

try

{

FileStream stream = File.Open(filename, FileMode.OpenOrCreate);

if (stream != null)

{

BinaryWriter writer = new BinaryWriter(stream);

writer.Write(_width);

writer.Write(_depth);

for (int i = 0; i < _maskValues.Length; ++i)

{

writer.Write(_maskValues[i]);

}

writer.Flush();

stream.Close();

}

}

catch (Exception)

{

}

}

/// <summary>

/// Load a heightmask from a file.

/// </summary>

/// <param name=»filename»></param>

public void LoadFromFile(String filename)

{

try

{

FileStream stream = File.Open(filename, FileMode.Open);

BinaryReader reader = new BinaryReader(stream);

_width = reader.ReadInt32();

_depth = reader.ReadInt32();

_maskValues = new bool[_width * _depth];

for (int i = 0; i < _maskValues.Length; ++i)

{

_maskValues[i] = reader.ReadBoolean();

}

reader.Close();

stream.Close();

}

catch (Exception)

{

}

}

}


Реклама
Запись опубликована в рубрике Uncategorized. Добавьте в закладки постоянную ссылку.

Добавить комментарий

Заполните поля или щелкните по значку, чтобы оставить свой комментарий:

Логотип WordPress.com

Для комментария используется ваша учётная запись WordPress.com. Выход / Изменить )

Фотография Twitter

Для комментария используется ваша учётная запись Twitter. Выход / Изменить )

Фотография Facebook

Для комментария используется ваша учётная запись Facebook. Выход / Изменить )

Google+ photo

Для комментария используется ваша учётная запись Google+. Выход / Изменить )

Connecting to %s