Эта история о том, как я делал очередную 3D веб-игру. История терзаний и сомнений, история недосыпаний и лени. История о том, как все сделать в самый последний момент. В общем, чего тянуть кота за рога – перейду к рассказу. Но сперва – еще кое-что, чтобы покончить со всеми формальностями. «Мадам, вам кофе в постель?» «Нет, лучше в чашку». Кофе был моим ежедневным спасением из лап Морфея, особенно когда я хотел поработать над игрой с утра, перед всеми остальными дневными заботами. Надо отдать ему должное. Я просто не могу не упомянуть о нем, так как для меня это стало воистину одной из составляющих успеха, под коим я понимаю доведение игры до релиза. (Здесь могла быть ваша реклама кофе).
Конкурс
На самом деле, я вписался не в тот конкурс, для которого мог бы что-то сделать. По его условиям требовалось создать игру под Windows, скачиваемую и запускаемую по exe-файлу. Я же люблю программировать под браузеры. Я не знаю C++ и C#, у меня даже нет Visual Studio. Но тут внезапно у меня появилась отличная идея игры, и я подумал – а какого черта! Буду просто делать игру. А получится ли соблюсти все условия конкурса или нет – не так важно. В конце концов, просто запущу ее в социальных сетях.
Нет предела совершенству
Моя предыдущая игра запускалась по php-файлу с сервера. На этот раз я решил пойти дальше. Мне захотелось, чтобы все работало не только в Интернете, но и из файловой системы, без веб-сервера совсем, даже локального. Во-первых, автономной версии требовали условия конкурса, а, во-вторых – чтобы можно было самому поиграть в пути, например, где нет Интернета. (Вообще, все, что я делаю, я делаю для себя. И сейчас играю в свою игру.) Но при необходимости – и с сервера тоже, с людьми, а не с ИИ, то есть, такой вариант тоже должен присутствовать. В общем, задача состояла в том, чтобы создать универсальную сборку со всеми скриптами, 3D моделями, текстурами и звуками для распространения как на странице в Интернете, так и в виде архива, который можно было бы скачать, распаковать и просто запустить игру по index.html. Конечно же, последний вариант имеет смысл только для однопользовательской версии.
По сути, вышеупомянутый локальный вариант представляет собой сохраненную веб-страницу с относительными путями. Загвоздка в том, что там содержится не только текст с картинками, но еще и 3D движок, а также, модели с текстурами. Впрочем, с самим движком нет никаких проблем. Это THREE.JS, он написан на JavaScript, весит 500 Кб и подключается просто тегом SCRIPT. Однако, движок не умеет загружать 3D модели из локальной файловой системы, а хочет тянуть их ajax-ом с сервера. Пришлось допилить загрузчик для этого, чтобы модели тоже подключались из файлов скриптов. Обратите внимание на адресную строку.
Первоначально созданные мной 3D модели для веб-версии хранились в файлах вида model.json, однако, я обнаружил, что при подключении их тегом SCRIPT все работает в Firefox, но в Chrome выдает ошибку, связанную с политикой безопасности. Решение оказалось простым: переименовываем json в js – и никаких проблем. Ну, наверно, можно было бы принудительно поставить нужный content-type, но кто знает, какие, там, в будущем еще появятся политики безопасности в браузерах. Вдруг они все равно не будут разрешать загружать скрипты из файлов, называющихся иначе, чем *.js, из локальной файловой системы. А так, js – и никаких вопросов. Загружай скрипт и не выпендривайся. Загрузка 3D модели:
var el=document.createElement("script");
el.type="text/javascript";
el.src=url+'.json.js'; el.async=true;
document.getElementsByTagName("head")[0].appendChild(el);
Также из-за политики безопасности браузера текстуры моделей, которые прекрасно вели себя в версии с сервером, наотрез отказались подключаться из своих файлов png и jpg, расположенных локально, поэтому пришлось их конвертировать в base64 и оформить тоже в виде скриптов, с именами файлов типа texture.png.js. Ну а эти скрипты подключать старым добрым тегом SCRIPT, динамически создаваемым. Это очень просто!
var image = document.createElement( 'img' );
image.onload = function() {
var tex = new THREE.Texture( image );
tex.needsUpdate = true;
tex.wrapS = tex.wrapT = THREE.RepeatWrapping;
tex.anisotropy = 5;
};
image.src="data:image/gif;base64,"+imagedata.replace(/^data:image\/(png|jpg);base64,/, "");
Работает так: мой парсер модели находит в ее файле ссылки на изображения, эти ссылки добиваются окончаниями .js, динамически подключаются скрипты с картинками текстур, картинки переводится из base64 и накладываются на модель. А для того, чтобы уж наверняка, железно, определить момент окончание загрузки скрипта, я каждый такой скрипт дополнил в конце вызовом функции, сигнализирующей основной программе о том, что он загружен и с содержащимся в нем изображением или моделью теперь можно работать дальше.
Средства разработки
Итак, теперь, когда загрузчик моделей и текстур нормально заработал с файловой системой, без какого-либо сервера, я начал писать сам игру. Да, я программирую на чистом JavaScript. Многие на этом месте покрутили пальцем у виска. Я не использую никаких библиотек (кроме 3D движка), даже JQuery не использую. ООП не использую тоже. Прототипы – в редких случаях, только тогда, когда, на мой взгляд, это действительно необходимо. Люблю минимализм и ничего лишнего. И никакого лишнего кода, в котором я не понимаю, что делает каждая строчка!
Открою страшную тайну. Я также не разбираюсь в новомодных менеджерах для сборки проектов, не пользуюсь гитхабом и ни разу не открывал никакое Visual Studio. Доктор, что со мной? Из средств программирования я использую только продвинутый блокнот. Я, наверно, псих.
Однако, мне кажется, что это очень просто – подключить на страницу скриптовую 3D библиотеку, работающую с WebGL, и создать сцену. Кто-нибудь еще сегодня подключает скрипты тегом SCRIPT и создает HTML разметку вручную? Кто-нибудь сегодня вообще нажимает на кнопки пальцами и смотрит в монитор глазами? Этими вот пальцами – по этим кнопкам? Этими вот глазами – в этот монитор? Дикость какая! Ладно, шучу. Но сегодня прикоснуться к HTML или, не дай бог, к чистому JavaScript – это как дотронуться до змеи. Лучше навернуть поверх несколько слоев. И после конвертировать код уже десятью этапами в JavaScript, недоумевая, почему в итоге ни черта не работает. Я никого не осуждаю, просто, мне кажется, что в веб-разработке наблюдается некоторый не совсем здоровый перекос в сторону использования многочисленных средств, менеджеров и библиотек. Надеюсь, мой рассказ не вызовет ни у кого приступ когнитивного диссонанса.
Техзадание? Чего? Самому себе? Диздок? Да ну, вот еще, тратить драгоценные минуты своей жизни на эту чушь. Единственное, о чем я пожалел – что сразу не нарисовал блок-схему алгоритма серверной части многопользовательской версии. В итоге, она заработала неправильно. Но, когда я все же нарисовал на бумажке блок-схему, то сразу увидел все свои ошибки и исправил их.
Творчество
Что мне нравится в индии-разработке, так это творческий процесс. Вообще-то изначально в рамках конкурса, я задумал написать вариацию на тему Color Lines и даже создал работающий первый уровень. Вот, как это выглядело.
Смысл был в том, что игра работает в 3D, а мячи сами не появляются на поле, их туда нужно бросать и составлять в линии одного цвета. То есть, необходимо попадать в нужные лузы в решетке. После чего под мячами открываются створки в полу, и они проваливаются. Но, когда был готов первый уровень, и я сам в него сыграл, то я вдруг понял, что игра получилась не особо интересной. И тогда я решил оставить это до лучших времен, пока мне в голову не придет идея, как улучшить это творение, что, может быть, добавить. У меня появилась другая идея. Бросание мячей вызвало у меня ассоциацию с полетом пушечных ядер. И я решил быстро переделать эту игру в морской бой. Благо, на написание конкурсного проекта отводилось целых два месяца.
Та-дам! Легким движением руки «Линии» превращаются… превращаются… превращаются… В элегантный «Морской бой!» И да, я просто не мог не создать «Черную жемчужину». Какой же морской бой без грозы морей.
3D графика – это, конечно, не моя стихия. Корабли сделал, как мог. С целью снижения нагрузки для определения столкновений я использовал невидимые коллайдеры вокруг каждого корабля. На один корабль — один коллайдер, примерно повторяющий форму.
Таким образом, получился пошаговый морской бой, но в 3D и с возможностью перемещать корабли в любую точку игрового поля. Смысл в том, чтобы, правильно настраивая углы поворота и наклона пушек, попадать ядрами в корабли противника.
Способы поделиться со всем миром
1. Конкурс.
Во-первых, конечно, конкурс. В процессе выяснилось, что организаторов конкурса не устраивает запуск игры по html-файлу и нужен непременно exe. Что там у нас со сборкой экзешников из js? Сначала я попробовал было Electron. Однако, там для меня все оказалось слишком сложно. Требовалось установить кучу каких-то менеджеров с гитхаба и произвести десятки манипуляций по настройке и сборке всего этого. Я так понял, что Electron хочет меня увести на путь стандартной веб-разработки. У меня не было времени с этим разбираться, так как шел последний день отведенного на создание конкурсной игры срока. Нет, это не годится. И тут я наткнулся на утилиту web2exe. Все очень просто – запускаешь ее, тыкаешь в файл html, она даже сама качает node-webkit, и получаешь на выходе сборку с exe-файлом. Что мне не понравилось – так это то, что файл оказался очень большой, 105 Мб. М-да… В то время, как сама игра весит всего 15 Мб. Однако, я нашел упаковочную утилиту, при помощи которой сжал самый большой файл nw.dll. В итоге вся сборка стала весить 68 Мб, а в zip-архиве 43 Мб.
Кстати, web2exe не поддерживает сборку «эезешника» под Windows XP, а жаль. Моя html версия работает и в XP тоже. Только в этой операционке есть некоторые особые требования для WebGL графики в браузерах.
Конкурс я не выиграл, но зато получил бесценный опыт решения, как мне казалось ранее, не решаемых задач. Теперь я уверен, что ничего невозможного нет.
Что ж, пойдем дальше, и будем выкладывать веб-вариант. После окончания конкурса я написал сервер на php. На это ушло около месяца свободных вечеров.
Да, кстати, сервер
Опрашивать сервер на обычном http хостинге слишком часто не получится. Поэтому будем опрашивать его раз в 10 секунд. На клиенте создается очередь из действий игрока, таких как движение корабля из точки А в точку Б, выстрел и т.д. И в подошедший момент времени отправки данных эта очередь уходит на сервер. Соперник ее читает и у него формируется очередь уже для проигрывания. Все то же: движение, выстрел и т.д., только, с его точки зрения, с кораблями оппонента. Таким образом, у каждого из двух игроков есть две очереди – одна для отправки информации на сервер, другая – для проигрывания действий противника. Тут, конечно, не будет никакой синхронизации, но, так как игра пошаговая, то она и не нужна. Главное – чтобы корабль противника стрелял в итоге из нужной точки, пришедшей с сервера. А маршрут до нее строит программа на клиенте, и в эту точку перемещает корабль.
Весь серверный скрипт на php, если его представить одной строкой, получился вот таким:
2. Фейсбук
Игра прошла модерацию чуть более, чем через сутки. Вообще, выкладывать что-либо в Фейсбук довольно бессмысленно, если ты не компания, специализирующаяся на ААА-играх. Заработать там на внутриигровых покупках простому смертному, назовем так программиста, работающего за идею, довольно сложно, а рекламу сторонних баннерных сетей Фйсбук не разрешает. Только разоришься на хостинге. Есть, правда, специальный список партнеров, преимущественно американских, через которых допускается размещать рекламу в своем приложении. Но для этого надо через их сайт отправлять им письмо с разъяснением того, чего ты от них хочешь и т.д. Не факт, что они согласятся работать с приложением какого-то парня из России. Да и на большинстве их сайтов, к тому же, я ни слова не обнаружил про Фейсбук… Тоже мне, партнеры. А еще вопрос уплаты налогов, как в США, так и в России. В общем, пробовать с ними договариваться мне как-то сразу расхотелось. И я просто сверстал свой небольшой баннер с названием игры и разместил в нижней части экрана. Его можно отключить, пожертвовав автору некоторую сумму через систему внутриигровых покупок.
3. ВКонтакте.
Модерация в ВК длилась несколько суток. И игру добавили, как обычно, в самый низ каталога. Зато в ВК можно сразу и без проблем подключать рекламный баннер. Схема монетизации такая же, как в Фейсбуке – отключение баннера. Надо отметить, что рекламная система в ВК очень удобная, простая и понятная. Личный кабинет – выше всяких похвал. Вот только заработать на рекламе там можно очень немного. ВК платит только за клики, а не за показы. И в некоторые дни на, примерно, 200 показов получаешь просто 0 рублей. Мало, кто вообще кликает по рекламе, видимо.
Итог
Отмечу, чего я для себя достиг. Теперь я могу писать однопользовательские игры с графикиой WebGL, запускающиеся по html-файлу как с сервера, так и из локальной файловой системы. Работает все это в обоих вариантах в Windows, Android и должно работать в iOs и Linux – но эти ОС у меня нет возможности проверить. Также, я могу делать из той же html версии более «тяжелые» сборки с исполняемыми файлами под Windows и iOs (в web2exe есть такая возможность). Весь код пишется один раз на JS и работает во всех вышеперечисленных вариантах. Ну разве что для онлайн варианта для соц.сетей нужно добавить еще серверный скрипт и клиентские обращения к api.
Комментарии (25)
zxcabs
26.10.2016 19:26Вся статья сводится к тому как идет борьба с загрузкой ресурсов и как потом все то упаковать в .exe, нет ни слова о создании игры. Картинку с совой прилагать не будут.
Kempston
26.10.2016 20:36Можно было привести код самой игры, но там все настолько банально. Например, о том, как создавать сцену в THREE.JS, есть множество статей. И так далее. Все же эта статья — не туториал по известным темам. Я хотел описать что-то новое.
SerhiyRomanov
26.10.2016 20:29Да, я тоже ожидал увидел хоть какие то детали самой реализации, но увы…
Ну Вы молодец! Я тоже давно мечтаю создать свою игру но все то времени нет, то лень. А эта статья немного дает вдохновение!
P.S. Если можно — ссылку на игру ВК, очень хочется поиграть.
Kempston
26.10.2016 20:37Спасибо.
Вот: https://vk.com/app5677746
guyfawkes
27.10.2016 00:41А исходники (или хотя бы архив для описанной автономной игры) покажете?
Kempston
27.10.2016 01:04По ссылке выше, в ВК, собственно, можно через браузер просмотреть код фрейма. Это и есть исходник, правда, обфусцированный. Оригинал, который у меня, показывать не хочу, да и код изобилует закомментированными кусками и моими комментариями, что и где надо еще доработать и добавить. Обфускатор все это, естественно, удаляет.
den5323
27.10.2016 00:58снаряды еще б побыстрей летали, и по динамичный бы
Kempston
27.10.2016 01:00Да, мне вКонтакте уже заметили, что маловато динамики. Думаю о том, чтобы сделать ускорение времени во время полета ядра.
Wolf4D
30.10.2016 15:35Если можно, то лучше — пропуск анимации с переходом к самому результату. А то навёл пушку изрядно выше цели, выстрел, уже на глаз виден перелёт — и следующие пять секунд ждёшь, пока ядро ме-е-едленно, трагично и печально завершит свой полёт. Хотелось бы в этот момент нажать пробел и пропустить полёт, перейдя, непосредственно, к его итогу :)
Kempston
30.10.2016 19:15С пропуском — очень здравая идея! Спасибо. Пожалуй, ее и попробую реализовать. Но для этого, возможно, придется отказаться от используемого физического движка, cannon.js, кстати, который делает покадровый расчет, и написать свою функцию движения ядра.
Zenitchik
30.10.2016 20:50Бросок камня под углом к горизонту, мне кажется, слишком простая задача, чтобы вообще использовать какой-то физический движок.
Kempston
30.10.2016 22:12В данном случае — да. Но хочется помучить физический движок ради универсальности, для последующих игр, в том числе, для упомянутой в статье недоделанной игры. В ней мячи летят, падают, остаются лежать, а брошенные новые могут также на них падать, сталкиваться и взаимодействовать. В этом смысле, желательно допилить существующий движок, чтобы он мог ускорять время. Пока мои эксперименты по ускорению приводили к изменению траектории, что неверно.
alek0585
27.10.2016 06:55Непонятно как считается урон. Кто-нибудь знает?
Kempston
27.10.2016 08:13Чем ближе к центру корабля, тем урон сильнее. То есть, в идеале ядро желательно «накинуть» сверху на центр палубы. Действительно, в игре об этом ничего не сказано. Подумаю, как донести это до игрока…
snovikov
27.10.2016 13:48а еще ничего не сказано как перемещаться :)
минут 5 потратил чтобы опытным путем выяснить это
cdmlex
27.10.2016 08:13Да, я программирую на чистом JavaScript. Многие на этом месте покрутили пальцем у виска. Я не использую никаких библиотек (кроме 3D движка), даже JQuery не использую.
Тоже так иногда пишу игры на конкурсы (если на JS), только с прототипами, и все в блокноте++ и браузере с его средствами отладки. Есть в этом какой-то дзен.
Поиграл, понравилось, модели кораблей симпатичные получились, жаль, что мало их. Даже подкинул автору один пиастр (больше не завалялось). ИМХО, Gui в игре немного перегруженный и его стиль соответствует духу игры.
Информация о кораблях отражается в воде, это баг или фича?
Не знаю, как так получилось, во время полета ядра вкладку переключил, оно застряло в воздухе, и стрелять больше было нельзя. Повторить глюк не удалось.
Kempston
27.10.2016 08:26Спасибо.
Безусловно, это баг. Воюя с не отправляющимися запросами к серверу при неактивной вкладке, я, по видимому, забыл повоевать с останавливающейся анимацией при этом. Мне тоже не удалось воспроизвести, но буду копать код в поисках понимания причин явления.
По поводу отражений. Это недоработка. Зеркальный шейдер тупо отражает весь мир. Но можно скормить ему лишь отдельные объекты. Я пока не придумал универсального способа, как это сделать. Дело в том, что для кораблей это тоже не помешало бы. Незачем отражать самые мелкие детали. Можно, например, исключать объекты тупо по black-листу по их именам. А имена задаются в 3d-редакторе. Добавить к ним какую-нибудь приставку или букву. При этом, под объектами понимаются меши ( mesh). В модели корабля их, кстати, порядка 40 вроде бы… В общем, это надо переименовать поля name и пересохранять заново для игры все корабли…
megavolt0
27.10.2016 13:46ИИ никудышный. Корабли врага стреляют строго по уже раненному. Таким образом отводим раненого как можно дальше и ставим его перпендикулярно линии стрельбы. Остальными кораблями перекрываем к нему проход. И все. ИИ промахивается в 90% выстрелов. Расстреливать корабли ИИ можно в упор.
HabraBabra
Молодец, че тут еще сказать!
Вартандер уже напрягся?)
Kempston
Не знаю, не играл. Но юмор оценил.