В Части 1 я рассмотрел различные подходы, которые я использовал для рендеринга ландшафта на основе карты высот в PICO-8. На этот раз я объясню, как генерируется ландшафт, и покажу тележку PICO-8 «2D Terrain Demo (PICO-8 BBS)», которая демонстрирует некоторые решения.

Рельеф хранится в виде массива чисел. В примере корзины этот массив имеет длину 128, ширину экрана PICO-8 для простоты. Функция generate_terrain() (строка 57) вызывается при инициализации тележки и изменяет массив ландшафта с помощью упрощенного алгоритма Diamond-Squared, описанного в предыдущем посте. Первое, что он делает, это инициализирует массив до единой высоты.

Это интересно, потому что значения высоты ландшафта в целом будут следовать этой базовой высоте (с вариациями в зависимости от случайного фактора — подробнее позже). Если вы попытаетесь инициализировать это случайными значениями, вы получите резкие неровные изменения высоты. Нравится:

Следующим шагом является выбор начального значения «шага», которое вы используете для обхода массива. Это начинается с простого деления длины массива на два. Когда я впервые написал это, меня поймали, потому что я не понимал, что в Lua нет целых чисел, поэтому продолжал ссылаться на несуществующие элементы массива.

Затем у вас есть цикл while, который формирует основную часть генерации ландшафта. Вы выполняете цикл, пока шаг не станет слишком маленьким. В конце каждого цикла шаг делится на 2, чтобы заполнить увеличивающиеся небольшие пробелы в местности. Если вы измените размер минимального шага, вы заметите, что вертикальные срезы по-прежнему имеют исходное значение и не были «сглажены» в кривую ландшафта.

while(step>=1) do
 local segmentstart=1
 while(segmentstart<=width) do
  ... -- see below
 end
 randomness/=2
 step/=2
end

В основном цикле while вы используете переменную шага для обхода массива, чтобы установить среднюю точку подсегмента массива. Каждый сегмент охватывает ваши текущие позиции и вашу текущую позицию плюс значение шага. Одна проблема заключается в том, что шаг может «выходить» за границы массива. В этом случае правый/сегментный индекс зацикливается на начало массива.

while(segmentstart<=width) do
 local left=segmentstart
 local right=left+step
 if right>width then
  right-=width
 end
 generate_height_at_midpoint(left,right,randomness)
 segmentstart+=step
end

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

function generate_height_at_midpoint(left,right,randomness)
 terrain[flr((left+right)/2)]=
 (terrain[left]+
  terrain[right])/2
 +(rnd(1)*randomness-(randomness/2))
end

Сюда добавляется случайный фактор, из-за которого в ландшафте появляются вершины и долины. В демонстрационной корзине попробуйте нажимать вверх и вниз, чтобы изменить степень случайности. Больше хаотичности = больше неровностей. В конце вложенного цикла while «обход массива» случайность уменьшается вдвое. Если вы не уменьшите случайность после каждого прохода ландшафта, вы не получите приятного эффекта сглаживания, который дает повторные проходы массива.

Как только все это будет сделано, ландшафт будет создан!

В демонстрационной тележке одномерная карта высот из массива `terrain` затем копируется в двумерный массив значений пикселей, которые используются для рендеринга ландшафта в попиксельной форме.

Функция _draw() содержит три способа рисования ландшафта. Первый использует линии, чтобы дать зеленый блок местности. Второй использует «текстуру местности» в данных спрайта. Он не намного сложнее линейного варианта. Он использует sspr() для рисования фрагментов текстуры ландшафта шириной в 1 пиксель. Эта текстура хранится в 0,0 в данных спрайта.

Обратите внимание, что вызов line() все еще здесь, он используется для рисования темно-синей области. Можно было бы изменить четвертый аргумент на sspr() и использовать большую вертикальную область данных спрайта (используя вторую страницу), если вы хотите текстурировать больше ландшафта. Мне очень нравится контраст между текстурированной и нетекстурированной областью, по крайней мере, для моего текущего проекта.

function draw_textured()
 for i=1,width do
  sspr(i%32,0,1,32,i-1,128-terrain[i])
  line(i-1,128,i-1,128-(terrain[i]-32),1)
 end
end

Вы могли заметить, что значение рельефа вычитается из 128 из-за того, что ось Y увеличивается по мере того, как вы спускаетесь по экрану. Это может быть изменено ранее во время генерации ландшафта.

Режим попиксельного рисования также довольно прост. Он использует вложенный цикл for для обхода массива 2D-пикселей и должен выполнить нулевую проверку, чтобы увидеть, есть ли значение в текущих координатах x, y. О проблемах этого я говорил в прошлом посте. Если вы не выполняете проверку нуля, он просто рисует черные пиксели там, где нет значения пикселя ландшафта.

Я надеюсь, что этот пост + корзина будет вам полезен. Пожалуйста, используйте его в качестве основы для любых других проектов и дайте мне знать, если вы это сделаете (или если у вас есть какие-либо вопросы) в Твиттере @Powersaurus!