Как я создавал Fallout 2 (Diablo 2) для браузера

Когда-то я занимался веб-разработкой, но видимо это не совсем мое, и я решил оставить эту деятельность как хобби. Но напоследок я решил сделать что-то интересное. Мне всегда было непонятно, почему сегодня так мало креатива в браузерных играх, и вообще я не понимал почему до сих пор никто не сделал такие игры как Fallout 2, Arcanum или Commandos 2 в браузере, хотя технологии уже давно позволяют сделать такие игры (ну помимо сложностей с монетизацией). А так как мое детство прошло с такими играми, то я решил, что можно попробовать сделать что-то подобное.

Никаких знаний ни о спрайтах, ни о движках или разработке какой-либо игры у меня не было, только опыт веб-разработки: сайты, интернет-магазины и т.д..

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

Как выглядит поле из тайлов в изометрической проекции 
Как выглядит поле из тайлов в изометрической проекции 
Оказывается есть много видов изометрических проекций. И все они строго просчитаны. Все картинки и спрайты рисовались соответственно.
Оказывается есть много видов изометрических проекций. И все они строго просчитаны. Все картинки и спрайты рисовались соответственно.

Анимация спрайтов персонажа

Затем я нашел спрайты какого-то персонажа и попытался их анимировать, что-то начало получаться. Тут проблем не возникло, так как в CSS заложены такие механизмы. Причем важно, чем больше я углублялся в свойства CSS, тем больше я понимал, насколько там все уже предусмотрено. Просто не многие даже знают о наличие таких свойств, так как не сталкиваются с ними по работе с сайтами - там они просто не нужны (анимации и их кеширование).

Я специально нашел именно тот самый спрайт, которые я использовал в первый раз для анимирования персонажа
Я специально нашел именно тот самый спрайт, которые я использовал в первый раз для анимирования персонажа

В середине проекта я понял, что разработчики заложили абсолютно все свойства в CSS, чтобы создать такую игру, просто нужно найти их - это очень интересный момент, который я отметил. То есть мне нужно что-то сделать, я не знал как, но я уже понимал, что такие свойства там есть, просто они либо не распространены, либо в тестовом режим, но они есть. Когда люди делают сайты, они используют максимум 1/4 от того, что там заложено изначально разработчиками HTML и CSS.

Суть анимации в том, что во время бездействия персонажа запускается один спрайт, но как только персонаж начинает двигаться, мы должны подменить тот другим спрайтом, причем мы должны подменять спрайты в зависимости от направления персонажа и от его действия. Это все нужно как-то минимально закэшировать, так как спрайты - это изображения достаточно большие по размеру. А подменять мы должны при любом действии: шаг, удар, смена направления. И это все в реальном времени.

Алгоритм поиска кратчайшего пути (A*)

Cложности начались, когда я понял, что мало того, чтобы персонаж просто двигался. Если помните, то в старых квестах по типу Broken Sword персонаж просто двигался по фону и выглядело это немного неуклюже, но в изометрических играх персонаж должен ходить строго по клеткам. Я тогда не понимал, что это уже не просто свойства CSS или HTML. Я нашел, что в программировании за такие вещи отвечают алгоритмы поиска кратчайшего пути, например, A* Алгоритм. Я нашел какой-то, где точка на кроссворде просто искала путь из А в Б. Пришлось его переписать под свои задачи. Главное, что математически алгоритм работал и искал кратчайший путь, остальное - дело техники.

И вот мой тайл уже начал перемещаться из одной точки в другую. Суть алгоритма в том, что при определении конечной точки Б, из точки А по всем соседним точкам расходится "волна", которая проверяет все соседние точки на проходимость: либо -1, либо 1. И так по всем точкам, пока не находит кратчайший путь из А в Б. Довольно затратно для процессора, но об этом позже.

К сожалению я не нашел именно ту картинку, которая была в том маленьком примере алгоритма, который я "одолжил"
К сожалению я не нашел именно ту картинку, которая была в том маленьком примере алгоритма, который я "одолжил"

Я намеренно опускаю десятки всяких сложностей, такие как: определение активного тайла при клике, слои тайлов, сквозные клики, подмена слоев, так как на это уйдет очень много времени.

Так строится путь из точки А в точку Б. Красные тайлы - это найденный путь
Так строится путь из точки А в точку Б. Красные тайлы - это найденный путь

Я нарисовал поле, анимировал персонажа, заставил его передвигаться из точки А в точку Б. Потом мне нужно было заставить его поворачиваться в нужных точках. В нужный момент останавливать анимацию. Все это очень затратно с точки зрения производительности, поэтому я начал записывать весь будущий путь персонажа при клике по точке Б. Но что делать, если во время движения, мне нужно изменить направление, и путь должен пересчитываться?

Я понял, что я могу изменить конечную точку в любой момент времени при клике во время движения. И тогда нужно запускать механизм заново. А что, если персонаж встречает препятствие во время движения или другого персонажа. Значит, механизм должен проверять точки во время движения по всему пути движения в реальном времени. Звучит просто, но на самом деле: путь нужно определить, где-то сохранить для оптимизации, потом проверять точки, и при появлении препятствия, заново его просчитывать. А если персонажей много, и они ходят независимо друг от друга, при этом сталкиваясь друг с другом и так далее...

Чуть больше персонажей
Чуть больше персонажей
Случайные движения персонажей с определением препятствий

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

Положение объектов в пространстве

Далее я понял, что персонаж ходит просто по картинке, и как же тогда создать псевдо 3D. То есть персонаж должен заходить за стены, за заборы, но в то же время проходить перед другими стенами и препятствиями. То есть, если он заходит за дом - он должен исчезнуть, а перед домом появиться. А как это понять, и где хранить эту информацию. Я понял, что одной картинки недостаточно. Нужно картинку делить на объекты и расставлять их на уровне. Потом прописывать, какие из них в пространстве находятся перед персонажем, а какие за ним. Плюс, опять же это нужно определять в реальном времени, пока персонаж идет по пути, что опять же затратно с точки зрения производительности.

Мы просчитываем путь, все препятствия на пути и будущие исчезновения персонажа за преградами еще перед стартом движения, а при любом изменении при движении, снова пересчитываем это. По сути мы закладываем свойство Z-index на всем отрезке пути. Но это более менее понятно. А что делать, когда персонаж находится в помещении?

Я реализовал метод, который использовали в Fallout 2 в 98 году. Мы вырезаем область в объекте, за которым находится персонаж в реальном времени. И это не так просто при помощи Canvas и Html
Я реализовал метод, который использовали в Fallout 2 в 98 году. Мы вырезаем область в объекте, за которым находится персонаж в реальном времени. И это не так просто при помощи Canvas и Html
А вот как это делали в 98 году
А вот как это делали в 98 году

Динамические тени

К тому моменту я понял, что можно сделать все, что захочешь, если есть время. То есть все инструменты в браузере уже существуют. Главное понять, что использовать и как. Я решил сделать тени для персонажей. Кому-то может показаться это несложным, но поверьте, это не так. Нужно просчитать расстояние источника света до персонажа, подставить нужную тень, изменять ее положение в зависимости от движения персонажа и (или) источника света. Также, я сделал, чтобы тень удлинялась или укорачивалась в зависимости от расстояния для реалистичности. А теперь представьте, что если источников света много и персонажей также на карте много.

Удлинение тени при приближении
Удлинение тени при приближении

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

Но самое главное, я начал понимать, почему современные игры такие "статичные". Если вы внимательно посмотрите даже на современные игры, то на самом деле там очень мало обсчетов в реальном времени. Как я понял, современных процессоров до сих пор недостаточно для серьезных обсчетов . Движения и действия персонажей, спрайтовые облака с травой и несколько анимированных элементов на карте. Я лично никогда раньше этого не замечал, пока сам не столкнулся с этим, хоть и в более примитивной форме (В данном абзаце я выражаюсь очень любительским языком, и высказываю только свое мнение)

Статичные тени от солнца
Статичные тени от солнца
Тени от динамического источника света
Тени от динамического источника света
Тени от динамического источника света

Что такое движок?

Вы когда-нибудь задумывались, почему игровой движок называется движком? А вот я даже понятия не имел. Но когда я столкнулся с этим, мне кажется, что я понял. Я опишу свое видение, так как я понял это по своему, так как опыта не было, поэтому программисты должны отнестись к моим рассуждением с пониманием.

Как только я анимировал персонажа и научился искать путь из точки А в точку Б - все начало работать, но, как бы это объяснить попроще, все это работало в статике. То есть действие запускается только при моем участии, то есть при клике по клетке. Это очень важно. действия обсчитываются, путь просчитывается, спрайты анимируются, но все это - только при моем воздействии. А как же оживить игровой мир и других персонажей без моего участия? Вот тут начинается самая интересная часть.

Я понял, что что-то должно запускать все эти действия и проверять их автоматически без моего участия. то есть должен быть какой-то глобальный цикл. Я пришел к выводу, что нужен какой-то цикл (счетчик), но я не понимал, что именно мне нужно. Он должен выполнять все действия на экране не только по моему клику, но и автоматически. Внутри этого глобального цикла обсчитываются все объекты на экране и их движения, колыхание травы, тени, препятствия, облака. И пришлось все переписать под новое "открытие".

Этот глобальный цикл обсчитывает автоматически все пути и запускает анимацию всего на экране за единицу времени, например, за 1 секунду. И если мой процессор не сумеет обсчитать пути для, например, 100 персонажей на экране за эту 1 секунду, то произойдет лаг. Остается, либо увеличивать время цикла, либо уменьшать количество обсчетов внутри цикла. Почти как в современных играх. И я понял, почему это называется движком.

Кстати я не знал, как это реализовать, поэтому нашел тогда еще экспериментальную функцию - requestAnimationFrame. Она мне подошла. Потихоньку я реализовал это. Персонажи начали "сталкиваться" друг с другом. А при желании и обходить друг друга.

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

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

Пример преследования персонажа

Добавление разных функций

Далее было лишь дело техники. Все основные функции были готовы, и можно было реализовывать все, что угодно. Я сделал возможность персонажам наносить удары друг другу, а также имитацию стрельбы на расстоянии. Сделал триггерные клетки, при прохождении которых срабатывало определенное действие. Например, при заходе в помещение можно подменить фон или включить другой источник света или слой.

Далее я добавил разные фильтры к картинке и немного замылил ее. Вы знаете, что в CSS есть свойство по действию копирующее SSAO в современных играх? Оно отбрасывает небольшие тени от объектов для реалистичности. До сих пор не понимаю, зачем его придумали в CSS, и ресурсы оно жрет как в современных играх. Также добавил взаимодействие с объектами; можно открывать двери, сундуки, а также можно включать источники света. Ну и в заключении я сделал боевую систему, причем в реальном времени по типу как она была реализована в Diablo 2 только быстрее.

Простенький пример боевой системы

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

И конечно я совсем не описал серверную часть проекта, просто из-за объема статьи. Не стал описывать код PHP и Ajax запросы. Не стал рассказывать про базу данных, так как это относительно простая часть данного проекта.

Видео уровня 1
Видео уровня 1
Пример уровня 1
Пример уровня 1
Пример уровня 2
Пример уровня 2

Заключение

Как результат, на этом маленьком браузерном "движке" можно создать практически любую 2D игру из конца 90-х начала 2000-х годов. Изометрическую РПГ, стратегию или какой-то квест. Механизмы по сути везде одинаковые. Главное, что бы работали главные функции, и неважно перемещаете вы один объект или группу как в стратегии - это не так важно. Взаимодействие с объектами везде одинаково как в Fallout 2, Arcanum, так и в Broken Sword и Starcraft. Даже можно в принципе сделать Resident Evil 2 но только гораздо красивее и разнообразнее - в той игре все фоны были в статике, где была имитация 3D. Мое мнение, они и сейчас все "статичные" и не разрушаемые, просто в 3D. Ну это так, если с юмором.

В середине проекта я понял, что он, мягко говоря, будет не простым, но я не хотел бросать его в незавершенном виде. Делал я это все для себя и ради эксперимента, так как программирование я оставил в качестве хобби уже давно. В итоге, может быть, кого-то такой проект воодушевит сделать что-то действительно большое и стоящее. Также, может быть косвенно, я хотел показать молодым людям, что творчество - это совсем неплохо. Нужно не боятся экспериментировать, пробовать что-то создавать; не боятся пробовать делать то, что никто не делал до этого. И все получится. Удачи.

Видео с примерами уровней:

Все картинки в проекте были взяты не для коммерческого пользования, но только в целях развлечения. Я уважаю труд других людей, и тех людей, кто когда-то нарисовал их. Брал я их только лишь для теста.

6868
18 комментариев

Я дико извиняюсь, я не осилил весь текст. Может я упустил, но зачем вы это все силами чистого html и css делали? это же потом ещё надо для разных браузеров поддерживать!
Есть же такая штука как <canvas>

2

Не хочу преуменьшать ваши заслуги, реализовать изотермический вид силами html - это на мой взгляд достаточно не тривиально.
Но если ты выбрали рисование на <canvas> думаю у вас мог бы выйти очень аутентичный опыт, как у авторов оригинальной игры, т.к предполагаю во времена разработки оригинальных игр, графические библиотеки умели отрисовывать только базовые примитивы и линии.

1

Очень годно!

1

Ты, конечно, молодец, и работа реально крутая, но игры такого уровня в чистых CSS/HTML/JS не просто так не делают, ибо они в итоге будут лагать как сволочи, и далеко на этом не уедешь. Ну и плюс сверху ещё проблема, что твою игру в таком виде просто скачают и себе положат куда-нибудь, ни обфускации, ни скрытого кода, ничего, все исходники на руках в клиенте. Ивместо твоей игры с донатом будет чужая игра с донатом)

1

Не знал, что так легко. Выходил любую браузерную игру можна скачать и переделать под свою?

Выглядит здорово! Боюсь спросить: есть пример с несколькими источниками динамического света, и на какой лампочке все повиснет?)
(я наверно превратился в фанатика С3, но) есть ощущение, что этот проект можно без особых костылей прям вставить внутрь сцены Construct 3 и использовать тамошние функции для расширения функционала.