Генератор ландшафтов
В этой главе я научу вас методу создания 2D ландшафтов, который я использовал в свой игре Castles. Опять же, я не буду рассказывать о классе Main, которые управляет апплетом в целом, и о классе Stars, который рисует звезды на заднем фоне. Оба этих класса простые и забавные, и я думаю, что с самостоятельным их изучением у вас проблем не будет. Перед тем, как начать программировать, мы должны знать, что наш "проблема" представляет из себя.
Проблема
Мы хотим генерировать 2D ландшафт, выглядящий как скалистые горы, который можно будет использовать в игре типа "Castles". Ландшафт выглядит по разному в каждой новой сессии игры, поэтому мы не можем использовать файлы *.gif или что-либо вроде этого. Нужно, чтобы ландшафт генерировался случайным образом. Также желательно, чтобы он трансформировался, к примеру, когда бомба на местности взрывается "бомба". С другой стороны, структура ландшафта не должна быть слишком сложной, чтобы игра шла быстро и количество данных не было слишком большим. Я уверен, есть много разных решений этой проблемы. Давайте рассмотрим мое:
Идея и набросок алгоритма
Чтобы производить наименьшее возможное количество данных, я решил генерировать свой ландшафт из множества линий, а не из одних точек/пикселей. Основная идея - рисовать вертикальные линии для каждого пикселя по длине апплета. Нижняя точка у нас является константой, поэтому хранить в массиве нам нужно только верхние точки, "поверхность" нашего ландшафта, которые имеют различное значение для каждой линии.
![]() |
Следует признать, что случайный выбор каждой верхней точки смысла не имеет. Если вы хотите создавать рельеф таким образом, поверхность не будет иметь какую-либо структуру; вместо этого вы получите что-то вроде картинки рядом с этим текстом. |
Сгенерировать натурально выглядящую поверхность сложнее. Для начала я покажу вам основные концепции алгоритма, затем вы получите метод, который я использовал в игре Castles. Запомните, мы оставляем значение нижних точек постоянной величиной, меняя/храня только верхние точки!
1. В первую очередь мы выбираем начальное значение случайным образом и сохраняем его в массиве
2. Инициализируем вторую верхнюю точку (вторая линия) со значением на 1 пиксель больше или меньше предыдущего
3. Таким макаром создаем остальные значения массива, добавляя или вычитая 1 от значения, предшествующего создаваемому
4. Решение о том, добавлять или вычитать, принимается случайно. Шанс изменить направление (от добавления к вычитанию и наоборот) таков: в 10% тенденция сохраняется, в 90% направление меняется.
![]() |
Слева от этого текста вы можете видеть результат действия этого простого, но эффективного алгоритма - и он вполне неплох. Правда, есть маленькие проблемы. Зачастую происходит, что "горы" оказываются выше, а впадины ниже, чем границы апплета. Другая проблема состоит в том, что ландшафт выглядит, как я думаю, скучновато. Ниже вашему вниманию представлен алгоритм, который решает названные проблемы и добавляет некоторые дополнительные фичи типа изменения цвета. |
"Финальный" алгоритм
public void generateLandscape ()
{
/* инициализируем переменную plus, которая задает величину, которая будет прибавляться или вычитаться из предшествующего значения */
plus = 1;
// инициализируем переменную faktor, решающую, какой знак, + или -, будет у plus
faktor = 1;
// инициализация первоначального значения поверхности
start = Math.abs(300 + (rnd.nextInt() % 50));
// сохраняем первоначальное значение на первой позиции в массиве
map [0] = start;
// инициализируем первоначальные значения цветов
int greenvalue = 200;
int redvalue = Math.abs(rnd.nextInt() % 200);
int bluevalue = Math.abs(rnd.nextInt() % 201);
// сохраняем первое RGB-значение в массиве Color
colors [0] = new Color (redvalue, greenvalue, bluevalue);
// зацикливаем, чтобы инициализировать все позиции массива
for (int i = 1; i < mapsize; i ++)
{
-
// берем значение перед текущей позицией и сохраняем его в переменную last
last = map [i - 1];
// Выбор решения изменения направления
change = Math.abs(rnd.nextInt() % 10);
// изменяем направления и, возможно, plus
if (change > 8)
{
-
// Andern der Richtung
faktor = - (faktor);
// новый plus (значение 1 или 2)
plus = 1 + Math.abs(rnd.nextInt() % 2);
/* Убедитесь, что значения поверхности остаются в определенном диапазоне */
if (last > 350 || last < 120)
{
-
// Andern der Richtung
faktor = - (faktor);
// Убедитесь, что значения цвета остаются в определенном диапазоне
if (greenvalue > 240)
{
-
// если значение цвета слишком высокое
greenvalue -= 10;
else if (greenvalue < 100)
{
-
// если значение цвета слишком маленькое
greenvalue += 10;
// Вычисляем и сохраняем значение поверхности на позиции i
map [i] = last + (faktor * plus);
// Вычисляем и сохраняем значение цвета на позиции i
greenvalue = greenvalue + (-faktor * plus);
colors [i] = new Color (redvalue, greenvalue, bluevalue);
}
![]() |
Когда алгоритм инициализирует все значение массива, ландшафт будет раскрашен. Наилегчайший путь сделать это - получить все значения массива с помощью цикла for. Затем нарисовать линию от нижней границы апплета (постоянная величина) до поверхности (значения в массиве). Рисуйте линии соответствующего цвета. Результат метода вы видите рядом с текстом. Как я упоминал в начале главы, есть немало способов создать рельеф (например, использовать и хранить в массиве точки вместо линий), но мой алгоритм быстрый и не вырабатывает большого количества данных, а результат, по крайней мере, неплох. Ну, а если вы знаете другой хороший метод создания ландшафтов (может быть даже трехмерных), напишите мне. |
Скачать исходный код апплета
Запустить апплет
Следующая глава:
Редактор уровней