Новая игра со старой атмосферой на Three.js

в 19:41, , рубрики: canvas, javascript, nw.js, three.js, WebGL, игры, разработка игр

Существует множество поклонников старых игр. И они не прочь пустить скупую ностальгическую слезу и нет-нет, да сыграть в «Арканоид», «Пакмана» или «Принца Персии», как двадцать, тридцать, сорок или — подставьте нужное число — лет назад. DOS-box и эмуляторы — им в помощь. Да, что там, я недавно смотрел стрим самого первого 2D «Принца Персии» на Ютьюбе, где довольно молодой «стример» после прохождения очередного смертельного препятствия, смахнув со лба пот рукой, изрек: «Мне еще никогда не было так страшно в компьютерной игре». То есть, даже молодежь способна оценить хардкорность и крутизну старых игр.

Новая игра со старой атмосферой на Three.js - 1

Я подумал, а почему бы не создать новую игру в подобном стиле? Да, существуют различные ремейки и клоны. Также, радуют современные игры в стиле пиксель-арт. Однако, все они, как правило, повторяют квесты, механики и иногда вообще полностью левел-дизайн старых игр, по мотивам которых они сделаны. Ну либо, наоборот, предлагают совершенно новый сюжет и локации, являя собой просто визуальную стилизацию «под старину». А что, если представить, какой была бы новая часть старой игры, выйди она следом за последней из серии? Я решил такую создать.

Я взял 2D платформер и добавил туда 3D графику, сохранив, при этом, классический вид сбоку. Построил новые лабиринты, придумал новые квесты, ввел задания и реализовал слоты инвентаря. Слегка разнообразил ощущение пространства, добавив повороты на 90 градусов. Возможно, в те древние времена, перед тотальным переходом игр в 3D с тремя степенями свободы, нечто подобное вполне могло бы выйти.

Поскольку я увлекаюсь написанием игр под браузеры, то я решил сделать игру под браузер. В силу ее специфики, в ней нет какого-то бешеного количества полигонов, по крайней мере, отображающихся на экране одновременно, нет открытого мира. Поэтому все это не будет сильно нагружать браузер. Для вывода 3D графики я выбрал свою любимую библиотеку Three.js (WebGL) с собственной оберткой. Больше никаких других библиотек не используется, а код написан на чистом javascript.

Three.js и проблемы, с которыми я столкнулся

Разработчики графических 3D движков периодически, примерно раз в пару лет, выпускают новые версии своих творений. В случае со скриптовой библиотекой Three.js, разработчики радуют нас обновлениями с бешеной частотой — примерно раз в месяц. На момент написания статьи последняя версия имела номер 106. Казалось бы, это прекрасно. (Нет.)

Почему никто даже не задумывается об обратной совместимости? Когда разработчики Three.js переименовали свойство материала ambient, я понял — «Хьюстон, у нас проблемы». Ладно, допустим, мне не трудно было во всем своем коде поиском-заменой поменять слово «ambient» на «color». Но когда они исключили возможность источника света создавать тень без освещения как такового, я понял, что теперь уже посадке «Орла» угрожает реальная опасность, а база «Спокойствие» начинает медленно превращаться в базу «Бешенство». Однако, как выяснилось позже, это было еще полбеды…

Дело в том, что в моей игре, помимо запеченного света, используется также и динамическое освещение. Количество динамических источников на 3D сцене сильно влияет на производительность.

Новая игра со старой атмосферой на Three.js - 2

Я вынес в настройки игры выбор количества источников — от 1 до 7. И в зависимости от мощности видеокарты, можно попробовать разные значения.

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

Итак, Хьюстон, какие у нас проблемы? Докладываю.

Проблема номер 1: тени

Источники света создают тени. Я заметил, что тени от точечных источников (point light) потребляют намного больше ресурсов, чем от направленных (spot light). И это понятно: в случае точечного источника, тени отбрасываются во все стороны, а направленного — только в заданном конусе. Однако, я предположил, что, когда тени во все стороны не нужны, то можно применить сразу два источника: точечный будет создавать только освещение, а направленный — только тени в некоем заданном направлении. Это идеально подходит для моей игры и, как выяснилось, действительно, существенно экономит вычислительные мощности.

Новая игра со старой атмосферой на Three.js - 3

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

Но вот, в чем беда, начиная с версии three.js r73, направленный источник света больше не может только отбрасывать тень, он теперь всегда дает и освещение тоже. И свет от него распространяется внутри области, так же, как и тень. Убрать точечный источник и оставить только направленный нельзя: освещение мне необходимо во все стороны. А использовать оба источника, подогнав их под нужную яркость, тоже не получится: тогда объекты внутри конуса будут освещены заметно ярче. Использование же «честного» освещения с тенями во все стороны на производительности игры сказывается просто фатально.

А убрали нужное мне свойство направленного источника света .shadowOnly просто потому, что так захотелось автору движка.

Проблема номер 2: имена свойств

Я решил, что лучше останусь на версии r71, где нужное мне свойство освещения еще присутствует. Почему не r72 тогда? Ведь свойство исчезло только в версии r73. Потому что я уже написал довольно много кода для загрузки 3D моделей, анимации и физики под версию r71. А в версии r72 изменилось большое количество имен свойств: типа — shadowMap стало shadow.map и т.д. В общем, переименовывать все это мне тоже не хотелось. Да и разница между одной версией невелика. Итак, остаемся на версии r71.

Проблема номер 3: запекание теней

Новая игра со старой атмосферой на Three.js - 4

Запеченный свет в разных версиях движка выглядит тоже по-разному. Я не знаю, что они там с ним вытворяют, но при наложении на текстуру карты теней (shadow map) резко изменяется яркость и даже цвет. В общем, эту проблему я кое-как решил, подшаманив в GLSL коде движка и поставив автоматическую коррекцию цвета в загрузчике 3D моделей в тех местах, где используются карты теней. Это работает, естественно, только для версии r71. Для других версий придется использовать какие-то другие параметры. Это еще один повод остановиться на версии r71.

Новая игра со старой атмосферой на Three.js - 5

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

Хорошо. Направленный источник света создает только тень. Запечь и загрузить тени в сборку на версии r71 я могу. А теперь — самая главная засада. Персонажи.

Проблема номер 4: скелетная анимация

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

Новая игра со старой атмосферой на Three.js - 6

В последних версиях Three.js этой проблемы нет. Там я успешно создал анимированную модель персонажа в известном онлайн сервисе, сконвертировал ее в популярный формат gltf и загрузил на тестовую сцену. Однако, если в новых версиях Three.js используется формат gltf 2.0, то в моей поддерживается только 1.0. Казалось бы, в чем проблема, ну, сконвертируй в 1.0 и загружай. Оказалось не все так просто. Видимо, существует несколько вариаций формата gltf 1.0. Мне нужен был тот, где, помимо основного файла модели, должны присутствовать еще два файла с расширением *.glsl. Но и в этом случае, формат основного файла может варьироваться… В общем, мне не удалось найти конвертер, который бы удовлетворял всем параметрам, тем более, чтобы он еще и конвертировал из нового формата в старый. Допилить старую версию three.js до поддержки gltf 2.0 мне тоже не удалось: слишком глубоко в коде завязана эта поддержка, уходя корнями в математику, реализованную по-разному в разных версиях движка… В общем, с gltf как-то не сложилось.

Я попробовал использовать для 3D моделей формат .dae. В итоге, сама модель загружалась, но мне не удалось заставить корректно работать анимации персонажа. С текстурами также возникали проблемы.

Хорошо получилось с форматом .md2. Персонаж сразу отобразился и все анимации заработали правильно. Но, насколько я понял, md2 — это формат моделей для Quake 2 и его поддержку в Three.js кто-то реализовал просто по фану. А где достать модели в этом формате, а тем более, сконструировать и сохранить в нем своего собственного персонажа, я не нашел.

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

Базу «Бешенство» было впору переименовывать в базу «Отчаяние».

Уже почти отчаявшись и подумывая о переходе на новую версию движка в ущерб производительности (история о направленном источнике света), я решил в последний раз помучить формат json, с которого я, на самом деле, начал. Но в первый раз мне не удалось повторить в 3D редакторе Блендер процесс конвертации персонажа из файла примера, который поставлялся в комплекте Three.js, из исходного формата .blend в .json. Анимации портились и вели себя как-то хаотично, а с десяток вариаций экспортера из Blender в json все до единого срабатывали неправильно. Причем, я их испробовал еще и на нескольких версиях Блендера. Сам JSONLoader, то есть, загрузчик моделей в формате json, сейчас уже удален из three.js. Я решил поискать, на какой версии прекратилась его поддержка, чтобы взять его и образец 3D модели не из своей версии, а из той, где он еще был. Ей оказалась r88. И, о чудо! Мне удалось воспроизвести экспорт тестовой модели в r71 и все, включая анимации персонажа, заработало в игре нормально!

«Орел» благополучно прилунился.

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

Тогда я подумал — а что, если спросить у автора этого примера, как он это делал. Но докопаться до автора было нелегко. На Гитхабе у него нет никаких личных контактов. Поиск по нику и еще паре параметров вывел на его Твиттер, однако личные сообщения там были закрыты. Зато оттуда я узнал, что он — профессор какого-то американского университета, и зашел на сайт этого учебного заведения. Оказалось, что профессор занимался 3D графикой со своими учениками, используя свой пример в качестве методического материала. Тогда я вернулся на Гитхаб и просмотрел все его репозитории. Здесь меня ждал успех. Как я и предполагал, в отдельном репозитории хранился его пример. И не просто копия того, что я уже видел в примерах three.js, а пример, заботливо снабженный инструкцией (видимо, для студентов). Я скачал архив и, следуя инструкции, все повторил. Ура!

Это маленький шаг для человечества, но огромный скачок для одного человека и его игры!

Никаких проблем, Хьюстон!

Новая игра со старой атмосферой на Three.js - 7

Теперь я понимаю, что если я буду придерживаться этого формата, этих инструкций и этих версий Three.js, JSONLoader-а и Блендера и делать все единообразно, то смогу создавать и загружать в браузерную игру любых своих персонажей. Радовало еще то, что, несмотря на использование старой версии движка, можно использовать самую новую версию 3D редактора Blender и создавать любых персонажей с анимацией. Просто нужно потом экспортировать их по строго определенной схеме при помощи этого определенного инструментария.

Да, я заметил еще одну проблему новой версии Three.js: во время игры при скроллинге экрана почему-то наблюдаются постоянные фризы. И это не связано с усиленным потреблением ресурсов — процессор и видеокарта не загружаются на 100%. А в старом r71 такого безобразия нет.

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

Немного об игре

Название. Я назвал игру «Перси Ланкастер». Здесь все очевидно: «я художник, и я так вижу».

Как создавалась графика. Я создал в 3D редакторе две плоскости, расположил их на расстоянии одна от другой так, чтобы дальняя скрывалась за ближней, натянул на них текстуру камней, прорезал дырки в ближней, а затем убрал ненужные, то есть, невидимые, части. Затем смоделировал плоскости для полов и потолков. Так получился коридор для хождения игрока. Разнообразил локации различными текстурами.

Новая игра со старой атмосферой на Three.js - 8

В самой игре такого вида нет, там — только вид сбоку, и никаких черных пространств не заметно. Это просто общий вид коридора.

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

Новая игра со старой атмосферой на Three.js - 9

Со статической графикой, собственно, никаких проблем нет, она легко экспортируется в json из любого 3D редактора. Сложности, как я уже упомянул, возникли только с запеканием теней и анимацией персонажа.

Производительность. В разрешении экрана HD, то есть 1280x720, моя не самая мощная видеокарта GT-730 нагружается примерно на 35-40%, а процессор Xeon E5440 — примерно на 30%. Думаю, это более чем приемлемый результат.

ОС. Пока демоверсия доступна только под Windows в виде сборки на Webkit. В дальнейшем планирую запустить и браузерную версию. Я столкнулся с тем, что не во всех браузерах скроллинг экрана идет плавно. Мне нужно еще поработать над управлением и вызовом функции вывода графики. А пока я остановился на Webkit версии 26.0. Она мало весит и на ней все работает нормально.

Звук. Звуки частично взяты из бесплатных библиотек, частично сгенерированы. В общем, они пока сделаны, «чтобы было». Пока я над ними не сильно заморачивался.

Видео. Полное прохождение демосцены.

Планы

Я планирую выйти на краудфандинг, и на собранные деньги нанять 3D моделера, который создаст нормального персонажа и анимации для него. Все же, графика — это не мое. Но зато теперь я знаю, как внедрить персонажа в мою игру.

Также, планирую запустить браузерную версию под свежие версии Chrome и Firefox. Скажу по секрету, мне даже удалось запустить игру на MS Edge, но там почему-то пропадали объекты, на которые накладываются текстуры запеченных теней, я еще с этим не разобрался. Далее займусь отладкой под браузеры под Linux и Android и, если добуду у кого-нибудь для теста яблочные девайсы, то — также и под браузеры на iOS и MacOS.

В отдаленной перспективе — написание собственной библиотеки для работы с WebGL, наподобие Three.js: мне не нравится, что они резко переименовывают свойства и убирают нужный мне функционал. И наоборот, большинство из огромного количества возможностей, которые предлагает Three.js, мне не нужно.

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

Новая игра со старой атмосферой на Three.js - 10

Автор: Kempston

Источник


* - обязательные к заполнению поля