Инди Pirate Parrot
2 834

Наша с девушкой первая видеоигра. Разработка на Unity. Часть 1

Ну как первая… Если не считать релизы только под Android и с десяток заброшенных проектов у финиша, то да, это первая наша игра с замахом больше чем на одну платформу. Как же всё начиналось? А всё просто, работали мы значит над другим проектом, назовем его «проект А», работали уже долгое время и решили, а не сделать ли нам за пару месяцев игру и потренировать на ней наши маркетинговые навыки, а «проект А» выпустим сразу после с большим опытом в продвижении игр. Но звезды не сошлись, петух не свистнул и «проект А» залег на дно ровно на год. Но эта история не о нем, а о логической игре под названием «Cubicity: Slide puzzle».

В закладки
Аудио

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

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

Возвращаясь к истории создания Cubicity, работаем мы в основном только на Unity и стандартный набор любого уважающего себя Unityразработчика здесь: Newtonsoft.json, Zenject, Cinemachine, Dotweenи т.д.. Как видели выше, первый прототип игры выглядел именно так, кубы и блинчики. После недели раздумий, как разнообразить игру и завлечь игроков, пришла гениальная мысль... Посмотреть на Asset store кубических или круглых персонажей. Ну и понеслась, несколько паков с персонажами были куплены без раздумий. Такая же ситуация произошла и с блоками по которым сейчас перемещаются персонажи. Также составили список новых элементов геймплея, со списка примерно в 30 новых плюшек, отобрали для начала нейтральные вещи, такие как: перенаправляющие блоки/стрелки, лифт и телепорт. Остальное решили оставить на новые уровни и внедрять их по одному в 30-35 уровней.

Честно, не можем вспомнить, что нас побудило на первых порах сделать так много уровней, но как есть, и в первый релиз пошло 95 уровней. Очень много на самом деле и мы не раз об этом пожалели. Почему пожалели? А потому, что игра была сырой и много чего изменялось по ходу. Приходилось довольно часто получать дозу «дня сурка», заходя в каждый из 95 уровней и внося изменения. На все уровни ушло 2 месяца непрерывной работы. Это не были уже на 100% готовые уровни, но очень близко. В продуктивные дни, 10 уровней не составляло особого труда переместить с головы на бумагу, а после и в сцену. Но были и те дни, когда чувствуешь себя Хенком Муди из Блудливой Калифорнии, переживающим творческий кризис, думаешь всё, иссяк, но наступает новый день и новые идеи.

Если говорить про визуальную составляющую, то тут все несколько сложнее. Отрисовка как и в большинстве игр проводится во вне экранную поверхность с разрешением меньше нативного и блитится в основную поверхность, но UI для четкости и читабельности рисуется без каких либо изменений в разрешении. Таким образом, мы получаем лучшее от двух миров – не размытый UI, но и не слишком прожорливый рендер в игре. Для сглаживания было экспериментальным путем выбрано 2x MSAA + FXAA, как те которые дают лучшую картинку при наименьших затратах ресурсов. Здраво рассудив, что логической игре ни к чему 60 кадров в секунду, мы решили не изобретать велосипед и установить лимит кадров в 30fps (чего уж говорить, даже консоли обычно этим занимаются). Установка лимита кадров позитивно сказывается не только на потреблении энергии, но и на нагреве телефона, что в свою очередь не дает телефону тротлить из-за перегрева.

Нелегкое решение ждало нас впереди, и это Finish points. Поскольку при каждом запуске уровня персонажи выбирались рандомно из доступных игроку, то рисовать какую либо миниатюру фигуры из персонажей было бы проблематично. Можете не верить, но именно эту задачу решали дольше всего и оттягивали на потом. Кубы на финишах не казались тогда столь жуткой идеей, и бумажная живопись помогала пройти уровень и довести каждого на свое место. После было принято решение вместо кубов использовать тех же персонажей но поменьше, стало лучше, но только для нас. Еще спустя несколько дней, этих персонажей развернули и подсветили, стало гораздо понятнее кто есть кто, но все еще не удовлетворительно. Окончательный вариант был принят еще через месяц, методом проб и ошибок, и еще пара недель уходит на создание иконок для финишей. До свидания лето, скоро с тобой вновь встретимся!

На наш скромный взгляд, тучи у нас получились довольно приятными на вид. Но по факту это простейший и не очень грязный хак. Когда только решили добавить тучи, то первая мысль была, сделать задник 360 видео. Этот подход не оправдал себя, так как для мобильных платформ желательно уместить игру в лимит размера для скачивания по LTE. Чтобы видео выглядело чуть лучше, чем отданное на растерзание шакалам сжатия, ему самому нужно было выделить 10-15 Мбайт, что в сочетании с наличием в игре ночных уровней со своими тучами, слишком много (весь конечный билд игры на Android занимает 61 Мб). Вторым желанием было написать свою систему для облаков, это было заманчиво как для разработчика, но как для человека, который хочет закончить игру поскорее это не подходило. Решение пришло в виде создания текстуры для облака и создания системы частиц с бесконечным временем жизни частицы, и также ограниченным количеством частиц в общем. После добавили случайные размеры между двумя константами на ряду со случайным вращением. Результат был более чем удовлетворительный – наше небо заполнилось облаками, которые были миловидны и не вызывали у нас желание плакать глядя на них.

Тени в игре (в мобильной версии) полностью состоят из квадов, которые просто расставлены вручную, так как не хотелось добавлять реальные тени в мобильную версию. Одной из причин является отсутствие мягких теней на мобильных платформах с OpenGLES 2.0, ну и конечно деградация производительности на слабых устройствах.

Как говорили ранее, для сглаживания мы использовали 2x MSAA + FXAA, но это еще не все! Также к нашему процессу пост обработки добавлен AmplifyColor – отличный ассет за свои деньги, позволяющий применять разные Lut-ы на пост обработке. При правильно подобранном lut, картинка становится лучше. В процессе разработки, мы пробовали разные подходы, включая стандартный unity post processing stack, но в билде его шейдеры и варианты занимали столько, что ни в сказке сказать, ни пером описать. Некоторые решения были очень красивы, но работали крайне плохо на телефонах не первой свежести (поверьте, если вы думаете, что у всех сейчас хотя бы ‘нормальные’ телефоны – вы ошибаетесь. Огромное количество людей, до сих пор ходят с китайцем за 40$ и жалуются вам в комментах, что на их микроволновке ваш DOOM не идет).

Баланс игры - это всегда не просто и даже сейчас всплывают мысли, а не слишком ли сложные уровни, а не часто ли сложные уровни выпадают и т.д. Отбалансив, как могли одной левой ногой, решили внедрить инструменты для облегчения жизни игроку (Ход назад, Бомба, Ледяной блок, Телепорт), и да, стало жить проще, но не нам, а только будущим игрокам. У нас же работы и багов прибавилось.

Добрались к меню игры, силы и нервы на пределе, творческая натура ударила по тормозам, и не будем утаивать, пришлось вдохновляться другими играми, за что им огромное спасибо. И вот «На утро вышла черепаха!». Не прямо на следующее утро, но вышла, UI был готов по предварительно созданным макетам.

Желание быть стильным, модным, молодежным не обошло и нас. Мы решили добавить облачные сохранения и в целом не пожалели об этом. Это не было самой простой задачей, так как на разных платформах, разные провайдеры облачного сохранения. На Steam - это Steamworks, для мобильных – GooglePlay и GameRoom. Так что пришлось унифицировать систему сохранения для возможности подмены для нужной платформы. Для начала мы решили использовать EasyMobile для этих целей, но увы, рано или поздно отказались от этой идеи. Плагин сам по себе хорош, и имеет огромное количество возможностей, но сама специфика работы с нативными облачными хранилищами нам не очень понравилась. Как результат выбор пал на Firebase Realtime Database и аутентификацию через Facebook. Если коротко, то пришлось пройти 7 кругов ада, чтобы это все заработало (и тут дело не в программировании, а скорей в 100500 настройках, которые нужно сделать в 100500 местах приложения и кабинетах в Facebook, Firebase и т.д.). Так же в базе есть лимиты по трафику и чтобы экономить его, мы каждый раз при записи создаем GUID и записываем его как в базу так и на устройстве. Таким образом если мы видим что GUIDы на устройстве и в облаке совпадают, мы можем быть уверенными, что не нужно вычитывать все данные из облака, а можно пользоваться локальной копией данных. В результате синхронизация была добавлена, но… Одним из самых странных для нас багов, было неочевидное поведение Firebase Database в некоторых случаях. Так как мы используем Json, мы сериализируем классы предназначенные для хранения состояния, но Firebase иногда ведет себя несколько странно.

Если мы передаем Firebase для записи объект-словарь, например такого вида:

var dict = new Dictionary<int, SlotState> { { 0, new SlotState() }, { 1, new SlotState() }, { 2, new SlotState() };

Когда мы будем считывать его из базы, мы получим не объект Json, а массив Json (What?)

Ну вроде, понятно, будем использовать везде списки и не будем испытывать проблем, да? Но не тут-то было.

Если мы запишем в Firebase:

var dict = new Dictionary<int, SlotState> { { 0, new SlotState() }, { 1, new SlotState() }, { 100500, new SlotState() };

Или даже:

var dict = new Dictionary<int, SlotState> { { 0, new SlotState() }, { 1, null }, { 2, new SlotState() };

Когда мы будем читать его из базы, мы таки получим Json объект с ключами и значениями.

Понять логику разработчиков в какой то степени можно, но это может привести к багам, которые возможно проявятся лишь через время (Помните про вышесказанные GUID добавленные для сохранения? Как результат редкие чтения из базы при относительно частых записях в нее).

Когда релиз? Этот вопрос слышали чаще всего. Но нужно было основательно подготовиться к этому дню. Составить список маркетов, выбрать дату релиза, избегать крупных распродаж, довольно много нюансов, из-за которых релиз сдвинулся, как минимум на 2 месяца. Послушав совета одной статьи, выбрали вторник и среду для релиза. Решили точно заказать обзор на 4pda, закинуть новость о игре на несколько форумов и бомбить соц.сети в частности Instagram (конечно же платно). Что из всего этого сработало, мы с вами узнаем во второй части этой истории, но уже позднее.

Что имеем в итоге? Создать быстро игру – это не всегда быстро. И не исключено, что ожидаемые сроки создания игры придется умножить на 5. Обзаведитесь людьми, которые смогут помочь вам дельным советом в незнакомых вам отраслях. Расслабляйтесь при любой возможности, так как создание чего либо, не только игр, забирает много сил. Не гоже подбираясь к релизу быть вялой сосиской и быть менее полезным, чем на старте проекта. Ну и деньги, ищите деньги, они вам понадобятся. А от нас, спасибо за внимание, удачи и до встречи в следующей статье.

Материал опубликован пользователем. Нажмите кнопку «Написать», чтобы поделиться мнением или рассказать о своём проекте.

Написать
{ "author_name": "Pirate Parrot", "author_type": "self", "tags": [], "comments": 14, "likes": 64, "favorites": 76, "is_advertisement": false, "subsite_label": "indie", "id": 42278, "is_wide": false, "is_ugc": true, "date": "Fri, 08 Mar 2019 21:39:04 +0300" }
{ "id": 42278, "author_id": 121888, "diff_limit": 1000, "urls": {"diff":"\/comments\/42278\/get","add":"\/comments\/42278\/add","edit":"\/comments\/edit","remove":"\/admin\/comments\/remove","pin":"\/admin\/comments\/pin","get4edit":"\/comments\/get4edit","complain":"\/comments\/complain","load_more":"\/comments\/loading\/42278"}, "attach_limit": 2, "max_comment_text_length": 5000, "subsite_id": 64960, "last_count_and_date": null }

14 комментариев 14 комм.

Популярные

По порядку

Написать комментарий...
9

Статью не читал (некогда просто), но скриншоты "цепляют".
Выглядит интересно.

Ответить
0

читать было лень, но потом я заметил кнопку - аудио

Ответить
5

За желание сделать игру: 5+
За писательский труд: 3-. Отстуствует структура статьи. Было бы намного легче читать если бы разбили по разделам: введение, геймплей, сюжет, текущий статус, планы на будущее. Непонятные куски кода, в то время как вся статья описательная, какие то технические вставки про 2x MSAA + FXAA, и т.д. Но так и не понял в чем гейплей.
За попытку давать советы (при непонятных сроках релиза текущего проекта, и брошенному проекту А): ну как то неэтично что ли...

Ответить
1

Спасибо за критику. В этой статье постараемся попаравим всё о чем вы сказали. В следующий раз учтем и будем работать над этими недостатками. )

Ответить
3

Читну и вторую статью, если не пропущу, а так навык писательства хромает.

Ответить
0

Спасибо. В следующий раз постараемся лучше написать)

Ответить
2

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

(это вы в прошлом сделали то, что могли бы совершить в неопределенном будущем :D).
Имеет смысл выстраивать структуру текста с заголовками, а финальный вариант отдавать на вычитку более грамотным знакомым.

Ответить
0

Спасибо за критику, в следующий раз поработаем над текстом лучше )

Ответить
1

"и стандартный набор любого уважающего себя Unityразработчика", а остальные разработчики значит себя не уважают?

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

Возможно я ошибаюсь и они действительно облегчают жизнь.

Ответить
3

Здравствуйте! С радостью отвечу на этот вопрос:
Скорее не уважают свое время. Большая часть вещей может быть реализована самостоятельно, но нужно ли?

Давайте по порядку:
Newtonsoft.json - сохранения. Можно было бы изобретать велосипед, но этот плагин - золотой стандарт.

Zenject - если речь заходит о проекте который будет работать в разных условиях и на разных платформах, то DI как явление, вещь незаменимая. Возьмем к примеру достижения. Если вы, например, как мы релизитесь на steam и на мобильные платформы, вам нужно каким то образом выдавать игроку достижение через некое API которое в общем случае не совместимо с другими. Тут приходит на помощь zenject. Есть интерфейс назовем его IAchievementAPI, имплементируем от него SteamAchievementAPI и MobileAchievementAPI и в зависимости от платформы инжектим нужный. Это лишь одна маленькая и простая часть где можно было бы обойтись без того же Zenject, но было бы не так красиво и удобно.

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

Про Dotween смысла много расказывать думаю нет. Он делает в целом жизнь гораздо проще, а интеракцию с UI и подобными вещами более красивой (и вцелом не слабо очищает код view).

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

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

Надеюсь ответил на ваш вопрос :)

Ответить
0

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

Ответить
1

Молодцы! Удачи в новых проектах)

Ответить
0

Если не считать релизы только под Android

Почему не считать? Чем они принципиально хуже, к примеру, этой?

Ответить
0

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

Ответить
0

Прямой эфир

[ { "id": 1, "label": "100%×150_Branding_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox_method": "createAdaptive", "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfl" } } }, { "id": 2, "label": "1200х400", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfn" } } }, { "id": 3, "label": "240х200 _ТГБ_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fizc" } } }, { "id": 4, "label": "240х200_mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "flbq" } } }, { "id": 5, "label": "300x500_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "ezfk" } } }, { "id": 6, "label": "1180х250_Interpool_баннер над комментариями_Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "clmf", "p2": "ffyh" } } }, { "id": 7, "label": "Article Footer 100%_desktop_mobile", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjxb" } } }, { "id": 8, "label": "Fullscreen Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjoh" } } }, { "id": 9, "label": "Fullscreen Mobile", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fjog" } } }, { "id": 10, "label": "Native Partner Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyb" } } }, { "id": 11, "label": "Native Partner Mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyc" } } }, { "id": 12, "label": "Кнопка в шапке", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fdhx" } } }, { "id": 13, "label": "DM InPage Video PartnerCode", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox_method": "createAdaptive", "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "clmf", "p2": "flvn" } } }, { "id": 14, "label": "Yandex context video banner", "provider": "yandex", "yandex": { "block_id": "VI-250597-0", "render_to": "inpage_VI-250597-0-1134314964", "adfox_url": "//ads.adfox.ru/228129/getCode?pp=h&ps=clmf&p2=fpjw&puid1=&puid2=&puid3=&puid4=&puid8=&puid9=&puid10=&puid21=&puid22=&puid31=&puid32=&puid33=&fmt=1&dl={REFERER}&pr=" } }, { "id": 15, "label": "Плашка на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byudo", "p2": "ftjf" } } }, { "id": 17, "label": "Stratum Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fzvb" } } }, { "id": 18, "label": "Stratum Mobile", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fzvc" } } } ]
Новая игра Ubisoft на релизе выглядит
точно так же, как и на E3
Подписаться на push-уведомления