Спокойствие, статья не заразная! К тому же её автор умер еще в прошлом году. Делать на том свете нечего, кроме как игры писать и публиковать сами-знаете-где.
Начало
Эта история началась благодаря Telegram-боту, приславшему мне стикеры со смешными корона-вирусами. Я 5 минут не мог оторвать взгляда от анимации и понял - надо сделать игру. Последние три года я делал игры на языке Lua и движке Corona SDK. Напомню, что Corona SDK — это кросс-платформенный 2D Фреймворк - из единого проекта он генерит приложения сразу на все iOS, Android, десктопы и ХТМЛ5.
И замечу, господа, движок очень шустрый для разработки, прототип игры делается за пару часов, три дня шлифовки и продукт залит в ГуглПлей или куда вы там заливаете.
Арт
Повторюсь, картинки взял из анимированных стикеров в Телеграмме — там они хранятся в tgs формате, в самом деле это переименованный gzip, он разархивируется в json-файл и выглядит примерно так:
{"tgs":1,"v":"5.5.2","fr":60,"ip":0,"op":180,"w":512,"h":512,"nm":"05_STAYHOME_OUT","ddd":0,"assets":[{"id":"comp_0","layers":[{"ddd":0,"ind":1,"ty":3,"nm"
Сгоряча, я стал было изобретать собственную программу для отрисовки json-а, но, обжёгшись, нашел в интернете телеграмм-бота #GIF Export Bot, конвертирующего tgs-стикеры в GIF файл. Каждая GIF-ка хранит примерно 100-200 фреймов размером 512х512. Редактирую её в GIMP.APP - скейл, трансформ, ну вы знаете, главное не залипнуть на редактировании арта…
Гейм и физика
В Corona SDK волшебный, невероятный box2D движок. Смотрите, как пишутся на нем игровое поле и герои
physics.start()
physics.setGravity( 0, 0 )
physics.addBody( downBox, "static", {shape=rect } )
physics.addBody( cvirus, "dynamic", {shape=octogon, density=2,
friction=1, bounce=0.2 } )
physics.setGravity( 0, 9.8 )
-- тут играем
physics.stop()
-- а тут уже не играем
В принципе, добавить что-либо к этому куску кода мне нечего — вы все поняли.
Сама идея игры
Помните хит Панда? Там (в поле силы тяжести) мишек-панд и мамочку разлучают цветные кубики. Их надо убить (кубики, а не мишек), но они противные множатся, как головы лирнейской гидры и не хотят соединения банды Панды. Цветные кубики в этой игре расположены на равномерной ортогональной регулярной сетке.
Лет пять назад Панда была бестселлером, даже я написал к ней ремейк Hex Rabbit. На такой же сетке, только шестиугольной и с такими же пандами, только кроликами. Я удивился, что кроликов до сих пор гоняют.
Hex-кроликов тогда нарисовал мне художник Андрюха Чесноков. Светлая ему память. Хотя он жив, слава Богу, идём дальше.
Итак, я подумал - а зачем вообще сетка в этой панда-игре? Уберем её, добавим физику. Злобные кубики заменим на вирусы и банду панд превратим в колобков. И поменяем геометрию главных героев!
Ко-вирусы против ко-лобков.
Гоняю колобков вторую неделю, жду когда надоест. Спасибо самоизоляции, в аду мне времени хватает.
Дебаг физики
Для отладки физики в Corona есть соответствующая функция, вот такая
physics.setDrawMode( "debug" )
В этом режиме схематично изображены элементы физического мира
Кстати, здесь был косяк — стенки стакана изначально задавались не очень высокими (три экрана) и на некоторых уровнях колобки вываливались наружу. Я недоумевал — куда они подевались? Наверное, до сих пор летят к центру Земли… Хотя нет, скорее всего, уже долетели с учетом сопротивления воздуха K_{air} = 0.75.
Выключаем debug-режим
-- physics.setDrawMode( "debug" )
и получаем арт
Редактор уровней
Изначально определил 12 уровней, думая постепенно увеличивать на доске число ко-вирусов и кроликов, которые #онижеколобки.
Рассуждал примерно так:
На первых двух уровнях живут только 2 вида злодеев. Зеленые самые опасные (с) ДМБ.
На следующих двух — посложнее, живут 3 вида злодеев.
Затем — 4 вида.
Пять — планету не спасти.
Ш(Ж)есть — импосибль.
С удовольствием бросился отлаживать игру и через 24 часа заметил скукоту на уровнях с 5-ю или 6-ю типами монстров. Сложно и думать надо.
Что делать? Эх, взял я лыжи, вышел на улицу, вернулся, заменил лыжи на палки и побежал в лес. На 19-ой минуте бега включились мозги и я резко понял — добавлю горизонтальных палок в поле игры! По типу перегородок в open-space. Так-то open-space — редкая гадость по жизни, а в игре норм.
Пошло дело — всё задышало, каждый уровень начал демонстрировать свой характер, пульс и температуру. Вы тоже проверьте! В смысле, температуру… Количество игровых полей в такой постановке, сами понимаете, бесконечно. Но счётно. Я ограничился дюжиной уровней и привлек сына-балбеса-третьеклассника к отладке, сунув ему в руку один из пятых iPhone. А он и счастлив, есть чем заняться на видео-уроке.
Вуаля, уроки сделаны, уровни отлажены и именованы местами на планете Земля, где мне было хорошо.
И хорошо бы эти места спасти.
Еще вариант - колобков сделать квадратными. Русский народный колобок и не такое стерпит.
Ой! А куда делись вирусы? Убрал… Гугл со зверской серьезностью относится к словам вирусы, инфицированы, вылечи… Пришлось редактировать мета-данные, ко-вирусы стали цветными монстриками, все умерли стали попробуй еще и пр.
Параллельно заменил Telegram-картинки. Эх, эх, прости Павел Дуров, ирония и смех не спасут мир.
Ошибки, без них скучно
Box2D иногда вылетал по ошибке в функции onGlobalCollision. Особенно на реальных устройствах, то есть айФонах. Смешно, но у меня ни одного Андроида в доме, а приложение в ГуглПлее…
Код выглядит предельно простым, а ломается, как девочка.
Отключаю звук — не ломается. Девочка, выходит, глухая?
Смотрим код:
local function onGlobalCollision( event )
if ( event.phase == "began" ) then
local ball = event.object1
local u, v = ball:getLinearVelocity()
local speed = u*u + v*v
-- print( "speed: " .. speed )
if speed>10000 then
if speed<20000 then
audio.play( rockSound )
else
audio.play( woodenSound )
end
end
end
end
Видите, в функции onGlobalCollision я ловлю столкновения колобков и проигрываю 2 типа звуков от соударений. Так вот, на некоторых уровнях синус равнялся двум сталкивались более 500 объектов одновременно (или одновременно? Гришковец). В этот редкий момент при вызове sounds внутри collision происходит страшный crash в runtime. <kznm! Не отключать же звук, верно?
Как пофиксить? Решил не вызывать напрямую play(sound) из функции onGlobalCollision, а копить число соударений в переменных boom1 и boom2. Затем в цикле runTimeLoop (эта функция вызывается в игре 10 раз в секунду, не пойми зачем) проверять boom1 и boom2 и играть звук при необходимости.
if boom1>0 then
audio.play( rockSound )
boom1=0
end
Ошибка изчезла, но звук стал неприятно ритмичным (оно и понятно, 10 раз в секунду)
Тогда я просто ввел time-delay шум. Рандом-задержка на 100 миллисекунд — и оппа! все стало звучать очень натурально:
local function play1()
audio.play( rockSound )
end
local function play2()
audio.play( woodenSound )
end
local function playSound()
local t = math.random(100)
if boom1>0 then
boom1=0
timer.performWithDelay(t, play1)
end
if boom2>0 then
boom2=0
timer.performWithDelay(t, play2)
end
end
Знаю-знаю, вы сами бы так сделали.
Google Play и YouTube
Подготовил скриншоты и снял ролик для ю-тьюба, за роялем — автор. Выглядит тормознуто, но я такой по жизни, это уже не исправить:
Видео записывал родной мак-программой QuickTime.app. Для Apple store полученное видео требуется отредактировать (установить frame-rate в 30). Для этого я обычно использую приложение handBrake.app, очень рекомендую.
Сервер
Любопытно наблюдать географию пользователей игры — для этого вытаскиваю из запроса игрока его ip-адрес, стравливаю адрес сервису api.wipmania.com, взамен получаю имя домена страны (например RU или UA) и показываю флаг фамилии игрока. География пользователей навевает мечтания. Не будем мечтать, а посмотрим на php-пример, как превратить ip -> country
$ipAddress = $_SERVER['REMOTE_ADDR'];
$ipCode = file_get_contents('http://api.wipmania.com/' . $ipAddress . '?k=Е3g-Y6ХренВамКодQrmGQ7');
if (strlen($ipCode)!=2) $ipCode = 'HN'; // да-да Гондурас
We don't allow apps that lack reasonable sensitivity towards or capitalize on a natural disaster, atrocity, conflict, death, or other tragic event.
Это не я, это гугл…
Html5 всех спасает - в нее можно сразу всем и сразу играть.
Напомню, что на Github-е у каждого пользователя есть возможность держать один бесплатный сайт — я потратил этот шанс для веб-версии игры по адресу https://papabubadiop.github.io/.
Предупреждаю, д-дизайн старый, нецензурированный. Нервных (и без юмора) прошу не запускать.
Чуть не забыл! Была еще одна браузерная ошибка. При запуске игры в Сафари получал дюжину одинаковых сообщений/предупреждений
libpng warning: iCCP: CRC error
Ошибка не критическая (жмешь десять раз ОК и играешь), но неприятная. Интернет уверяет, что виноват редактор картинок GIMP. Он б-бесплатный и сохраняет PNG файлы с неправильной контрольной суммой цветовой палитры. Советы из сети не помогли,
сними галку, конвертни
— не, не работает.
Исправил проблему тупо — открыл все PNG- файлы в приложении Preview (системное приложение Mac OS для просмотра и редактирования всех типов файлов), сделал два раза Flip и закрыл. Все картинки пересохранились в правильном формате.
Эпилог
Желаю вашим семьям здоровья. И терпения.
Cсылка на игру размещается в формате: "Страница игры в ГуглПлей: https://play.google.com/store/apps/details?id=com.gmail.papa..."