
Уважаемые Хабровчане, представляю вашему вниманию продолжение изысканий на тему поиска подходящих теней для 2D рогалика.
Данный пост является сиквелом публикации, своеобразной работой над ошибками и дальнейшее развитие идеи.
В своих комментариях, уважаемые критики, совершенно справедливо отметили, что в замкнутых пространствах тени получились угловатыми, и несколько не естественными. Было предложено несколько вариантов решения, мне понравилось предложение использовать ray casting для расчёта тени.

Уточняю, я не работаю с видеокартой (пока не работаю), все результаты смоделированы на ЦПУ.
В данной работе по рейкастингом понимается метод построения изображения посредством бросания лучей от наблюдателя в пространство до пересечения с препятствием (границами экрана) и подсвечиванием места их столкновения.
Здесь мы будем использовать упрощённую разновидность рейкастинга, основанную на пересечении лучом тайловой сетки. Данный метод широко использовался в псевдотрёхмерных играх прошлого (например Wolfenstein_3D, почтение тем кто в теме), мы его адаптируем для двумерного пространства.

Алгоритм, достаточно прост как для понимания, так и воплощения. Приведу собственную его реализацию:
Pascal
// 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 для виндовс)
Часть 1, Часть 3
