Уважаемые Хабровчане, представляю вашему вниманию продолжение изысканий на тему поиска подходящих теней для 2D рогалика.
Данный пост является сиквелом публикации, своеобразной работой над ошибками и дальнейшее развитие идеи.
В своих комментариях, уважаемые критики, совершенно справедливо отметили, что в замкнутых пространствах тени получились угловатыми, и несколько не естественными.
Было предложено несколько вариантов решения, мне понравилось предложение использовать ray casting для расчёта тени.
Уточняю, я не работаю с видеокартой (пока не работаю), все результаты смоделированы на ЦПУ.
В данной работе по рейкастингом понимается метод построения изображения посредством бросания лучей от наблюдателя в пространство до пересечения с препятствием (границами экрана) и подсвечиванием места их столкновения.
Здесь мы будем использовать упрощённую разновидность рейкастинга, основанную на пересечении лучом тайловой сетки. Данный метод широко использовался в псевдотрёхмерных играх прошлого (например Wolfenstein_3D, почтение тем кто в теме), мы его адаптируем для двумерного пространства.
Алгоритм, достаточно прост как для понимания, так и воплощения. Приведу собственную его реализацию:
// i,j - координаты тайла, а - угол
// X,Y - начальное смещение координат
// r - максимальный радиус для расчёта
// Инициализация направления
if cos(a)<0 then
begin di :=-1; ddi:= 0; end
else
begin di := 1; ddi:= 1; end;
if sin(a)<0 then
begin dj :=-1; ddj:= 0; end
else
begin dj := 1; ddj:= 1; end;
// Предварительный расчёт первой точки по Х и Y
x1 := (i+ddi) * tile_size;
y1 := y+ (x1-x) * tan(a);
Dx := len(x,y,x1,y1);
y1 := (j+ddj) * tile_size;
x1 := x+ (y1-y) * cotan(a);
Dy := len(x,y,x1,y1);
sum_lenX := 0;
sum_lenY := 0;
// Размер тайла по X и Y под углом a
rX := abs(tile_size / cos(a));
rY := abs(tile_size / sin(a));
// выбираем точки пересечения
repeat
if sum_lenX+DX < sum_lenY+DY then
begin
x1 := (i+ddi) * tile_size;
y1 := y+ (x1-x) * tan(a);
i := i+di;
// Проверяем тайл на наличие стены или границ экрана
key := is_wall(i,j);
sum_lenX := sum_lenX + DX;
if DX<>rX then DX:=rX;
// Если радиус больше нужного обрываем цикл
if r<sum_lenX then Break;
end
else
begin
y1 := (j+ddj) * tile_size;
x1 := x+ (y1-y) * cotan(a);
j := j+dj;
// Проверяем тайл на наличие стены или границ экрана
key := is_wall(i,j);
sum_lenY := sum_lenY + DY;
if DY<>rY then DY:=rY;
// Если радиус больше нужного обрываем цикл
if r<sum_lenY then Break;
end;
until (Пока не найдём стену или границу экрана);
// x1,y1 искомые координаты
Так как луч пересекает ячейки по каждой оси на одинаковом расстоянии, то можно сэкономить на расчётах и только проверять нет ли стены в границах тайла. Нам нужно пересечение с препятствием и запоминать его координаты.
В своей реализации я вынес всю тригонометрию и деления в отдельную таблицу для каждого угла, что значительно ускорило алгоритм.
Запустив лучи во все стороны с нужным шагом получим примерно такую картину:
Увеличив количество лучей до нескольких тысяч и получим искомый многогранник области видимости. Возможно, конечно, бросать лучи и для каждого пикселя изображения, как на 3D ускорителях, но без видеокарты тут уже не обойтись.
Дальше начинается работа со слоями.
Область видимости. Здесь и далее лучи немного проходят в глубь объектов. Такая игровая условность создаёт уникальный антураж, свойственный 2D Играм.
Генерация карты освещения. Статичные источники света генерируем заранее и кешируем для улучшения быстродействия, динамические накладываем в процессе вывода на экран.
Сведение всего вместе.
Не хватает только жутких монстров и сокровищ… много сокровищ.
Стены с изменяемой кривизной проникновения света мне не зашли, но возможно это на любителя.
В процессе создания прототипа я перепробовал множество вариаций модели, некоторые из них лучше подходят для хоррора:
Особенно понравился эффект множественного переотражения лучей от стен, но даже его наивная реализация так тормозила, что я оставил её на будущее, когда подружусь с видеокартой.
Спасибо за внимание.
Ссылка поиграться (exe для виндовс)
Автор: rebuilder