История участия в Game Jam. Snowbox

История участия в Game Jam. SnowboxВ конце 2017 года мне довелось проверить свои силы и энтузиазм в качестве участника одного из многочисленных мировых Game Jam’ов.

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

Под катом описание, как прошли интенсивные 30 дней разработки и медленные 20 дней ожидания результатов.

Примечание: статья носит повествовательный характер, с небольшим количеством технических деталей.

Пролог

Я давно хотел попробовать себя в качестве разработчика игр, а в последний год это желание становилось всё навязчивее и навязчивее. Под конец года я таки решился начать что-то делать — посещать meetup’ы по игровой разработке, читать книги и даже сделать небольшой прототип игры.

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

Так мы и решили принять участие. Два java-разработчика, совершенно без опыта в игроделании. Но когда-то же нужно начинать?

Подготовка

Выбранный Game Jam был тематическим, и тему организаторы должны были объявить строго перед началом разработки. На создание игры конкурсантам давался 1 месяц.

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

В час X организаторы объявили тему конкурса — Throwback (возвращение назад) и отсчёт пошёл.

У Селима было единственное пожелание к игре — наличие игрового сервера и обязательно real time. Ему было интересно познать прелести и сложности разработки серверной части. Также игра должна была быть простой, иначе мы могли не успеть её завершить.

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

И тут мне в голову пришла идея сделать игру-песочницу, при этом в прямом смысле — отсылка к детским развлечениям на пляже. Строим замки, бегаем и кидаемся в соперников песочком (надеюсь не у всех было такое тяжёлое детство), ну а недовольные родители за это наказывают. Все это в 2D с видом сверху.

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

Разработка. Неделя 1

Наши роли в проекте разделились сами собой. Как я уже говорил, Селиму была интересна только серверная разработка. А мне было интересно попробовать разработку UI, поскольку, на мой взгляд, она больше всего отличается от разработки бизнес приложений. Интерфейс игры планировался web.

Краткое описание использованных технологий

Клиент-серверное взаимодействие было полностью построено на вебсокетах, поскольку нам требовалось асинхронно и регулярно отправлять сообщения от сервера на клиент. Сообщения от клиента также передавались через вебсокет, но просто по причине «почему бы и нет». Все сообщения были в формате Json.

Для работы с вебсокетами на сервере был использован микро-фреймворк Spark. А в качестве физического движка использовался box2d, как один из самых популярных. Он отвечал за расчёт физики на сервере.

Клиент писался на чистом JS, без использования каких-либо фреймворков, поскольку я в них не силён, к тому же в этом мало смысла для небольшого проекта. В качестве игрового движка использовался Phaser 2 — молодой и перспективный JS движок. В отличие от физического движка на сервере, его главным предназначением была графика и простенькие физические расчёты. Также использовался KnockoutJS для data binding.

В качестве IDE использовались продукты от JetBrains: Intellij IDEA и пробная версия WebStorm (как раз 30-дневная версия, на время Game Jam).

Разработка началась с совместной встречи в выходной день. В первые 2 часа, я подобрал «временные» спрайты и реализовал бегающего мальчика, который умел кидаться снежками. Тут могла быть картинка «было/стало», но она лишена всяческого смысла — визуально что было, то и осталось.

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

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

Чтобы повторить успех первого дня (бегать и кидаться снежками), но уже через сервер, нам потребовалась неделя. На это в равной степени повлияло много технических факторов, в том числе проблемы с инструментами, плохо рассчитанными на нашу клиент-серверную модель взаимодействия. Хотелось бы остановиться на этой модели подробнее.

Модель клиент-серверного взаимодействия

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

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

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

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

  1. Клиент: нажата клавиша вверх;
  2. Сервер: игрок начал движение из точки [x, y1], под углом α, со скоростью ν;
  3. Клиент: отжата клавиша вверх;
  4. Сервер: игрок закончил движение в точке [x, y2].

У такого подхода есть как плюсы, так и минусы.

Минусы

  • гораздо больше логики на клиенте: клиент сам должен просчитывать движение, столкновения и т. п.;
  • логика на клиенте должна очень точно повторять логику на сервере, иначе получаются скачки в движении объектов. Далее в статье будут будет несколько примеров на этот счёт;
  • в случае сетевых проблем, пóзднее получение событий может привести к некорректным состояниям (выход за границы, прохождение сквозь объекты и т. п.);
  • в общем случае гораздо легче получить рассинхронизацию состояния на сервере и клиенте.

Плюсы

  • гораздо меньше нагрузка на I/O;
  • на события можно навешивать дополнительную логику. Например при начале/окончании движения можно запускать/останавливать анимацию;
  • сетевые проблемы меньше влияют на плавность движения. Пока снежок летит, задержка даже в одну секунду ни на что не повлияет, т. к. сервер ничего и не должен присылать;
  • API и само взаимодействие становится прозрачнее.

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

Разработка. Недели 2 и 3

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

Поэтому мы решили добавить бегающую девочку! Попутно, для девочки пришлось сделать окно выбора персонажа. А поскольку окно все равно пришлось делать, то грех не добавить ещё и ввод имени. К тому же, пришлось сделать растягивание картинок, по аналогу с 9-patch подходом.
image
Так выглядел первый вариант окна логина

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

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

Поэтому я реализовал простенький mock сервер прямо в клиенте. Многие вещи в нём были реализованы очень топорно, в т.ч. через использование глобального состояния игрового мира. Этого было достаточно, чтобы разрабатывать UI полностью независимо от сервера и сохранило мне немало времени.

У mock сервера был ещё один несомненный плюс. В нём были боты, которые не умели стрелять, поэтому был шанс хоть у кого-то выиграть.

Селим же в это время боролся с сервером. На сервере он подключил физический движок box2d для симуляции физики в игре. У этого движка много своих нюансов, и их изучение заняло немало времени. Самой большой сложностью при разработке было отсутствие визуализации игрового мира. Наш клиент отрисовывал только то, что было нужно. И некоторые элементы «серверного мира» были скрыты от клиентской части. К тому же не было полных гарантий, что клиент отрисовывает всё правильно.

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

Во время разработки столкновений мне запомнился один забавный баг, который мог претендовать на специальный функционал: когда игрок кидал снежок, его отбрасывало назад «отдачей». Это происходило, поскольку в box2d по-умолчанию каждый может столкнуться с каждым, и при этом всегда происходит отталкивание.

Эта проблема была решена введением масок, т. е. указанием классов объектов, которые не могут сталкиваться друг с другом. Например для игрока и его снежков, маска была сделана одинаковая.

Селим решил не тратить время на специальную обработку столкновений снежков друг с другом и пометил такое столкновение комментарием:
// for the unlikely event that we collide with a sibling snowball
Как показала практика, частота этого «маловероятного» события стремится к частоте кидания снежков, поскольку когда стоишь друг напротив друга, траектории снежков совпадают. Из-за этого чужие снежки постоянно отбиваются собственными. Наше мнение насчёт этого поведения разошлись, поэтому мы оставили как есть.

Пока Селим развлекался со столкновениями, я отлаживал синхронное движение объектов на сервере и клиенте. Были мелкие баги в нашей собственной реализации, но самый большой сюрприз преподнёс Phaser. В его физическом движке реальная скорость объектов подгоняется под FPS и остановки мира. Это делается, чтобы повысить плавность движений. К сожалению, такое умное поведение не согласовалось с синхронной работой относительно сервера и пришлось делать собственную реализацию перемещения объектов.

Описание специфики скорости в Phaser или «наперегонки с тенью»

С целью проверки и отладки скорости, я добавил на карту ещё один объект игрока, который двигался с условно той же скоростью, но рядом. Этот игрок-тень и нормальный игрок использовали разные алгоритмы перемещения. И таким образом я устраивал соревнования и сравнивал стабильность скорости.

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

Следующим шагом я попытался сам реализовать перемещение объектов, но от движка я взял контроль и дельту времени между каждым циклом мира. В Phaser есть несколько моделей времени и на одной из них как раз основана стандартная реализация скорости. Но, по неизвестной причине, не у одного из этих времён нет стабильности и они не могут обеспечить постоянную скорость. Это известная проблема, которая не планируется к исправлению в версии 2: github.com/photonstorm/phaser/issues/798.

На «соревнования» игрока и тени я потратил 2 дня и так и не нашёл рабочего варианта. Поэтому в конце концов я сделал полностью свою обработку скорости, основанную на стандартном времени в JS. Очень удивительно, что такая важная функция получила такую странную поддержку в движке и пришлось реализовывать собственный велосипед.

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

За неделю до сдачи, на встрече разработчиков мы даже не смогли показать нормальную версию и пришлось показывать mock сервер.

Разумеется, при такой скорости разработки нечего было и мечтать об изначально задуманном списке функциональности и тем более о nice to have вещах. Поэтому нам пришлось забыть о «песочнице» и остановиться на простейшей 2D стрелялке.

Разработка. Неделя 4, последняя

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

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

Из всего функционала, в эту неделю была добавлена только система очков, а также ограничение запаса снежков и их восстановление.

В последний день Game Jam нам удалось-таки немного поиграть с коллегами. Несмотря на общие положительные отзывы, эта игровая сессия получилась неудачной из-за критического бага — полёт снежков на сервере и на клиенте происходил с разной скоростью. Из-за этого попадание в других игроков было скорее случайностью.

Описание причины и исправления ошибки

Этот баг мы допустили в последний момент, уменьшив количество FPS на сервере с экспериментальных 1000 до 100.

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

Когда я стал исследовать этот баг, я обнаружил 2 закономерности:

  • движение игрока работало корректно и стабильно;
  • движение снежка на клиенте всегда было быстрее, чем на сервере.

Значение скорости объектов приходит на клиент от сервера, т. е. это не могло быть банальным несовпадением значений. Также обратное увеличение FPS до 1000 улучшало ситуацию.

Я провёл немало времени в попытках исправить эту ошибку. Но ничего не помогало. И в итоге причина нашлась при помощи гугления — box2d косвенно ограничивает максимальную скорость, перемещая объект не более чем на 2 пикселя за один шаг мира. Т.е. при 100 FPS максимальная скорость составляет 200 пикселей/c (п/c), а при 1000 FPS — 2000 п/c. Это значение указано в константе и изменить его динамически нельзя. Это полностью объясняло причину замедления наших снежков, т. к. их скорость должна была быть 700 п/c, что требовало стабильное FPS выше 350.

Для исправления этой проблемы я увеличил FPS до 500, но не просто так. В box2d в функцию шага мира нужно передавать сколько времени прошло с момента предыдущего шага. До моего изменения мы всегда рассчитывали эту разницу перед вызовом функции. Но теперь, зная желаемую частоту, можно было всегда указывать постоянную дельту в 2мс. При сильном отставании времени мира от реального, шаги нужно было повторять друг за другом, пока время мира не нагонит отставание. Затем немного sleep и всё по-новой.

Это исправление, как и ожидалось, решило проблему скорости снежков. К тому же у нас наконец исчезла проблема с несинхронным движением на сервере и клиенте. В то время это чудесное исцеление было для нас полной неожиданностью, но сейчас я понял причину: несмотря на максимальные 1000 FPS, никто не отменял медленной работы сервера и тем более сборок мусора. Т.е. в какие-то моменты времени FPS мог свободно падать ниже необходимых 350 FPS, что и приводило к произвольным скачкам скорости.

Итак, счастливые от закрытого бага и работающей игрушки, за 2 часа до окончания сроков, мы были готовы к сдаче. Оставалось только отправить игру на сайт проекта.

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

image
Скриншот с итоговой версией игры

Голосование

По условиям конкурса, сразу после завершения разработки, открывалось 20-дневное голосование. В этот период любой желающий мог посмотреть сданные проекты, коих набралось более 200. Голосовать за проекты разрешалось только участникам. Каждую игру можно было оценить в следующих категориях: общее, графика, звук, gameplay, инновационность и соответствие теме.

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

Мы старались организовывать игровые сессии через форум проекта. Также мы с Селимом периодически заходили в игру, в надежде развлечь скучающего одинокого странника. Это всё почти не давало никаких результатов.

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

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

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

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

20 дней, данных на оценку игр, тянулись очень долго, но в конце концов они завершились и пришла долгожданная пора результатов.
image

Таким образом, мы заняли 36 место из чуть более, чем 200 участников. Что с одной стороны неплохо для первого проекта, но с другой стороны несколько неприятно для самолюбия. Особенно учитывая, что в топ 10 попали неплохие игры, но далеко не все из них заслуживали какого-то особого внимания.

Вынесенные уроки

Ради получения уроков в полу-тепличных условиях это всё и затевалось. Многие вещи мы старались изобретать сами и ограничиваться минимальным набором инструментов, чтобы ощутить проблемы и испытать плохие подходы на собственной шкуре. Зато теперь имея багаж знаний о том, как делать не надо и почему, изучать теорию будет проще.

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

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

Не все библиотеки одинаково полезны. Почти никто не разрабатывает игры на собственном движке и на рынке есть движки на все случаи жизни. На дворе шёл 2017 год и можно было бы ожидать их высокого качества. Я выбрал Phaser как один из самых рекомендуемых и молодых JS движков. После всех тех проблем, которые у нас с ним были, я боюсь представить как выглядят движки похуже. Нет, в целом впечатления от работы с Phaser скорее положительные, особенно учитывая хорошую документацию и примеры. Но вот работать с ним, не зная большого количества нюансов, очень тяжело. Весной выходит новая версия и я надеюсь на существенные улучшение. А также в моих планах стоит мини-проект на каком-нибудь другом движке, чтобы сравнить.
И ту проблему с максимальной скоростью в box2d я запомню надолго.

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

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

(Game) Jam это отличный повод попробовать. При этом не важно что: идею, новую библиотеку, свои силы. Когда есть чёткая цель и твой результат должны увидеть другие, это очень мотивирует не раскисать и выкладываться по полной. И даже если результат получится хуже ожидаемого, его будет не жалко выбросить, вынести для себя уроки и двигаться вперёд!

Ссылки на ресурсы:

Всем спасибо за уделённое время и хорошего настроения!

 
Источник

Читайте также