Идея игры
Из существующих на тот момент игр нам понравилась игра, где катается шарик по лабиринту, уворачиваясь от дырок, и пытаясь добраться до финальной лузы. На тот момент таких игр уже было несколько штук, но по части графики они были не очень. Поэтому мы решили сделать свою, с упором на красочность. Хочется отметить художника, рисовал он очень и очень классно. Благодаря ему игра получилась такой как есть — красивой.
В качестве движка было решено использовать libGDX. Я хорошо знал его на тот момент, и он мне нравился, уже были завершенные проекты на нем. Я был уверен, что мы с ним дойдем до победного конца. Как оказалось — дошли.
Особенности геймплея
В принципе, геймплей получился стандартным для игр такого типа — катаем шарик с помощью акселерометра. Дополнительная фишка — у нас есть телепорты, а стены могут быть не просто скучными прямоугольниками. Но об этом дальше.
Из особенностей хочется отметить управление. Мы долго подбирали настройки акселерометра для комфортной игры, но наш тестер постоянно отмечал, что что-то не так. Как образец он приводил похожую игру с Android Market. В конце-концов, я скачал эту игру, декомпилировал (еще удивился, что она была не обфусцирована), и посмотрел момент с управлением. Я обнаружил, что код идентичный моему, отличаются лишь коэффициенты. Отмечу, что декомпилированная игра была тоже написана на libGDX — то есть, в то время на нем уже писали массовые игры.
У нас можно настроить управление
Техническая часть
Как я уже упомянул, игра написана на libGDX. В игре используется множество ресурсов — графика, звуки, карты, шрифты. Пройдемся по каждому моменту.
Графика. В игре есть несколько сотен спрайтов. С целью производительности, все спрайты были упакованы в текстурные атласы. Отдельный атлас для экрана меню, отдельный — для выбора уровней и т.д. Стандартная техника, которая экономит и видеопамять, и вытягивает производительность. Атласы упаковывались программой Texture Packer (доступная для скачивания на сайте libGDX). Если бы я делал это сейчас — я бы использовал встроенный класс TexturePacker. Упаковка атласа выглядит примерно так — TexturePacker.pack(«folder-with-sprite», «output-folder», «atlas-name»). Преимущество такого подхода — не нужно переключаться из IDE на другую программу, чтобы перепаковать атласы.
Вот так выглядит уменьшенный атлас с разными шариками. Это малая часть, у нас несколько таких атласов.
Формат атласов — 32 бита, png. Некоторые картинки, что без прозрачности, упакованы в jpeg атласы. Если бы я делал сейчас — атласы без прозрачности я бы паковал в ETC1. У libGDX есть родная поддержка этого формата, а для Android это экономия видеопамяти.
Для управления графикой (загрузка\выгрузка атласов, получение нужной картинки) был написан свой самопальный класс Images. Я знал про встроенный AssetManager, но на тот момент мне нужно было более удобное решение. Например, если я хочу получить какую-то фоновую картинку, я хочу, чтобы предыдущий запрошенный фон был автоматически выгружен. Мое решение позволяло делать такие вещи.
Отходя немного в сторону — когда я пишу игру, я часто делаю свои менеджеры ресурсов. Часто на базе стандартного AssetManager. Цель — более простой доступ к ресурсам. На более поздних стадиях разработки читаемость кода играет очень важную роль. А хорошо написанный код, как книга. Проще понять строчку images.createButton(«green-button»), чем assetManager.get(TextureAtlas.class, «data/atlases/buttons»).findRegion(«green-button»). Из минусов такого подхода — когда возвращаешься к игре через длительное время, нужно вспомнить особенности этих менеджеров. Из плюсов — когда вспомнишь, дальше править что-то уже просто.
Звуки. Все звуки в игре в формате .ogg. Почему не mp3? На некоторых моделях телефонов на тот момент были проблемы с воспроизведением нескольких звуков подряд в формате .mp3. Я допускаю, что это были мои кривые руки, но с .ogg проблем не возникло. Благо libGDX поддерживает оба формата, замена .mp3 на .ogg была несложной.
С целью оптимизации звуки были пожаты в минимальный битрейт, формат — моно. Разработка велась на linux, для конвертации использовал Sound Converter. На мой взгляд, Sound Converter — это образец отличной утилиты, которой я пользуюсь до сих пор. Одно маленькое окошко с настройками для выходного формата, и кнопка «Convert». Отличный ответ монструозным конвертерам.
Шрифты. В игре используются два типа шрифтов.
Первый тип — это .ttf шрифты и библиотека gdx-freetype. Она позволяет во время исполнения генерировать из .ttf шрифтов растровые картинки (bitmap fonts). Преимущество библиотеки — можно задать нужный размер шрифта прямо во время запуска программы. Это гарантирует, что шрифт всегда будет именно того размера, что нужно. Из недостатков — поскольку шрифт генерируется «на лету» в текстуру, это значит, что это будет есть оперативную память. И чем больше размер шрифта, тем больше ее нужно, этой самой памяти. Еще один неочевидный момент — поскольку шрифт — это отдельная текстура, то при отрисовке любой надписи будет дополнительный draw call (за исключением момента, когда мы рисуем несколько надписей подряд). Исходя из этого, область применения .ttf была ограничена лишь экраном «About».
Второй тип шрифтов — это distance field шрифты. Идея простая — с помощью программы Hiero (доступна для загрузки на официальном сайте libGDX) генерируется специальная текстура для шрифта. В текстуре для каждой буквы выделена своя область. Но эта область не отрисовывается «как есть». Вместо этого применяется специальный шейдер, который особым образом рисует этот регион (конечно, это тоже ломает батчинг). Преимущество — с относительно маленькой текстуры можно отрисовывать шрифт очень большим размером без потери качества. Недостатки — шрифт нужно заранее сгенерировать, и для шрифта недоступны эффекты (тень, обводка, и т.д.). Также, как я упомянул, это ломает батчинг. Можно лишь во время отрисовки устанавливать цвет шрифта. Для наших потребностей этого хватало, поэтому в игре повсеместно используется именно Distance Field шрифты.
Вот так выглядит Distance Field шрифт. Фон должен быть прозрачным, я лишь для наглядности сделал его черным.
Карты. Одним из «фишек» хорошей игры является количество контента. Можно генерировать его автоматически, а можно подготовить наборы уровней. Мы пошли вторым путем — у нас больше сотни уровней. Уровни разбиты на коробки — в коробке 15 уровней. Понятно, что с таким количеством уровней возникает вопрос их удобного редактирования и хранения. Нужен какой-то редактор карт. Дело усложняется тем, что у нас есть два типа карт — простые и «оригинальные» (во время разработки мы называли их именно так).
Простые карты — это стандартные для этого типа игр лабиринты с прямоугольными препятствиями. Для редактирования таких карт я написал редактор — простенькая Java-программа с использованием Swing как графической библиотеки. Вручную можно расставить препятствия, дырки, и т.д. Препятствия можно вращать, изменять размер как угодно — в этом плане редактор получился довольно удобным. Тестер жаловался, что неудобно, что нельзя отменять действия — я его понял, и написал отмену\повторение действий, изучив паттерн Команда.
Редактор карт в действии
Простая банальная карта — ничего необычного
Фишка — чтобы побыстрее тестировать карты, в редакторе была возможность переключения в игру. Сразу (в этом же окошке) запускалась игра, где можно было проверить карту. В роли акселерометра выступала мышь. Могу сказать, что написание редактора — это как та фраза, «лучше день потерять, потом за пять минут долететь».
Оригинальные карты. Так мы называли вручную нарисованные художником картинки, где отдельные элементы картинки — это препятствия. Ниже — пример такой карты.
Футбольное поле — уже интересней
Понятно, что стандартный редактор карт не подходит в данном случае. Я подумал, и решил проблему следующим образом — берем редактор tiled. В нем создаем два слоя — один фоновый, там будет картинка оригинального уровня. Второй слой — это слой разметки. На нем примитивами (кругами, квадратами и полигонами) размечается карта. Например, дырка — это круг с именем «hole» (tiled позволяет каждому объекту назначить свое имя). На выходе мы получаем xml-файл в формате tiled. Но нам этот формат не подходит. Поэтому я написал дополнительную утилиту, которая берет сгенерированный tiled xml, и конвертирует в мой формат уровня. На этом же этапе выяснилось, что box2d не поддерживает полигоны, которые состоят более чем из 4-х вершин — проще говоря, четырехугольники (по крайней мере порт box2d для libGDX).
Размечаем оригинальную карту
Весь этот «Франкенштейн» хоть и выглядел неуклюже, но свою задачу выполнил на отлично. Почти половина игровых уровней «оригинальные», и благодаря tiled мы обошлись без написания еще одного редактора. Вывод — иногда не нужно писать свое что-то сложное и большое, достаточно оглянуться по сторонам, и взять готовое решение с минимальным допиливанием «под себя».
Анимация шарика
Когда шарик движется, он должен как-то анимироваться, создавать иллюзию движения. Некоторые игры этого жанра не заморачиваются с этим, и используют простую статическую картинку. Мы решили заморочиться.
У нас есть несколько «скинов» шарика — стеклянный, огненный (лава), футбольный и т.д. На тот момент я был не в курсе возможностей 3D в libGDX, и использовать 3D-модельку не мог. Поэтому решили делать покадровой анимацией. Полный оборот шарика у нас занимал порядка 50 кадров. В зависимости от скорости движения я менял частоту смены кадров, создавая иллюзию более быстрого или медленного вращения. Ну и конечно менял поворот этих кадров. Не могу сказать, что вышло идеально, 3d модель однозначно дала бы более красивую картинку, но что есть, то есть.
Физика
В игре используется физический движок box2d (точнее, его порт под libGDX). Из плюсов — он быстрый, и возможностей у него много. Из минусов — нужно его знать :) Шарик — это динамическое тело, препятствия — статические тела. Когда загружается уровень, создается физическая модель мира, и, собственно, начинается игра. При паузе симуляция останавливается. В принципе, это все, что можно сказать про физику — особых проблем с ней не возникало.
Дополнительные фишки
Экран меню у нас — непростой. Он тоже выполнен, как часть игры. По нем катается шарик, слушаясь акселерометра. А когда шарик ударяется о кнопки, он отскакивает. А если коснуться шариком определенных кнопок в определенном порядке, откроется портал в секретный уровень :)
Шарик катается и отскакивает от кнопок — как в пинболе
Есть много достижений — за прохождение уровней, за определенное количество выигрышей, за определенное время в игре, и т.д. Мы не использовали стандартные Android Google Play достижения, а сделали свою систему достижений.
Малая часть достижений
Моменты, которые я бы делал иначе
Понятно, что не бывает идеальных решений, и с опытом приходит понимание, что та или иная часть сделана неоптимально. Я скажу, что я бы переделал, делай я игру сейчас.
Первый момент — это выбор коробок. Если вы откроете игру, и посмотрите на коробки, вы увидите, что на каждой коробке есть точки. В зависимости от сложности, этих точек больше или меньше. Так вот — для расстановки этих точек я написал класс, который загружает json-файл, и по этому конфиг-файлу расставляет точки. Мотивация, почему я так делал — сэкономить место в текстурных атласах. То есть, мы храним общую картинку-фон коробки, одну точку, и порядок расстановки этих точек для конкретной коробки. Почему это плохо — сложность модификации. Если бы каждая коробка была отдельной картинкой, достаточно подправить картинку — а теперь нужно лезть в конфиг-файлы и разбираться что и как. Лучше бы хранить это просто картинками.
Каждая коробка — это не просто картинка. Это подложка + текстовый файл-конфиг с расположением точек.
Второй момент — я очень заморочился с оптимизацией (пример выше — тому подтверждение). Все экраны (меню, выбор коробок, игровой и т.д.) я создал в одном экземпляре, и просто переключался между ними. Это избавило от создания этих же экранов каждый раз, когда мы переходим в другой экран, но привело к проблеме очистки. Например, игровой экран хранит множество параметров для конкретного уровня (например, очки, время и т.д.). Когда мы загружаем новый уровень, нужно сначала сбросить состояние игрового экрана. Но по мере добавления новых фишек легко было забыть что-то очистить. Это «сьело» приличный кусок времени, отладка таких проблем. Когда я делаю игру сейчас, я делаю иначе. Если нужно сменить экран — я сохраняю нужные для этого экрана параметры в каком-то классе настроек, потом создаю новый экран. Тот новый экран создается, и читает нужные данные из класса настроек. При этом больше тратится ресурсов, но на фоне современных мощных телефонов это время незаметно.
Приведенный пример про экраны — лишь один из. Плюс такого подхода — игра не лагает даже на самых плохих телефонах. Минус подхода — а много ли их, таких плохих телефонов, чтобы тратить так много времени на оптимизацию? Для себя я решил проблему так — при покупке нового смартфона я намеренно выбирал характеристики чуть ниже средних. Если игра на нем идет без тормозов, я не заморачиваюсь над оптимизацией — у большинства людей игра запустится и пойдет нормально.
Заключение
Идеальных игр не бывает, как и не бывает идеальной разработки. Спустя время, мы довели игру до конца, и она увидела свет в Google Play. Что сказать — несмотря на все шишки и грабли, заниматься разработкой игр интересно. Всяких моментов возникает много, некоторые моменты не решаемы, и приходится искать обходные пути. Но решение этих проблем поднимает ваш уровень, как разработчика. Мне было интересно писать эту игру, и я верю, что в нее будет интересно играть.
Комментарии (21)
Goodkat
31.10.2016 13:08На иконке у вас шарик зеркальный с отражением комнаты, бортиков и дракона на иконке — я уж думал прочитать под катом, как вы использовали камеру для формирования отражения на шарике и теней в дырках :)
egoserg
31.10.2016 14:29Если не секрет.
Как монетизировали игру, какой выхлоп?iMrDron
31.10.2016 15:05Мы долго подбирали настройки акселерометра для комфортной игры, но наш тестер постоянно отмечал, что что-то не так.
Установил вашу игру и ещё одну из google play. Покатал и там и там по 5 минут, и честно говоря у вас движение шарика показалось очень нереалистичным и заторможенным. Даже если наклонить телефон на 90 градусов, шарик движется очень медлено. Я так же нашел у себя металический шарик и ровную деревянную поверхность и тоже покатал там шарик 5 минут, и ещё раз убедился что в вашей игре шарик двигается не реалистично. Попробуйте может ещё поработать с настройками акселерометра и физики? Хотя если поменять физику, пользователям, которые уже привыкли к текущей физике, может это тоже не понравится.
1nt3g3r
31.10.2016 15:06Монетизация — реклама + покупки (отключение рекламы, покупка платных уровней). За выхлоп — еще ничего не скажем, потому что игра лишь несколько дней в Google Play.
hardex
31.10.2016 15:19-2Верстка в жопе, цвета текста от балды. Типичные проблемы первой игры.
1nt3g3r
31.10.2016 16:45Да, ошибки в дизайне возможны. В свое оправдание скажу лишь, что дизайном занимался художник. А вообще поддерживаю alrdev — если напишете статью, где "на пальцах" обьясните основные ошибки в дизайне — будет очень круто, с удовольствием почитаю.
hardex
31.10.2016 16:59-3Раньше я бы и сделал статью. Но сейчас Хабр словно вымер, и желания тратить пару-тройку часов на поиск и оформление материала, чтобы получить пять комментариев практически нет.
P.S. насчет дизайнера — на вашем этапе лучше купить готовый кит с оформлением, чем полагаться на начинающего фрилансера.Edmunds
07.11.2016 16:32Совсем недавно пытался найти более менее приличный готовый кит — это непросто. Основная масса это какой ужасный треш. Соглашусь, что дизайн данной игры не верх мастерства.
hardex
31.10.2016 16:46+1https://hsto.org/files/312/4c8/ae0/3124c8ae0835429e8aa73fa01a311622.jpg — стремная плашка, надпись на кнопке не выровнена, все в куче, заголовок ближе к надписи, чем та к своей кнопке.
https://hsto.org/files/3cd/2b7/029/3cd2b7029c9741349b565dbe0dbd32e0.jpg — стремная плашка, стремная обрезка при прокрутке, верхние строки ближе к границе, чем к своим собратьям
https://hsto.org/files/c20/a52/556/c20a525563c84701a0ed986f952f6612.jpg — стремные плашки, стрелка залезает на контент, время выровнено абы как.alrdev
31.10.2016 17:34Спасибо. Я думаю может вам действительно стоит написать статью? Я сомневаюсь на счет «пять комментариев», я думаю их будет много больше. Я не имею отношения к данному проекту, просто у меня всегда проблема с UI.
perfect_genius
01.11.2016 16:28стремная плашка, надпись на кнопке не выровнена, все в куче, заголовок ближе к надписи, чем та к своей кнопке.
стремная плашка, стремная обрезка при прокрутке, верхние строки ближе к границе, чем к своим собратьям
стремные плашки, стрелка залезает на контент, время выровнено абы как.
sova
01.11.2016 03:46автору удачи) отдельный респект за выбор достаточно низкоуровневого движка в эпоху этих ваших unity)
1nt3g3r
01.11.2016 11:37Когда мы начинали писать эту игр, еще была возможность использования Unity. Другой парень собрал демку на Unity быстро, но поскольку она даже не запустилась на моем телефоне (на то время у меня был Samsung Galaxy Gio), то решили делать на проверенной технологии.
denismart
01.11.2016 11:33Конечно уважение что выпустили игру. НО.
Графика — просто стыдоба. Очень печально, что люди до сих пор считают то, что вы нарисовали графикой, еще и «очень красивой и красочной».1nt3g3r
01.11.2016 11:36По поводу графики — это субьективно. Многим нравится текущая графика, на гугл плей наоборот отмечают, что дизайн приятный.
zagayevskiy
Анимация движения шарика неестественная. Даже когда шарик катится еле-еле, кадры мельтешат как сумасшедшие.
1nt3g3r
Вы правы, и я упомянул про этот момент в статье. Для действительно правдивого движения нужно было бы использовать (или писать свой) 3D физ. движок + использовать 3D модель. Увы, на тот момент у меня не было необходимых знаний.