«Йо-хо-хо»-2 или перевозка рабов и трупов оппозиционеров
Я продолжаю работу над своей браузерной игрой про пиратов на Three.js. Пришло время добавить торговлю. Я подумал, раз у нас тут век пиратов, то и в политическом смысле обстановка должна быть весьма фривольной, то есть, без всякой богомерзкой демократии. А чего стесняться? Заходим в порт, а там нам, пиратам, сразу же предлагают приобрести пушечные ядра и выполнить заказы на транспортировку трупов оппозиционеров и доставку рабов. Еще можно отвезти ром на Тортугу или подбросить губернаторскую дочь на вечеринку на Райский остров. Или тайно эвакуировать беглого губернатора за хорошую плату. Действительно, для подобных дел правительству идеально использовать пиратов. В конце концов, рабы сами себе билет на регулярный рейс не купят, а оппозиционеры сами себя не похоронят. А проблему решать надо… Короче говоря, перевозить древесину и шелк, как в обычных играх — это скучно. Пусть будут этакие «девяностые», но в эпоху пиратов. Пираты выполняют не совсем законные задания правительства, а последнее закрывает глаза на их не совсем законные методы обогащения.
Какие бизнесмены, такой и бизнес…
В игре появилась местная газета. Для пиратов подписка бесплатная. Газета будет содержать различные игровые оповещения.
Интересный нюанс
В предложениях работы в портах появляются заявки по перевозке с одного острова на другой неких агента Икс и лорда Напального. Если перевезти агента Икс, то какое-то время спустя на том острове, куда он прибыл, произойдет властный переворот, а порту появится задание на перевозку беглого губернатора. Не бесплатно, конечно же. Если же перевезти лорда Напального, то чуть позже в порту его прибытия появится задание на вывоз трупов оппозиционеров, тоже за вознаграждение. И так эти двое будут путешествовать по всем островам и создавать для нас новую работу. А информация о том, что где-то произошло восстание или переворот, появится в газете «The Pirate Times». Визуально — изменит цвет иконка сообщение. Можно открыть и почитать. Ну и направиться за заданием в нужный порт. Только не перевозите этих двоих на один и тот же остров одновременно.
Замечание. Не все острова еще готовы. Некоторые из них представляют собой клочок суши, пальму и причал. Позже, в ходе создания миссий, они превратятся в полноценные локации.
Какие времена, такие и новости…
Еще одна чрезвычайно важная новость. «Черная жемчужина» превратилась в «Черную устрицу». Да. Так, определенно, лучше. Потому что я художник, я так вижу.
«Наша команда»
О нашем продукте я рассказал. Теперь — о нашей дружной команде, о наших ценностях и о корпоративном духе… Ладно, шучу. Игру я делаю в одиночку. А для того, чтобы не терять мотивацию, в эти жаркие дни я иногда выходил работать на побережье Финского залива. И, надо сказать, тишина и гладь, а иногда и шум моря неплохо мотивировали к написанию игры про морских разбойников. Вот мой офис.
Наш офис и корпоративные ценности
И тут меня понесло…
Когда я делаю игру, то, как правило, все начинается с атмосферы. Вот, кто-то вдохновляется придуманной им историей и создает визуальную новеллу, кто-то — главным героем и пишет игру про супермена, кто-то — коммерческими соображениями и выдает кликер на мобилку. А я вдохновляюсь атмосферой. Возможно, это глупо, но атмосфера в игре для меня — самое главное. Я, например, не люблю шутеры, однако, если в нем будет какая-то невероятно красивая локация, интересный живой мир, то я, скорее всего, буду очарован этой игрой и стану в нее играть. И это касается всех жанров. И наоборот, если в моих любимых автогонках будет некрасивая графика, неприятное управление или что-то еще, что мне не понравится, то я в это точно играть не буду, несмотря на то, что данный жанр мне в принципе близок. Так вот, игра про пиратов началась с того, что я собрал простую сцену, представляющую собой воду, небо и неподвижный кораблик. Мне понравилось, как это выглядит. И только потом я уже стал думать, как сделать из этого игру. «Корабль — это свобода. Смекаешь?» Если корабль — то пираты, острова, миссии, ну вы поняли… Идеи приходили по мере работы. Понимаю, что это неправильно, но я это делаю для себя и, возможно, для тех, кому это тоже понравится. О диздоке даже не спрашивайте: я еще понятия не имею, какой будет следующая, четвертая, из восьми миссий.
Еще об оптимизации
Теперь — о том, какие еще моменты мне удалось оптимизировать. Я начал эту тему в своей предыдущей статье. Продолжу.
Отражения
В игре, как вы понимаете, много воды. В прямом смысле. Ну, нет, вот не настоящей воды, а там — текстура, шейдеры, ладно… Собственно, это основная поверхность всех локаций. А в воде отражается все, что находится на суше. По сути, в воде еще раз рендерится вся сцена. Я подумал, а возможно ли отражать не всю сцену, а только отдельные объекты? Честно говоря, я не знал о слоях three.js. На информацию о них меня навел добрый человек на профильном канале в одном мессенджере, где я искал ответ на свой вопрос. Принцип очень простой:
//основная камера видит все слои до 1 (0, 1)
camera.layers.enable(1);
//камера зеркала видит только слой 1
mirrorCamera.layers.set(1);
//объект 1 виден во всех слоях до 1 (0, 1),
//то есть, в основной камере и в зеркальной
object1.layers.enable(1);
//объект 2 виден только в слое 0,
//то есть, виден в основной камере, но не виден в отражении
object2.layers.set(0);
Таким образом можно исключить отражение в воде отдельных объектов. Более того, можно даже оперировать отдельными мешами одного объекта. Например, незачем отражать разные мелочи, которые за рябью воды все равно не видно. Либо они вообще находятся внутри модели и их тоже совершенно незачем обрабатывать в «зеркальной» камере. Свойство layers имеется как в корневом объекте, так и в каждом его меше.
В воде не отражаются некоторые канаты корабля
А для того, чтобы не перебирать все меши всех объектов вручную с целью создать или не создавать их отражения в воде, я решил прямо в 3D-редакторе добавлять в имена мешей управляющие символы. Например, один из мешей корабля имеет имя «mrd_flo». Здесь «flo» — это пол (от floor), просто произвольное имя, а перед ним идет отделенный символом подчеркивания ряд управляющих символов. Например, «m» (mirror) — убрать отражения в зеркалах, «r» (receive shadows) — принимать тени: на пол должны отбрасываться тени от других объектов, «d» (double side) — двусторонний: этот пол должен быть виден как сверху, с палубы, так и снизу, из каюты. Другой меш имеет имя «md_wire». Здесь «wire» — просто имя (канат), «md» — не отражается в воде и материал его двусторонний. При загрузке каждой 3D-модели происходит перебор имен мешей и последним задаются все требуемые свойства.
Столкновение ядер со стенами
В предыдущей версии игры при стрельбе проверялись столкновения ядер с теми объектами, которые они могут поразить. Это — другие корабли, пушки, различные цели на берегу. Собственно, ранее я реализовал полезную возможность проверять столкновения ядра не со всеми целями, которые присутствуют на сцене, а только с теми, которые находятся вблизи от летящего ядра, перестраивая список ближайших объектов во время полета. Теперь несложно в этот список добавить и коллайдеры окружающих скал. Всего их в локации очень много, но ближайших к ядру в каждый момент времени окажется 2–3. Это не критично для производительности. Зато теперь ядра не пролетают сквозь скалы.
Сохранение игры
Теперь игру можно сохранить, а при следующем открытии браузера продолжить. Информация хранится в Local Storage. Но будьте внимательны: если вы очистите кэш браузера полностью (а не только последние файлы), то сохранение пропадет.
Геймпад
В десктопных браузерах теперь поддерживается управление с геймпада, правда, переназначать кнопки пока нельзя.
Разные мелочи
Я учел некоторые замечания по игре, оставленные в комментариях к предыдущей статье. Теперь направление движения корабля на карте показано стрелочкой. Улучшен алгоритм слежения за ядром во время стрельбы. Добавлен визуальный индикатор перезарядки пушки. Улучшено управление для мобильных девайсов. Стрелка вверху экрана теперь указывает на цель движения относительно угла поворота корабля, а не камеры. Появилась возможность вернуться в меню выбора миссий, а также повторно просмотреть брифинг текущей миссии. Добавлена третья миссия, в Мэтью Тауне.
Удивительные открытия
В процессе взаимодействия с библиотекой Three.js я обнаружил интересные нюансы.
1. Фон для Safari.
В Apple Safari канвасу, на который выводится 3D графика, нужно задать какой-нибудь цвет фона:
scene.background=new THREE.Color(0x000000);
В противном случае, старый кадр при реднере может не очищаться и объекты будут оставлять за собой шлейфы.
2. В процессе загрузки объекта меняется материал, если он содержит текстуру.
После загрузки текстуры Three.js заменяет материал, к которому, собственно, прикрепляется текстура. Я долго не мог понять, почему я не могу программно настроить материал по его uuid, если он уже загружен и присутствует в модели. Например, поменять его цвет или яркость или еще что. Вроде бы материал появляется, в нем можно менять значения, а эффекта не видно. Оказалось, я настраивал старый материал, который открепляется как только загружается текстура. А с ней создается новый материал и прикрепляется к модели вместо старого. Решение — изменять свойства материала только после загрузки текстуры.
3. Произвольный порядок следования мешей в сложный объектах
Несколько раз я наблюдал, как настройка, заданная мной программно для одного меша, применялась не к нему, а совершенно к другому. Это потому, что я делал так:
object.children[5].material.shininess=0.5;
Оказывается, меш, которому вы захотите задать какой-либо параметр, при очередной загрузке сцены не обязательно окажется с тем же порядковым номером (пять, как в примере). В children 3D-объекта он может стать абсолютно любым. Не знаю, связано ли это с тем, что Three.js подгружает объекты по частям и добавляет их к children, или еще с чем-то, но факт, что иногда (не всегда, даже очень редко) порядок меняется. Решение — искать child по имени, а не по индексу. Соответственно, предварительно при создании модели в редакторе, нужно озаботиться заданием уникального имени каждому мешу. Для парсинга пришлось написать функцию вида:
getObjectChildByName(object, 'meshname').material.shininess=0.5;
Продолжаем плавание
О том, как я буду продвигать игру, я еще особо не думал. Я разместил ее на некоторых сайтах (магазинах), где возможно размещение браузерных игр. Можно собрать исполняемый файл, чтобы разместить его в магазинах, подобных Steam. Больше я пока ничего толкового на эту тему придумать не могу. Впрочем, до релиза еще далеко.