Создание и исследование трехмерных лабиринтов с помощью Silverlight 5
В лабиринтах есть что-то такое, что всегда изумляет меня. Они кажутся простыми, но математика, стоящая за ними может быть очень сложной (по крайней мере, для парней вроде меня). Еще у них есть притягательность, которой сложно сопротивляться. Затем у каждого внутри есть что-то цельное, работающее над нахождением выхода из сложившейся ситуации.
Сегодняшний проект не только познакомит нас с созданием и генерацией случайного лабиринта, но теперь, используя новые трехмерные возможности Silverlight 5, мы можем также изучать наши создания…
Создание трехмерного лабиринта с помощью Silverlight 5.0
Я хотел бы начать это сообщение с заявления о том, что я не являюсь экспертом по разработке игр. В начале своей работы с компьютерами я был очарован игровыми возможностями, но никогда не углублялся в область разработки. Когда я впервые встретился с Silvrelight 5 и узнал о трехмерном программировании, первой идеей стало создание очень простого лабиринта, который можно использовать как основу для трехмерной игры. Здесь я хочу кратко проиллюстрировать работу, которую можно скачать в конце текста. Вы можете посмотреть короткое видео о результатах моей работы.
Лабиринт на этом видео целиком выполнен с помощью 3D API Silverlight 5.0 и полностью совместим с битами RTW. Лабиринт случайным образом генерируется каждый раз, когда вы загружаете программу, и вы можете управлять движением с помощью клавиатуры.
Генерируем лабиринт
В основе примера лежит алгоритм генерации случайных чисел. Я полагаю, что существует множество алгоритмов, которые можно найти в Интернете, и, возможно, тот, который использовал я, действительно прост, но он на самом деле эффективен.
Лабиринт основан на квадрате, разделенном на ряд ячеек, и каждая ячейка имеет стены с каждой стороны. После того, как я выбрал размер стороны, я заполнил квадрат ячейками и выбрал случайную ячейку на стороне. Затем я нахожу смежную ячейку, в которую двигаться. Если ячейка существует, я удаляю стенку между ними и передвигаюсь на новую позицию…
...
public void Generate()
{
ThreadPool.QueueUserWorkItem(
o =>
{
int progress = 0;
Stack<Cell> stack = new Stack<Cell>();
this.Reset();
Cell cell = this.GetRandom(0);
cell.Type = CellType.Enter;
cell.Visited = true;
while (true)
{
Cell adiacent = this.GetAdiacentNonVisited(cell);
if (adiacent != null)
{
stack.Push(cell);
adiacent.Visited = true;
cell = adiacent;
progress++;
this.OnGenerationProgressChanged((int)(progress * 100.0 / (this.Width * this.Height)));
}
else
{
if (stack.Count > 0)
cell = stack.Pop();
else
break;
}
}
cell = this.GetRandom(this.Height - 1);
cell.Type = CellType.Exit;
this.OnGenerationCompleted();
});
}
Создание трехмерной картинки
После того, как лабиринт рассчитан, время нарисовать его, используя Silverlight 3D API. Отрисовка осуществляется путем создания плана квадрата, представляющего пол лабиринта и последующих итераций по всем ячейкам и созданием оставшихся стен.
Стена представляет собой точный параллелепипед, созданный на стороне. Так как толщина стороны квадрата равна нулю, то стена создается на этой линии – некоторые точки внешние, некоторые внутренние. Чтобы избежать зазоров в углах, все стены включают углы квадрата, также если другая стена уже использует то же пространство.
Silverlight 3D API позволяет создавать любой вид фигур, используя коллекцию границ. С помощью границ рисуются треугольники. Треугольник это базовая поверхность, которую можно создать, соединяя три границы, являющиеся частью одной плоскости. Любая другая поверхность может быть сконструирована из коллекции треугольников, необязательно лежащих в одной плоскости. Так что для создания прямоугольника, представляющего лицевую сторону стены, потребуются два треугольника.
...
private void AddWall(List<VertexPositionColor> edges, float xOffset, float zOffset, float xSize, float zSize)
{
var wall = new List<VertexPositionColor>();
Vector3 topLeftFront = new Vector3(xOffset, this.Height, zOffset + zSize);
Vector3 bottomLeftFront = new Vector3(xOffset, 0.0f, zOffset + zSize);
Vector3 topRightFront = new Vector3(xOffset + xSize, this.Height, zOffset + zSize);
Vector3 bottomRightFront = new Vector3(xOffset + xSize, 0.0f, zOffset + zSize);
Vector3 topLeftBack = new Vector3(xOffset, this.Height, zOffset);
Vector3 topRightBack = new Vector3(xOffset + xSize, this.Height, zOffset);
Vector3 bottomLeftBack = new Vector3(xOffset, 0.0f, zOffset);
Vector3 bottomRightBack = new Vector3(xOffset + xSize, 0.0f, zOffset);
Color c1 = Color.FromNonPremultiplied(200, 200, 200, 255);
Color c2 = Color.FromNonPremultiplied(150, 150, 150, 255);
Color c3 = Color.FromNonPremultiplied(100, 100, 100, 255);
// Front face
wall.Add(new VertexPositionColor(topRightFront, c1));
wall.Add(new VertexPositionColor(bottomLeftFront, c1));
wall.Add(new VertexPositionColor(topLeftFront, c1));
wall.Add(new VertexPositionColor(topRightFront, c1));
wall.Add(new VertexPositionColor(bottomRightFront, c1));
wall.Add(new VertexPositionColor(bottomLeftFront, c1));
// Back face
wall.Add(new VertexPositionColor(bottomLeftBack, c1));
wall.Add(new VertexPositionColor(topRightBack, c1));
wall.Add(new VertexPositionColor(topLeftBack, c1));
wall.Add(new VertexPositionColor(bottomRightBack, c1));
wall.Add(new VertexPositionColor(topRightBack, c1));
wall.Add(new VertexPositionColor(bottomLeftBack, c1));
// Top face
wall.Add(new VertexPositionColor(topLeftBack, c2));
wall.Add(new VertexPositionColor(topRightBack, c2));
wall.Add(new VertexPositionColor(topLeftFront, c2));
wall.Add(new VertexPositionColor(topRightBack, c2));
wall.Add(new VertexPositionColor(topRightFront, c2));
wall.Add(new VertexPositionColor(topLeftFront, c2));
// Left face
wall.Add(new VertexPositionColor(bottomLeftFront, c3));
wall.Add(new VertexPositionColor(bottomLeftBack, c3));
wall.Add(new VertexPositionColor(topLeftFront, c3));
wall.Add(new VertexPositionColor(topLeftFront, c3));
wall.Add(new VertexPositionColor(bottomLeftBack, c3));
wall.Add(new VertexPositionColor(topLeftBack, c3));
// Right face
wall.Add(new VertexPositionColor(bottomRightBack, c3));
wall.Add(new VertexPositionColor(bottomRightFront, c3));
wall.Add(new VertexPositionColor(topRightFront, c3));
wall.Add(new VertexPositionColor(bottomRightBack, c3));
wall.Add(new VertexPositionColor(topRightFront, c3));
wall.Add(new VertexPositionColor(topRightBack, c3));
edges.AddRange(wall);
}
Передвиженияигрокаисоздание сплошных стен
Удивительно впервые взглянуть на нарисованный лабиринт, но еще более потрясающе двигаться внутри него с помощью клавиатуры. Каждый раз когда свойство объекта Observer изменяется, эти изменения отражаются наружу с помощью события Change. Событие вызывает обновление положения камеры и перерисовывает сцену. Используя стрелки «вверх» и «вниз» можно двигаться вперед и назад, а с помощью стрелок «вправо» и «влево» можно менять направление движения на соответствующие направления. Кроме того, можно использовать клавиши PageUp и PageDown для наклона камеры вниз и вверх, как если бы наблюдатель наклонял голову.
Чтобы постоянно перехватывать события клавиатуры я использовал прием. К этому пришлось прибегнуть, поскольку Silverlight не повторяет автоматически события клавиатуры. Используя класс KeyboardState, я создал проверочное условие для изменения состояния KeyUp и KeyDown отслеживаемых клавиш. Затем таймер изменяет свойства наблюдателя в соответствии с нажатой клавишей. Такой прием позволяет также использовать сразу более одной клавиши, давая возможность наблюдателю поворачивать в момент, когда он движется вперед.
...
Работа с 3D
Работа с 3D в Silverlight очень интересна, но чтобы достичь хороших результатов требуется глубокое знание 3D-математики. Каждый раз, когда вы получаете что-то реально эффективное благодаря использованию API низкого уровня, интерфейс программирования становится слишком трудным. Так что если вам нужно работать с 3D, я предлагаю использовать платформу высокого уровня, абстрагирующую API и позволяющую мыслить в терминах сплошных фигур, а не коллекции границ. Я полагаю, Балдер привел прекрасный наглядный пример https://balder.codeplex.com/.
Скачать: https://www.silverlightplayground.org/assets/sources/SLPG.Maze.zip (270kb)
Вот снимок решения:
И решение, запущенное на моем ноутбуке (которое у меня заработало с первого раза без проблем).
Если вы размышляете над тем как поиграть с новыми возможностями 3D, доступными в Silverlight 5 или являетесь фанатом лабиринтов, этот проект может заинтересовать вас…