Хабрахабр

[Из песочницы] Реалистичные тени для roguelike

image

Доброго времени, Хабр-сообщество.

Тогда меня озадачила возможность создать интересные элементы для геймплея в roguelike (2). Много лет назад, натолкнулся на пост (1). Но более мне по душе ситуация, когда мы, путешествуя по коридорам подземелья, раскрываем особенности расположения объектов постепенно на основе области видимости.
Позже в постах: (3), (4) и (5) рассматривались вопросы наложения теней в 2D играх. Допустим противник может находиться за стеной, мы его не видим, пока мы не столкнёмся с ним в зоне прямой видимости. Как отмечалось как самими авторами, так и в комментариях, что расчёт теней достаточно объёмная и не простая задача, как для вычислителя, так и для дизайна.

Понятное дело, что видеокарта справляется с тенями успешно и быстро, но в данном случае, хотелось обрабатывать тени для 2D игры, и переводить вычисления на видеокарту мне показалось лишним. Как-то появилось у меня несколько свободных дней, и я решил вернуться к вопросу более перспективных теней. Да и процессорная мощность за последние годы в целом подросла, собственно пост о том, что в итоге получилось.

Программа писалась на Pascal, просто потому что я его неплохо знаю, а Lazarus открытая IDE с широким набором компонентов.

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

image

Тени становятся то шире, то уже. Однако такая тень выглядит несколько неестественно, когда меняется угол зрения.

image

Для того чтобы построить такую тень, нужно провести две касательные от точки наблюдения к окружности, и до границ экрана. Тень от круглого объекта выглядит гораздо лучше. Диаметр окружности будет соответствовать размеру тайла.

В своей программе я использовал следующую функцию:

//Нахождение координат точек пересечения прямой с окружностью
function tangent_to_circle(x1,y1,x2,y2,r:Single; var x3,y3,x4,y4:Single):Boolean;
var l,dx,dy,i,ii,ij:Single;
begin dx := x1 - x2; dy := y1 - y2; l := Sqrt(dx*dx + dy*dy); if l<=r then begin tangent_to_circle:=false; exit; end; i := r/l; ii := i*i; ij := i * Sqrt(1 - ii); x3 := x2 + dx*ii - dy*ij; y3 := y2 + dx*ij + dy*ii; x4 := x2 + dx*ii + dy*ij; y4 := y2 - dx*ij + dy*ii; tangent_to_circle:=true;
end;

Где (x1,y1) – точка наблюдения, (x2,y2)- центр окружности, ®- её радиус, а (x3,y3) и (x4,y4) точки пересечения прямых и окружности. Функция возвращает истинность только когда наблюдатель находиться вне окружности.

Собственно опирался на простое правило (грубая модель), специалисты подскажут почему. Поскольку процессор не очень дружит с тригонометрией, постарался по минимуму её использовать.

(Плохо)SIN,COS..>‘/’,SQRT>‘DIV’,’MOD’>‘SHR’,’SHL’>‘*’>‘:=’,’+’,’-’,’AND’,’XOR’..(Хорошо)

При разработке на Delphi приходилось использовать библиотеку Agg2D, на Lazarus существует её порт (6), на нём и решил воплотить задумку. Реализовывать графическую часть примитивов по канве, то ещё удовольствие, существует множество библиотек и движков, которые облегчают работу. Собственно, выигрыш от библиотеки в том, что к RGB цветам добавляется альфаканал, и примитивы получаются сглаженными, а также за счёт прямого доступа к пикселям и различных ухищрений обработка значительно быстрее канвы.

залит зелёным). При рисовании тени тайла, изначально собирался заливать сектор тенью, но тогда изображение внутри тайла было плохо различимо (рассматриваемый сектор на рис 3. Поэкспериментировав с различными вариантами, остановился на выделении сектора из области тени.

(arctan2 – библиотечная функция модуля math) Для рисования сектора нам нужен угол в радианах, без тригонометрии всё же не обошлось.

// Получаем угол в радианах
function alpha_angle(x1,y1,x2,y2:Single):Single;
begin alpha_angle := arctan2(y1 - y2, x1 - x2);
end;

Собственно, всё готово для сборки изображения. Берём карту тайлов и на отдельном слое последовательно наносим тени, одну за одной. Для деревьев тени потемнее, для иных объектов тени более прозрачные.

image

Немного оформления заднего фона и подобрать тайлсеты поколоритнее. Готовое изображение наноситься поверх основного слоя тайлов. В итоге деревья рисовал сам, а иные элементы позаимствовал у пользователя Joe Williamson (7) (отличный стиль). Собственно, на поиск подходящих тайлсетов у меня ушло два дня, те что в открытом доступе или очень низкого качества или стоят денег.

image

При количестве объектов около полутысячи начинается просадка. На этом бы всё и завершилось, но остался некоторый осадок по поводу производительности. Рассматривались разные способы оптимизации, и разбиение по ядрам и ограничение области прорисовки некоторым радиусом, изменение формы тени (на менее затратную, чем дуги), даже думал перенести расчёт на видео.

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

image

Эффект так понравился мне, что пришлось сделать масштабирование динамическим процессом, через заданный параметр кратности.

image

Оставалось только создать непрозрачные стены и представить результат сообществу.

image

Жду новых игр, использующих данный эффект или его развитие.
Спасибо!

Демоверсия где можно пощупать ручками (exe для виндовс).

Ссылки:

1) habr.com/post/16927/
2) ru.wikipedia.org/wiki/Roguelike
3) habr.com/post/204782/
4) habr.com/post/305252/
5) habr.com/post/319530/
6) wiki.freepascal.org/BGRABitmap
7) twitter.com/joecreates

Теги
Показать больше

Похожие статьи

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть